Back button is going forward and ignoring required fields
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) }}
Hard to tell what's wrong there. Are you still experiencing this issue? Can you narrow it down somehow?
I think the problem is with mapped=>false option. Am struggling atm with the same issue, unmapped fields not being transferred between steps ...
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.