I'm using craue/CraueFormFlowBundle bundle in a Symfony4 application to generate stepper from basic SYmfony4 form and it work fine until I try to introduce form Event to modify my form depending of form changes :
Controller :
**
* @Route("/user/add", name="add_user")
* @param Request $request
*/
public function add(Request $request){
$employe = new Employe();
$flow = $this->addUserFlow;
$flow->bind($employe);
// form of the current step
$form = $flow->createForm();
if ($flow->isValid($form)) {
$flow->saveCurrentStepData($form);
if ($flow->nextStep()) {
// form for the next step
$form = $flow->createForm();
} else {
//Persist user
$this->manager->persist($employe);
$this->manager->flush();
//Reset flow to create new user
$flow->reset();
//Redirect to new user detail page
return $this->redirect($this->generateUrl('user_edit', array('id' => $employe->getId())));
}
}
return $this->render('app/users/users/add.html.twig', [
'form' => $form->createView(),
'flow' => $flow,
'employe' => $employe
]);
}
AddUserFlow.php
class AddUserFlow extends FormFlow
{
protected $allowDynamicStepNavigation = true;
protected function loadStepsConfig()
{
return [
[
'label' => 'Informations générales',
'form_type' => UserGeneralType::class,
],
[
'label' => 'Contact',
'form_type' => EmployeContactType::class,
],
[
'label' => 'Contrat',
'form_type' => EmployeContractType::class,
],
[
'label' => "Accès a l'application",
'form_type' => EmployeAccessType::class,
]
];
}
}
Here is my step 3 form which contains the FormEvent :
/**
* Class ContractType
* @package App\Form\User\Stepper
*/
class ContractType extends AbstractType
{
/**
* @var EntityManagerInterface
*/
private $manager;
/**
* ContractType constructor.
*/
public function __construct(EntityManagerInterface $manager)
{
$this->manager = $manager;
}
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type', EntityType::class, [
"class" => TypeContract::class,
'label' => 'Type de contrat',
"required" => true
])
->add('startDate', DateType::class, [
'label' => 'Date de début',
'widget' => 'single_text',
"required" => false,
])
->add('endDate', DateType::class, [
'label' => 'Date de fin',
'widget' => 'single_text',
"required" => false,
])
->add('isInsertion', CheckboxType::class, [
'label' => 'Insertion professionnelle',
"required" => false,
])
->add('hourlyRate', MoneyType::class, [
'label' => 'Taux horaire',
"required" => false,
])
;
$formContractTypeModifier = function (FormInterface $form, $contractType = null) {
if($contractType != null && !$contractType instanceof TypeContract){
$contractType = $this->manager->getRepository(TypeContract::class)->find($contractType);
}
if($contractType != null && $contractType instanceof TypeContract && $contractType->getSlug() === ContractTypeEnum::INTERIM){
$form->add('company', EntityType::class, [
"class" => Company::class,
"label" => "Entreprise",
"required" => true,
]);
}
};
// Events listener to modify the form regarding PRE_SET_DATA datas
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($formContractTypeModifier){
/** @var array $data */
$data = $event->getData();
/** TypeContract $contractType */
$contractType = isset($data['type']) ? $data['type'] : null;
//Add elements to form
$formContractTypeModifier($event->getForm(), $contractType);
});
// Events listener to modify the form regarding PRE_SUBMIT
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($formContractTypeModifier){
/** @var array $data */
$data = $event->getData();
/** TypeContract $contractType */
$contractType = isset($data['type']) ? $data['type'] : null;
//Add elements to form
$formContractTypeModifier($event->getForm(), $contractType);
});
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => null,
]);
}
}
Here is my add_user.html.twig
view :
{% if flow.getCurrentStepNumber() == 3 %}
{% include 'app/users/users/stepper/_step_3.html.twig' %}
{% else %}
{{ form_errors(form) }}
{{ form_rest(form) }}
{% endif %}
The step_3.html.twig view is like this :
{{ form_row(form.contracts.type) }}
{% if form.contracts.company is defined %}
{{ form_row(form.contracts.company) }}
{% endif %}
{{ form_row(form.contracts.startDate) }}
{{ form_row(form.contracts.endDate) }}
{{ form_row(form.contracts.hourlyRate) }}
{{ form_row(form.contracts.isInsertion) }}
<script>
$( document ).ready(function() {
$('#employe_contract_contracts_type').on('change', function() {
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(html) {
console.log(html);
console.log($(html));
$('#add_employe_step_3_container').replaceWith($(html).find('#add_employe_step_3_container'));
}
});
});
});
</script>
My problem is that I'm not sure if CraueFlow is meant to work in the way I'm using it for FormEvent and I didnt find any documentation.
When I'm trying the way I just presented in this question I get a result from my Ajax call containing the first step of my Flow. The result is that $('#add_employe_step_3_container').replaceWith($(html).find('#add_employe_step_3_container'));
does not load anything in my container because it is not found.
When I go to the next step and then come back to the third, the new company
field appears in my step 3 because the global form seems to be updated and the formEvent has worked correctly.
Any ideas or examples ?
To refresh the form and reload errors and controls I just had to render the form without calling $flow->nextStep()
/**
* @Route("/user/add", name="user_add")
*/
public function add(Request $request, UserPasswordEncoderInterface $encoder, UniquePasswordResetToken $uniqueToken){
$employe = new Employe();
$flow = $this->addUserFlow;
$flow->bind($employe);
// form of the current step
$form = $flow->createForm();
if ($flow->isValid($form)) {
$flow->saveCurrentStepData($form);
if ($request->isXmlHttpRequest()) {
$form = $flow->createForm();
} else {
if ($flow->nextStep()) {
// form for the next step
$form = $flow->createForm();
} else {
//Persist user
$this->manager->persist($employe);
$this->manager->flush();
//Persist change password user token
$this->manager->persist($uniqueToken->getToken($employe));
$this->manager->flush();
//Reset flow to create new user
$flow->reset();
//Redirect to new user detail page
return $this->redirect($this->generateUrl('user_edit_personnal_info', array('id' => $employe->getId())));
}
}
}
return $this->render('app/users/users/add.html.twig', [
'form' => $form->createView(),
'flow' => $flow,
'employe' => $employe
]);
}