简体   繁体   English

Symfony2使用css助手在子表单中拆分表格

[英]Symfony2 Form Splitting in sub forms with css helper

I have large form, i need to split it in multiple form when on mobile view. 我的表格很大,在移动视图上时需要将其拆分为多种形式。

Desktop = 1 large form 桌面= 1个大表格

Mobile = 2-3 smaller form, when i valid the 1 form, then new page 2 form, and so on.. 移动版= 2-3个较小的表格,当我验证1表格时,然后是新的第2表格,依此类推。

I would like to do it in responsive way NOT sub-domaine like ( http://mobile/blah.com ) 我想以响应方式做到这一点,而不是像( http://mobile/blah.com )这样的子

PS: I want to avoid third party bundle !! PS:我想避免第三方捆绑!

Advice, recommandation, direction anything than can help me 忠告,建议,指导无所不能

My controller: 我的控制器:

public function  ownerRegisterAction(Request $request)
{
    $em = $this->getDoctrine()->getManager();
    $owner = new Owner();
    $form = $this->createCreateForm($owner);
    $form->handleRequest($request);

    if($form->isSubmitted() && $form->isValid()) 
    {   
        $password = $this->get('security.password_encoder')
            ->encodePassword($owner->getOwner(), $owner->getOwner()->getPassword());
        $owner->getOwner()->setPassword($password);
        $owner->getOwner()->setStatus('owner');
        $owner->getOwner()->setIsValid(0);
        $em->persist($owner);
        $em->flush();

        // Login users after registration 
        $this->get('apx_auth_after_register')->authenticateUser($tenant->getTenant());

        $response = $this->forward('PagesBundle:SearchOwner:search', ['owner' => $owner]);

        return $response;

    }

    return $this->render('::form/owner-register.html.twig', array(
        'form' => $form->createView()
    ));
}

My Form Type : 我的表格类型:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('corporate', CorporateType::class, ['expanded' => true, 'multiple' => false, 'label' => false])
        //->add('postcode', NumberType::class,['label' => false])
        ->add('type', TypeType::class, ['expanded' => true, 'multiple' => false,'label' => false])
        ->add('room', NbRoomType::class,['expanded' => true, 'multiple' => false,'label' => false])
        ->add('rent', NumberType::class,['label' => false])
        ->add('area', NumberType::class,['label' => false])
        ->add('images', CollectionType::class, [
                            'entry_type' => ImagesFlatType::class,
                            'allow_add' => true,
                            'required' => false,
                            'allow_delete' => true,
                            'label' => false,
                            ])

        ->add('fee', NumberType::class, ['label' => false, 'required' => false])
        // to be defined in list  by city
        ->add('transport', TextType::class,['label' => false, 'required' => false])
        ->add('furnished', FurnishedType::class, ['expanded' => true, 'multiple' => false,'label' => false])
        ->add('vip', LesVipType::class, ['expanded' => true, 'multiple' => true,'label' => false])
        ->add('feature', FeatureType::class, ['expanded' => true, 'multiple' => true,'label' => false])
        ->add('description', TextareaType::class,['label' => false, 'required' => false])

        // City ajax call to be fix 
        ->add('location', EntityType::class, ['label' => false,
                                            'class' => 'PagesBundle:City',
                                            'choice_label' => 'zone']);

Thanks all 谢谢大家

Nico 尼科

Well, I don't think it is a hard task. 好吧,我认为这不是一个艰巨的任务。 Actually it's seems somewhat obvious (at least for me though). 实际上,这似乎有些明显(至少对我而言)。

You could to use something like TabView or Accordion-like view. 您可以使用TabView类似Accordion的视图。 All of this can be achieved by using pure CSS (and maybe Javascript). 所有这些都可以通过使用纯CSS(甚至Javascript)来实现。

As you can see it is not related to Symfony at all. 如您所见,它与Symfony根本无关。 By using CSS + Media-queries I'm sure you can get done the desired UI. 通过使用CSS +媒体查询,我确定您可以完成所需的UI。

If you want to split form using responsive way, you should format form output in your view (eg TWIG file). 如果要使用响应方式拆分表单,则应在视图中格式化表单输出(例如TWIG文件)。 Place form parts in separate containers and then use CSS media queries and JS. 将表单部件放在单独的容器中,然后使用CSS媒体查询和JS。 Good luck! 祝好运!

I did something a few years back in which a passed a 'step' var into the formType which I incremented after each post. 几年前,我做了一些事情,在其中将一个“ step”变量传递给formType,该变量在每次发布后都会递增。

I used an if statement in the formType to build the form depending on the step value. 我在formType中使用了if语句来根据步骤值构建表单。

Without mobile subdomain and with css help only 没有移动子域并且仅具有CSS帮助

When you use FormBuilder::add() you create an instance of FormType which can be : 当您使用FormBuilder::add()您将创建一个FormType实例,该实例可以是:

  • the entire form, 整个表格,
  • a field, 一个领域
  • a sub form. 子表格。

So they all behave the same way. 因此,它们的行为均相同。

Then you can easily split your FormType in 3 SubFormTypes : 然后,您可以轻松地将FormType拆分为3个SubFormTypes

namespace AppBundle\Form;

/*
 * When forms get heavy, it becomes handy to use aliases
 * on import use statements since it eases the reading in code
 * and reduces the list of imports.
 */
use Type as AppType;
use Symfony\Bridge\Doctrine\Form\Type as OrmType;
use Symfony\Component\Form\Extension\Core\Type as CoreType;

// An abstract class which all sub form will extend.
abstract class AbstractSubFormType extends AbstractType
{
    /*
     * We need to add a submit button on the sub form
     * which will be only visible on mobile screens.
     *
     * Then to get help of css class :
     *  use ".submit-sub_owner { display: 'none'; }" for desktop screens,
     *  and ".submit-sub_owner { display: 'block'; }" for mobile screens.
     *
     * Plus we need dynamic hiding of next sub forms from controller :
     * use ".sub_owner-next { display: 'none'; }" for mobile screens.
     */
    public function buildForm(FormBuilderInterface $builder, array $options = array())
    {
        $builder->add('submit', CoreType\SubmitType::class,
            'attr' => array(
                // Needed to target sub owner controller.
                'formaction' => $options['sub_action'],
                // Hides sub owners submit button on big screens.
                'class' => 'submit_sub_owner',
            )
        ));
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $subNextNormalizer = function (Options $options, $subNext) {
            $hideNextSubOwners = isset($options['sub_next']) && $subNext;
            // Sets "attr" option of this sub owner type.
            $options['attr']['class'] = $hideNextSubOwners ? 'sub_owner-next' : '';
        }

        // Allows custom options.
        $resolver->setDefaults(array('sub_action' => '', 'sub_next' => false));

        // Hides sub owners exept the first stage from main controller.
        $resolver->setNormalizer('sub_next', $subNextNormalizer);
    }
}

