craue / CraueFormFlowBundle

Multi-step forms for your Symfony project.
MIT License
736 stars 118 forks source link

Back button is going forward and ignoring required fields #297

Open theedov opened 6 years ago

theedov commented 6 years ago

Hey,

I have a problem with my form. When I press back button, it's actually going forward and ignoring all required field.

Here is my form:

Flow:

class CheckoutFlow extends FormFlow
{
    protected $revalidatePreviousSteps = false;
    protected $maxSteps = 4;
    protected $allowDynamicStepNavigation = true;
    protected function loadStepDescriptions() {
        return array(
            'name',
        );
    }

    protected function loadStepsConfig()
    {
        return [
            [
                'label' => 'Adresa',
                'form_type' => 'Web\ApiBundle\Form\CheckoutAddressForm',
            ],
            [
                'label' => 'Doprava',
                'form_type' => 'Web\ApiBundle\Form\CheckoutDeliveryForm',

            ],
            [
                'label' => 'Způsob platby',
                'form_type' => 'Web\ApiBundle\Form\CheckoutPaymentForm',
                'form_options' => [
                    'validation_groups' => ['shipping'],
                ],
            ],
            [
                'label' => 'Dokončení',
                'form_type' => 'Web\ApiBundle\Form\CheckoutCompleteForm',

            ],
        ];
    }

    public function getFormOptions($step, array $options = array())
    {
        $options = parent::getFormOptions($step, $options);
        $formData = $this->getFormData();

        if ($step === 3)
            $options['validation_groups']['shipping'] = $formData->getShipping() ? $formData->getShipping()->getId() : null;

        return $options;
    }

}

Step1:

class CheckoutAddressForm extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        switch ($options['flow_step']) {
            case 1:
                $builder->add('customer', BillingInfoType::class, [
                    'data_class' => 'Web\ShopBundle\Entity\Customers'
                ]);
                break;
        }
    }

    public function setDefaultOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'category' => null,
        ));
    }

    public function getBlockPrefix()
    {
        return 'order_address';
    }

}

Step2:

class CheckoutDeliveryForm extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        switch ($options['flow_step']) {
            case 2:
                $builder->add('shipping', EntityType::class, [
                    'class' => 'Web\ShopBundle\Entity\Shipping',
                    'constraints' => [
                        new NotBlank(['message' => 'Způsob dopravy je povinný'])
                    ],
                    'expanded' => true,
                    'choice_label' => 'title',
                ]);
                break;
        }
    }

    public function setDefaultOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'shipping' => null,
        ));
    }

    public function getBlockPrefix()
    {
        return 'order_shipping';
    }

}

Step3:

class CheckoutPaymentForm extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        switch ($options['flow_step']) {
            case 3:
                $builder->add('payment', EntityType::class, [
                    'class' => 'Web\ShopBundle\Entity\Payment',
                    'constraints' => [
                        new NotBlank(['message' => 'Způsob Platby je povinný'])
                    ],
                    'query_builder' => function (EntityRepository $er) use ($options) {
                        return $er->createQueryBuilder('p')
                            ->leftJoin('p.shipping', 's')
                            ->where('s.id = :shipping_id ')
                            ->setParameter('shipping_id', $options['validation_groups']['shipping']);
                    },
                    'expanded' => true,
                    'choice_label' => 'title',
                ]);
                break;
        }
    }

    public function getBlockPrefix()
    {
        return 'order_payment';
    }

}

Step4:

  public function buildForm(FormBuilderInterface $builder, array $options)
    {
        switch ($options['flow_step']) {
            case 4:
                $builder
                    ->add('policy', CheckboxType::class, [
                        'mapped' => false,
//                    'label'    => 'Souhlasím s obchodními podmínkami',
                        'required' => true
                    ])
                    ->add('survey', TextType::class, [
                        'required' => false
                    ])
                ;
                break;
        }
    }

    public function getBlockPrefix()
    {
        return 'order_payment';
    }

Controller:

$flow = $this->get('checkout.form');
        $flow->bind($order);
        $form = $flow->createForm();
        if ($flow->isValid($form)) {
            $flow->saveCurrentStepData($form);
            if ($flow->nextStep()) {
                $form = $flow->createForm();
            } else {

                    //insert data to the db
            }
        }

        return $this->render('@WebApi/Checkout/index.html.twig', [
            'form' => $form->createView(),
            'flow' => $flow,
            'order' => $order
        ]);

View:

