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

Symfony2 Form Splitting in sub forms with css helper

我有大表格,我需要在移动设备上将其拆分为多个表格。

桌面 = 1 个大表格

Mobile = 2-3 更小的表格,当我验证 1 表格时,然后是新的页面 2 表格,依此类推..

我想以响应式的方式进行,而不是像 (http://mobile/blah.com)

这样的子域

PS:我想避免第三方包!!

建议、建议、指导任何对我有帮助的东西

我的控制器:

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()
    ));
}

我的表格类型:

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']);

谢谢大家

妮可

好吧,我认为这不是一项艰巨的任务。实际上这似乎有些明显(至少对我而言)。

您可以使用 TabViewA​​ccordion-like 视图之类的东西。所有这些都可以通过使用纯 CSS(也许 Javascript)来实现。

如您所见,它与 Symfony 完全无关。通过使用 CSS + 媒体查询,我相信你可以完成所需的 UI.

如果您想使用响应方式拆分表单,您应该在视图中格式化表单输出(例如 TWIG 文件)。将表单部件放在单独的容器中,然后使用 CSS 媒体查询和 JS。祝你好运!

几年前我做了一些事情,其中​​将一个 'step' var 传递到 formType 中,我在每个 post.

之后递增它

我在 formType 中使用了 if 语句来根据步骤值构建表单。

没有移动子域,只有 css 帮助

当您使用 FormBuilder::add() 时,您创建了一个 FormType 实例,它可以是:

  • 整个表格,
  • 一个字段,
  • 一个子表单。

所以他们的行为都是一样的。

然后您可以轻松地将 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';
    }
}

一个 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,
        ));
    }
    // ...
}

控制器

// 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(),
        ));
    }
}

查看

{# ::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) }}
...

替代使用移动检测

有很多 php 库可以检测移动浏览器。

例如https://github.com/serbanghita/Mobile-Detect 它为 symfony 提供了一个包: https://github.com/suncat2000/MobileDetectBundle

因此,您甚至可以在之前的实现中使用助手 :

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