// Stage 1
class SubOwnerOne extends AppType\AbstractSubFormType
{
    public function buildForm(FormBuilderInterface $builder, array $options = array())
    {
        // Arbitrary cut of your example
        $builder
            ->add('corporate', AppType\CorporateType::class)
            ->add('type', AppType\TypeType::class)
            ->add('room', AppType\NbRoomType::class);

        // Call parent to optionnaly add sumbit button
        parent::builForm($builder, $options);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array('expanded' => true, 'multiple' => false));
    }

    public function getName()
    {
        return 'sub_owner_1';
    }
}

// Stage 2
// note that this one is tricky because there is an image that may be a file.
class SubOwnerTwo extends AppType\AbstractSubFormType
{
    public function buildForm(FormBuilderInterface $builder, array $options = array())
    {
        // Arbitrary cut of your example
        $builder
            //->add('postcode')
            ->add('area')
            ->add('rent')
            ->add('images', CoreType\CollectionType::class, array(
                'entry_type' => AppType\ImagesFlatType::class,
                'allow_add' => true,
                'allow_delete' => true,
                'required' => false,
            ));

        // Call parent to optionnaly add sumbit button
        parent::builForm($builder, $options);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefault('data_class', CoreType\NumberType::class);
    }

    public function getName()
    {
        return 'sub_owner_2';
    }
}