{{ form_start(form) }}
                    {{ form_errors(form) }}
                    {{ form_row(form._token) }}
                    <ul id="checkoutsteps" class="clearfix panel-group">

                        <li class="panel">
                            <a href="#step-1" class="step-title" data-parent="#checkoutsteps" data-toggle="collapse">
                                <div class="number">1</div>
                                <h6>Fakturační a doručovací údaje</h6>
                            </a>

                            <div id="step-1" class="collapse in">
                                {% if flow.getCurrentStepNumber() == 1 %}
                                    <div class="step-content">
                                        <div class="row no-margin">
                                            <div class="col-sm-6 col-md-12">
                                                <label>Cele jméno nebo název firmy: <span class="required">*</span></label>
                                                {{ form_widget(form.customer.name, { 'attr' : { 'class' : 'form-control' } }) }}
                                                {{ form_errors(form.customer.name) }}
                                            </div>
                                            ...

                                            <div class="clearfix"></div>

                                            <div class="col-sm-12 buttons-box text-right">
                                                {% include '@CraueFormFlow/FormFlow/buttons.html.twig' with {
                                                craue_formflow_button_class_last: 'btn btn-default',
                                                craue_formflow_button_class_back: 'btn btn-default',
                                                craue_formflow_button_class_reset: 'btn btn-default',
                                                craue_formflow_button_render_reset: false
                                                } %}
                                                <span class="required"><b>*</b> Povinné pole</span>
                                            </div>
                                        </div>
                                    </div>
                                {% endif %}
                            </div>
                        </li>

                        <li class="panel">
                            <a href="#step-2" class="step-title" data-parent="#checkoutsteps" data-toggle="collapse">
                                <div class="number">2</div>
                                <h6>Způsob dopravy</h6>
                            </a>

                            <div id="step-2" class="collapse in">
                                {% if flow.getCurrentStepNumber() == 2 %}

                                    <div class="step-content">
                                        <div class="row no-margin">
                                            <div class="col-sm-6 col-md-6">
                                                <label>Vyberte způsob dopravy: <span class="required">*</span></label>
                                                {% for shipping in form.shipping %}
                                                    {% set index = shipping.vars.value %}
                                                    {% set entity = form.shipping.vars.choices[index].data %}
                                                    <label data-toggle="tooltip" class="radio">
                                                        {{ form_widget(shipping) }}{{ form_label(shipping) }}
                                                        {{ entity.price }} Kč
                                                    </label>
                                                {% endfor %}
                                                {{ form_errors(form.shipping) }}
                                            </div>
                                            <div class="col-sm-12 buttons-box text-right">
                                                {% include '@CraueFormFlow/FormFlow/buttons.html.twig' with {
                                                craue_formflow_button_class_last: 'btn btn-default',
                                                craue_formflow_button_class_back: 'btn btn-default',
                                                craue_formflow_button_class_reset: 'btn btn-default',
                                                craue_formflow_button_render_reset: false
                                                } %}
                                            </div>
                                            <div class="clearfix"></div>
                                        </div>
                                    </div>
                                {% endif %}
                            </div>
                        </li>

                        <li class="panel">
                            <a href="#step-3" class="step-title" data-parent="#checkoutsteps" data-toggle="collapse">
                                <div class="number">3</div>
                                <h6>Způsob platby</h6>
                            </a>
                            <div id="step-3" class="collapse in">
                                {% if flow.getCurrentStepNumber() == 3 %}
                                    <div class="step-content">
                                    <div class="no-margin">
                                        <label>Vyberte způsob platby: <span class="required">*</span></label>
                                        {% for payment in form.payment %}
                                            {% set index = payment.vars.value %}
                                            {% set entity = form.payment.vars.choices[index].data %}
                                            <label data-toggle="tooltip" class="radio">
                                                {{ form_widget(payment) }}{{ form_label(payment) }}
                                            </label>
                                        {% endfor %}
                                        {{ form_errors(form.payment) }}
                                        {% include '@CraueFormFlow/FormFlow/buttons.html.twig' with {
                                        craue_formflow_button_class_last: 'btn btn-default',
                                        craue_formflow_button_class_back: 'btn btn-default',
                                        craue_formflow_button_class_reset: 'btn btn-default',
                                        craue_formflow_button_render_reset: false
                                        } %}
                                        <div class="clearfix"></div>
                                    </div>
                                </div>
                                {% endif %}
                            </div>
                        </li>

                        <li class="panel">
                            <a href="#step-4" class="step-title" data-parent="#checkoutsteps" data-toggle="collapse">
                                <div class="number">4</div>
                                <h6>Dokončení objednávky</h6>
                            </a>

                            <div id="step-4" class="collapse in">
                                {% if flow.getCurrentStepNumber() == 4 %}
                                    <div class="step-content">
                                        <div class="row no-margin">
                                            <div class="col-sm-12 col-md-12">
                                                <label>Jak jste se o nas dozvěděl(a)?:</label>
                                                {{ form_widget(form.survey, { 'attr' : { 'class' : 'form-control' } }) }}
                                                {{ form_errors(form.survey) }}
                                            </div>
                                            <div class="col-sm-12 col-md-12">
                                                <label class="checkbox">
                                                    <input type="checkbox">
                                                    {{ form_widget(form.policy) }} Souhlasím s obchodními podmínkami
                                                    <span class="required">*</span>
                                                    {{ form_errors(form.policy) }}
                                                </label>
                                            </div>

                                            <div class="clearfix"></div>

                                            <div class="col-sm-12 buttons-box text-right">
                                                {% include '@CraueFormFlow/FormFlow/buttons.html.twig' with {
                                                craue_formflow_button_class_last: 'btn btn-default',
                                                craue_formflow_button_class_back: 'btn btn-default',
                                                craue_formflow_button_class_reset: 'btn btn-default',
                                                craue_formflow_button_render_reset: false
                                                } %}
                                                <span class="required"><b>*</b> Povinné pole</span>
                                            </div>
                                        </div>
                                    </div>
                                {% endif %}
                            </div>
                        </li>
                    </ul>
                    {{ form_end(form) }}
craue commented 6 years ago

Hard to tell what's wrong there. Are you still experiencing this issue? Can you narrow it down somehow?

pculka commented 6 years ago

I think the problem is with mapped=>false option. Am struggling atm with the same issue, unmapped fields not being transferred between steps ...

floschm commented 5 years ago

Maybe the form was submitted via AJAX? I just found this behavior using jQuery's submit handler on the form. Serializing a form in the submit handler will not add the clicked button to the posted data, therefore Symfony's $form->get('mybutton')->isClicked() won't work.