Sonata Admin and Dynamic Form - symfony

I made several search but I still have a problem...
I want to make a dynamic form. I want to hydrate a select in function of an other select.
This is my configureFormFields:
protected function configureFormFields(FormMapper $formMapper)
{
$emIndustry = $this->modelManager
->getEntityManager('*\*\*\*\Entity\Industry')
;
$query = $emIndustry->getRepository(*:Industry')
->getFindAllParentsQueryBuilder()
;
$formMapper
->add('company')
->add('industry', 'sonata_type_model', [
'attr' => [
'onchange' => 'submit()',
],
'query' => $query,
'required' => false,
])
->add('subIndustry', 'sonata_type_model', [
'choices' => [],
'required' => false,
])
;
$builder = $formMapper->getFormBuilder();
$factory = $builder->getFormFactory();
$subject = $this->getSubject();
$modelManager = $this->getModelManager();
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use($formMapper, $subject, $emIndustry, $modelManager, $factory) {
$form = $event->getForm();
if(!is_null($subject->getIndustry())) {
$query = $emIndustry->getRepository('*:Industry')
->getFindChildrenByParentQueryBuilder($subject->getIndustry())
;
$form
->add(
$factory->createNamed('subIndustry', 'sonata_type_model', null, [
'class' => '*\*\*\*\Entity\Industry',
'query' => $query,
'required' => false,
])
)
;
}
});
}
When I change the value of the select Industry, no problem my form is submited. But nothing happend in second select subIndustry because : all attributes of my $subject object is null...
Have you any idea why ? Is there a best way to make a dynamic form ?
Thank's for your help.
AlexL

Related

Symfony Form - Add required fields to form depending on other field value

I want to add some required fields to my form when an other field has some value. I've tried to do it with PRE_SET_DATA event but I cannot get data in my event.
My example here is to add partner name field when a user is married.
My UserType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('familyStatus', ChoiceType::class, [
'label' => 'Statut de famille',
'label_attr' => [
'class' => 'fg-label'
],
'attr' => [
'class' => 'sc-gqjmRU fQXahQ'
],
'required' => true,
'choices' => [
'Married' => 'M',
'Single' => 'S'
]
])
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$user = $event->getData();
$status = $user->getFamilyStatus(); // Give me NULL
//$status = $form->get('familyStatus')->getData() Give me NULL too
/*
if ($user && $status === 'M') {
$form->add('partnerName', TextType::class, [
'required' => true,
'mapped' => false
]);
)
*/
})
;
}
What's wrong ? How can I add dynamically new fields depending on other field ?
I also tried with POST_SET_DATA but it's not working.
You need the second example from this part of the docs link.
Basically you set the event listener to the entire form. You should add another listener to the field itself with POST_SUBMIT event.

Symfony Dynamic Form - Get file from embedded form gives NULL

I have a dynamic form which display/hide a FileType field depending on another field value (link).
When i'm trying to get the file in my controller, it always gives me NULL
My UserType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('familyStatus', ChoiceType::class, [
'label' => 'Statut de famille',
'label_attr' => [
'class' => 'fg-label'
],
'attr' => [
'class' => 'sc-gqjmRU fQXahQ'
],
'required' => true,
'choices' => [
'Married' => 'M',
'Single' => 'S'
]
]);
$formModifier = function (FormInterface $form, $status = null) {
if ($status === 'M') {
$form->add('familyInfo', FamilyInfoType::class);
}
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$user = $event->getData();
$formModifier($event->getForm(), $user->getFamilyStatus());
}
);
$builder->get('familyStatus')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$status = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $status);
}
);
}
My FamilyInfoType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('partnerName', TextType::class, [
'label' => 'Nom du partenaire',
'label_attr' => [
'class' => 'fg-label'
],
'attr' => [
'class' => 'sc-gqjmRU fQXahQ'
],
'required' => true
])
->add('weddingProof', FileType::class, [
'label' => 'Acte de mariage',
'label_attr' => [
'class' => 'Upload-label'
],
'attr' => [
'class' => 'Upload-input',
'maxsize' =>'4M',
'accept' =>'image/*'
],
'required' => false,
'mapped' => false
]);
}
My UserController:
/**
* #Route("/user", name="add_user", methods={"GET", "POST"})
* #param Request $request
*/
public function addUser(Request $request) {
$response = [];
$user = new User();
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
if ($user->getFamilyStatus() === 'M') {
var_dump("ok");
$document = $form->get('familyInfo')->get('weddingProof')->getData();
$partnerName = $form->get('familyInfo')->get('partnerName')->getData();
var_dump($document); // NULL
var_dump($partnerName); // OK the value is displayed
die;
}
}
return $this->render("user/registration.html.twig", ['form' => $form->createView()]);
}
It's working with TextType field but not with FileType. What's wrong with my form.
The issue is with the mapped => false option to the file type. The form is not setting the uploaded file object in the property of your entity because it’s not mapped.

how to map violation to Symfony form manually?

OrderType
$builder
->add('items', FormTypes\CollectionType::class, [
'entry_type' => OrderItemType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'label' => 'acme.form.order.items',
])
->add('channel', ChannelSelectType::class, [
'required' => true,
'label' => 'acme.form.order.channel',
])
OrderItemType
$builder
->add('service', ServiceSelectType::class, [
'label' => 'acme.form.order_item.service',
])
->add('product', ProductSelectType::class, [
'label' => 'acme.form.order_item.product',
])
->add('quantity', FormTypes\IntegerType::class, [
'label' => 'acme.form.order_item.quantity',
]);
How to map the error to OrderItemType product field?
the order item is valid when a product is applied to a specific channel. however we have no way to get the submited channel in OrderItemType,
because child form type is submmited before its parent. so $event->getForm()->getParent()->getData()->getChannel() is empty. the only way I have
is to validate order item in OrderType, or create a validator which is added to Order class. the problem is how can I map the error to OrderItemType product field.
$orderItems = $order->getItems();
$channel = $order->getChannel();
foreach($orderItems as $index => $orderItem) {
$product = $orderItem->getProduct();
if (!$this->isProductAvailableForChannel($channel, $product)) {
$message = sprintf('product %ss is not available for channel "%s"', $product->getName(), $channel->getName());
}
if (null !== $message) {
$this->context
->buildViolation($this->constraint->message)
->setParameter($message)
->atPath("items.children[$index].product") // this doesn't work, the error will be added to root form.
->addViolation()
;
}
}
You are able to get the channel on the pre submit event (Symfony\Component\Form\FormEvents::PRE_SUBMIT)
And there you can add your channel based validation for the order item
$addItems = function (FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
$options = [];
if (is_array($data) && array_key_exists('channel', $data)) {
$options['constraints'] = [
new OrderItemConstraint(['channel' => $data['channel']])
];
}
$form->add('items', FormTypes\CollectionType::class, [
'entry_type' => OrderItemType::class,
'entry_options' => $options,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'label' => 'acme.form.order.items',
]);
};
$builder->addEventListener(FormEvents::PRE_SET_DATA, $addItems);
$builder->addEventListener(FormEvents::PRE_SUBMIT, $addItems);

Symfony Dynamic Generation for Submitted Forms with Form events

I'm in Symfony 3.2.4 and I have 3 entities : Country - Province - City. And I want do 3 fields populated dynamically.
I explain, I've 3 fields: Country > Province > City, the user choose a Country, the Province list is updated, he choose a Province, the City list is updated.
But I've a problem, for just one field dynamically updated, it's work, but no for 2 fields... Actually a user can choose a Country, and when he does it, the Province field is updated. But, when he choose a Province, the City field wasn't updated...
Here is my code :
# FormType
...
$builder->addEventSubscriber(new DynamicFieldsSubscriber());
...
# DynamicFieldsSubscriber
class DynamicFieldsSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmitData'
);
}
public function preSetData(FormEvent $event)
{
$location = $event->getData();
$form = $event->getForm();
$country = "";
$province = "";
$city = "";
if ($location) {
// collect preliminary values for 3 fields.
$country = $location->getEditeur();
$prov
ince = $location->getProvince();
$city = $location->getCity();
}
// Add country field as its static.
$form
->add('country', EntityType::class, array(
'class' => Editeur::class,
'choice_label' => 'country',
'label' => false,
'required' => true,
'placeholder' => 'Éditeur *',
'empty_data' => 'Éditeur *',
'attr' => array(
'class' => 'col-md-12 validate-required',
'placeholder' => 'Éditeur *'
)
))
;
// Now add all child fields.
$this->addProvinceField($form, $country);
$this->addCityField($form, $province);
}
/**
* Handling Form fields before form submits.
* #param FormEvent $event
*/
public function preSubmitData(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
// Here $data will be in array format.
// Add property field if parent entity data is available.
$country = isset($data['country']) ? $data['country'] : $data['country'];
$province = isset($data['province']) ? $data['province'] : null;
$city = isset($data['city']) ? $data['city'] : null;
// Call methods to add child fields.
$this->addProvinceField($form, $country);
$this->addCityField($form, $province);
}
/**
* Method to Add State Field. (first dynamic field.)
* #param FormInterface $form
* #param type $country
*/
private function addProvinceField(FormInterface $form, $country = null)
{
$countryCode = (is_object($country)) ? $country->getId() : $country;
// $countryCode is dynamic here, collected from the event based data flow.
$form
->add('province', EntityType::class, array(
'class' => Province::class,
'choice_label' => 'province',
'label' => false,
'required' => true,
'placeholder' => 'Province *',
'empty_data' => 'Province *',
'attr' => array(
'class' => 'col-md-12 validate-required',
'placeholder' => 'Province *'
),
'query_builder' => function (EntityRepository $er) use($countryCode) {
return $er->createQueryBuilder('p')
->where('p.country = :country')
->setParameter('country', $countryCode);
}
))
;
}
/**
* Method to Add District Field, (second dynamic field)
* #param FormInterface $form
* #param type $state
*/
private function addCityField(FormInterface $form, $province = null)
{
$provinceCode = (is_object($province)) ? $province->getId() : $province;
// $stateCode is dynamic in here collected from event based data flow.
$form->add('city', EntityType::class, array(
'class' => City::class,
'choice_label' => 'city',
'label' => false,
'required' => true,
'placeholder' => 'City *',
'empty_data' => 'City *',
'attr' => array(
'class' => 'col-md-12 validate-required',
'placeholder' => 'City *'
),
'query_builder' => function (EntityRepository $er) use($provinceCode) {
return $er->createQueryBuilder('e')
->where('e.province = :province')
->setParameter('province', $provinceCode);
}
));
}
}
## jQuery
var $country = $('#ec_country');
$country('change', function() {
var $form = $(this).closest('form');
var data = {};
data[$country('name')] = $country();
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(html) {
$('#ec_province').replaceWith(
$(html).find('#ec_province')
);
}
});
});
var $province = $('#ec_province');
$(document).on('change', $province, function() {
var $form = $(this).closest('form');
var data = {};
data[$programme.attr('name')] = $('#ec_province').val();
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(html) {
$('#ec_city').replaceWith(
$(html).find('#ec_city')
);
}
});
});
Any idea?

Symfony 2: Set field as read only after first save

I have a Symfony 2 entity. When I create a new record, I must fill all the values using a form, but after saving it, one of the values, $amount shouldn't be updatable when I update the others members.
How can I accomplish this? It's possible to mark a form member as a read-only, in runtime?
By using the validation_groups and name options when creating your form, you can change the form.
The name attribute sets the form creation, and the validation_groups takes care of the validation.
For example, in the create/new method of your controller;
public function createAction(Request $request)
{
// Instantiate new Foo object
$client = new Foo();
// create the form (setting validation group)
$form = $this->formFactory->create('foo', $foo, array(
'name' => 'create',
'validation_groups' => array('create')
)
);
// form has been submitted...
if ('POST' === $request->getMethod()) {
// submits the form
$form->handleRequest($request);
// do validation
if ($form->isValid()) {
// do whatever
}
}
// either GET or validation failed, so show the form
return $this->template->renderResponse('FooBundle:foo:add.html.twig', array(
'form' => $form->createView(),
'foo' => $foo
));
}
And in the edit/update function of your controller;
public function updateAction($id, Request $request)
{
// Instantiate Client object
$client = new Foo($id);
// create the form (setting validation group)
$form = $this->formFactory->create('foo', $foo, array(
'name' => 'update',
'validation_groups' => array('update')
));
// form has been submitted...
if ('POST' === $request->getMethod()) {
// submits the form
$form->handleRequest($request);
// do validation
if ($form->isValid()) {
// do whatever
}
}
// either GET or validation failed, so show the form
return $this->template->renderResponse('FooBundle:foo/edit:index.html.twig', array(
'form' => $form->createView(),
'foo' => $foo
));
}
And your Form Type will look something like;
class FooType extends BaseAbstractType
{
protected $options = array(
'data_class' => 'FooBundle\Model\Foo',
'name' => 'foo',
);
private $roleManager;
public function __construct($mergeOptions = null)
{
parent::__construct($mergeOptions);
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->$options['name']($builder, $options);
}
private function create(FormBuilderInterface $builder, array $options)
{
// ID
$builder->add('Id', 'text', array(
'required' => true,
'label' => 'ID',
'attr' => array(
'placeholder' => 'Format: 2 alphanumeric (e.g. A1)'
)
));
// Name - only show on create
$builder->add('Name', 'text', array(
'required' => true,
'label' => 'Name',
'attr' => array(
'placeholder' => 'Your name'
)
));
// add the submit form button
$builder->add('save', 'submit', array(
'label' => 'Save'
));
}
private function update(FormBuilderInterface $builder, array $options)
{
// ID
$builder->add('Id', 'text', array(
'required' => true,
'label' => 'ID',
'attr' => array(
'placeholder' => 'Format: 2 alphanumeric (e.g. A1)',
)
));
// Name - just for show
$builder->add('Name', 'text', array(
'required' => true,
'label' => 'Name',
'attr' => array(
'readonly' => 'true' // stops it being editable
)
));
// add the submit form button
$builder->add('save', 'submit', array(
'label' => 'Save'
));
}
}
P.S. All my classes are declared as services, so how you call create forms/views/etc may be different.

Resources