// Last stage
class SubOwnerThree extends AppType\AbstractSubFormType
{
    public function buildForm(FormBuilderInterface $builder, array $options = array())
    {
        // Arbitrary cut of your example
        $builder
            ->add('fee', CoreType\NumberType::class, array('required' => false))
            // to be defined in list by city
            ->add('transport', CoreType\TextType::class, array('required' => false))
            ->add('furnished', AppType\FurnishedType::class) // define options in class
            ->add('vip', AppType\LesVipType::class)          // when belongs to AppType
            ->add('feature', AppType\FeatureType::class)     // ...
            ->add('description', CoreType\TextareaType::class, array('required' => false))
            // City ajax call to be fix
            ->add('location', OrmType\EntityType::class, array(
                'class' => 'PagesBundle:City',
                'choice_label' => 'zone',
            ));

        // Call parent to optionnaly add sumbit button
        parent::builForm($builder, $options);
    }

    public function getName()
    {
        return 'sub_owner_3';
    }
}

One FormType to wrap them all : 一种将它们全部包装的FormType

class OwnerType extends AbstractType
{
    public function createForm(FormBuilderInterface $builder, array $options = array())
    {
        $sub = isset($option['sub_action']) ? $options['sub_action'] : false;
        $next = isset($option['sub_next']) ? $options['sub_next'] : false;

        $builder
            ->add('stage_one', AppType\SubOwnerOne::class, array(
                'sub_action' => $sub, // get form action from controllers.
            ))
            ->add('stage_two', AppType\SubOwnerTwo::class, array(
                'sub_action' => $sub,
                'sub_next' => $next, // hide sub owners from main controller on mobile screens.
            ))
            ->add('final', AppType\SubFormTypeThree::class, array(
                'sub_action' => $sub,
                'sub_next' => $next,
            ))
            ->add('submit', CoreType\SubmitType::class, array(
                'attr' => array(
                    // Allows using ".submit-owner { display: 'none'; }" for mobile screens.
                    'class' => 'submit-owner',
                ),
            ));
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'label' => false,
             // Allows custom options
            'sub_action' => '',
            'sub_next' => false,
        ));
    }
    // ...
}

Controllers : 控制器

// Holds two controllers, one for owner type and another for sub owner types.
class OwnerController extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
    /*
     * Main controller.
     *
     * This handles initial request
     * and renders a view with a form view of OwnerType
     */
    public function  ownerRegisterAction(Request $request)
    {
        $owner = new Owner();

        // Customizes options
        $form = $this->createFormBuilder($owner, array(
            // Uses route name of the other controller
            'sub_action' => $this->generateUrl('handle_sub_owners'),
            // Hides next stages
            'sub_next' => true,
        ))->getForm();

        $form->handleRequest($request);

        if($form->isSubmitted && $form->isValid()) {
            $password = $this->get('security.password_encoder')
                ->encodePassword($owner->getOwner(), $owner->getOwner()->getPassword());
            $owner->getOwner()->setPassword($password);
            $owner->getOwner()->setStatus('owner');
            $owner->getOwner()->setIsValid(0);

            $em = $this->getDoctrine()->getManager();
            $em->persist($owner);
            $em->flush();

            // Login users after registration
            $this->get('apx_auth_after_register')->authenticateUser($tenant->getTenant());

            // Forwards quiting registration process
            return $this->forward('PagesBundle:SearchOwner:search', array('owner' => $owner));
        }

        // This view should use css rules from above comments
        return $this->render('::form/owner-register.html.twig', array(
            'form' => $form->createView(),
        ));
    }

    /**
    * Secondary controller handles sub forms
    * and renders one form view among the sub forms.
    *
    * @Route('/_sub_owners', name="handle_sub_owners")
    */
    public function handleSubOwnersAction(Request $request)
    {
        // Customize sub form action
        $action = array('sub_action' => $this->generateUrl('handle_sub_owners'));
        // Option "sub_next" will default to false.

        $form = $this->createForm(AppType\OwnerType::class, new Owner(), $action);
        $form->handleRequest($request);

        $subOwner1 = $form->get('stage_one');
        $subOwner2 = $form->get('stage_two');
        $finalOwner = $form->get('final');

        // Last stage is done, reforwards to the main controller.
        if ($finalOwner->isSubmitted() && $finalOwner->isValid()) {
            // Submits $data to new OwnerType as $form has been submitted by "handleRequest()" call
            $owner = $this->createForm(AppType\OwnerType::class, new Owner());
            $owner->get('stage_one')->submit(json_decode($finalOwner->get('j_stage_one')->getData()));
            $owner->get('stage_two')->submit(json_decode($finalOwner->get('j_stage_two')->getData()));
            $owner->get('final')->submit($finalOwner->getData());

            // Form in main controller will handle the request again,
            // so we need to pass normalized data.
            return $this->forward('App:Owner:ownerRegister', array(), $owner->getNormData())
        }

        // Stage 2 is done
        if ($subOwner2->isSubmitted() && $subOwner2->isValid()) {
            // Gets back json of stage 1
            $finalOwner->add('j_stage_one', 'hidden', array(
                // Unmaps this hidden field as it won't match any property of Owner
                'mapped' => false,
                'data' => $subOwner1->get('j_stage_1')->getData(),
            ));
            // Saves json of stage 2
            $finalOwner->add('j_stage_two', 'hidden', array(
                'mapped' => false,
                'data' => json_encode($subOwner2->getData(), true),
            ));

            // Renders final stage
            return $this->render('::form/owner-register.html.twig', array(
                'form' => $finalOwner->createView(),
            ));
        }

        // Stage 1 is done
        if ($subOwner1->isSubmitted() && $subOwner1->isValid()) {
            // Save json of $subOwner1
            $subOwner2->add('j_stage_one', 'hidden', array(
                'mapped' => false,
                'data' => json_encode($subOwner1->getData(), true),
            ));

            // Render stage 2
            return $this->render('::form/owner-register.html.twig', array(
                'form' => $subOwner2->createView(),
            ));
        }

        // Else renders stage 1
        return $this->render('::form/owner-register.html.twig', array(
            'form' => $subOwner1->createView(),
        ));
    }
}

View 视图

{# ::form/owner-register.html.twig #}
...
<style type="text/css">
    /* @media mobile screens */
    .sub_owner-next, .submit-owner { display: 'none'; }
    .submit-sub_owner { display: 'block'; }

    /* @media desktop screens */
    .submit-sub_owner { display: 'none'; }
</style>
...
{{ form_start(form) }}
{% form sub_form in form %}
    {{ form_start(sub_form) }}
    {{ form_widget(sub_form) }}
    {{ form_end(sub_form) }}
{% endear %}
{{ form_end(form) }}
...

Alternative use Mobile Detection 替代用途移动检测

There are many php libraries to detect mobile browsers. 有许多php库可以检测移动浏览器。

For example https://github.com/serbanghita/Mobile-Detect which provides a bundle for symfony : https://github.com/suncat2000/MobileDetectBundle 例如https://github.com/serbanghita/Mobile-Detect ,它提供了symfony的捆绑包: https : //github.com/suncat2000/MobileDetectBundle

So you can even use a helper in a previous implementation : 因此,您甚至可以在以前的实现中使用帮助器:

$mobileDetector = $this->get('mobile_detect.mobile_detector');
$mobileDetector->isMobile();
$mobileDetector->isTablet()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM