Symfony2 collection of choices different option for each elements - symfony

After several search and try I don't know how to solve my problem.
I found this: Symfony2 Form Collection Field with Different Choices but solution was not given only a search trail and I didn't found how to adapt solution in my case.
I have Many to Many relation between Localization and Region, Many to Many relation between LOcalization and Department and Many to Many relation between Localization and City.
To create a localization i have this form:
class LocalizationType extends AbstractType{
private $manager;
public function __construct(ObjectManager $manager)
{
$this->manager = $manager;
}
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('regions', CollectionType::class, array('entry_type' => ChoiceType::class,
'allow_add' => true,
'allow_delete' => true,
'required' => false,
'entry_options' => array(
'choices' => (array_key_exists('regions', $options['localization_value']) ? $options['localization_value']['regions'] : array('' => '')),
'multiple' => false,
'expanded' => false,
'attr' => array('class' => 'region input'),
),
'data' => (array_key_exists('regions', $options['localization_data']) ? $options['localization_data']['regions'] : null),
))
->add('departments', CollectionType::class, array('entry_type' => ChoiceType::class,
'allow_add' => true,
'allow_delete' => true,
'required' => false,
'entry_options' => array(
'choices' => (array_key_exists('departments', $options['localization_value']) ? $options['localization_value']['departments'] : array('' => '')),
'multiple' => false,
'expanded' => false,
'attr' => array('class' => 'department input')
),
'data' => (array_key_exists('departments', $options['localization_data']) ? $options['localization_data']['departments'] : null),
))
->add('cities', CollectionType::class, array('entry_type' => ChoiceType::class,
'allow_add' => true,
'allow_delete' => true,
'required' => false,
'entry_options' => array(
'choices' => (array_key_exists('cities', $options['localization_value']) ? $options['localization_value']['regions'] : array('' => '')),
'multiple' => false,
'expanded' => false,
'attr' => array('class' => 'city input')
),
'data' => (array_key_exists('cities', $options['localization_data']) ? $options['localization_data']['cities'] : null),
))
;
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event){
$data = $event->getData();
if(!empty($data['regions']) && is_array($data['regions'])){
$regions = array();
foreach($data['regions'] as $region){
$regions[] = $region;
}
$data['regions'] = $this->manager->getRepository('LocalizationBundle:Region')->findRegionsForCreateEntity($regions);
}
if(!empty($data['departments']) && is_array($data['departments'])){
$departments = array();
foreach($data['departments'] as $department){
$departments[] = $department;
}
$data['departments'] = $this->manager->getRepository('LocalizationBundle:Departments')->findDepartmentsForCreateEntity($departments);
}
if(!empty($data['cities']) && is_array($data['cities'])){
$cities = array();
foreach($data['cities'] as $city){
$cities[] = $city;
}
$data['cities'] = $this->manager->getRepository('LocalizationBundle:City')->findCitiesForCreateEntity($cities);
}
$form = $event->getForm();
$form->add('regions', CollectionType::class, array('entry_type' => ChoiceType::class,
'allow_add' => true,
'allow_delete' => true,
'required' => false,
'entry_options' => array(
'choices' => $data['regions'],
'multiple' => false,
'expanded' => false,
'attr' => array('class' => 'region input')
)
));
$form->add('departments', CollectionType::class, array('entry_type' => ChoiceType::class,
'allow_add' => true,
'allow_delete' => true,
'required' => false,
'entry_options' => array(
'choices' => $data['departments'],
'multiple' => false,
'expanded' => false,
'attr' => array('class' => 'department input')
)
))
->add('cities', CollectionType::class, array('entry_type' => ChoiceType::class,
'allow_add' => true,
'allow_delete' => true,
'required' => false,
'entry_options' => array(
'choices' => $data['cities'],
'multiple' => false,
'expanded' => false,
'attr' => array('class' => 'city input')
)
))
;
});
}
public function configureOptions(OptionsResolver $resolver){
$resolver->setDefaults(array(
'data_class' => Localization::class,
'localization_data' => array('regions' => '', 'departments' => '', 'cities' => ''),
'localization_value' => array('regions' => '', 'departments' => '', 'cities' => ''),
));
}
I choose a ChoiceType empty because I have several City for example( more of 25k) so I prefer load few of them with AJAX in my view and render them in a select2, it works for add action but for edit action I have a problem I want each field of my collections have a different value.
To illustrate my story, I want this result:
<label>Region n°1</label>
<select id="" name="">
<option value="foo1">bar1</option>
</select>
<label>Region n°2</label>
<select id="" name="">
<option value="foo2">bar2</option>
</select>
And the result I have for the moment is:
<label>0</label>
<select id="" name="">
<option value="foo1" selected="selected">bar1</option>
<option value="foo2">bar2</option>
</select>
<label>1</label>
<select id="" name="">
<option value="foo1">bar1</option>
<option value="foo2" selected="selected">bar2</option>
</select>
To change label if I understand I need to create my own template, ok but to display only option selected and not others I think I need a FormEventListener on PRE_SET_DATA but I don't see how implement this. So if someone has a solution, I'll take it.

I answer my own question, after try with FormEventListener on PRE_SET_DATA, on POST_SET_DATA, or with a choice_loader it's seems to be impossible to do what i want with a Symfony way, but i don't well understand why. I feel back on Jquery way to remove option who are not selected with following code, i dislike but I don't see others solutions:
<script type="text/javascript">
$.each($('.region'), function(i, val){
$(val).find('option:not(:selected)').remove();
})
$.each($('.department'), function(i, val){
$(val).find('option:not(:selected)').remove();
})
$.each($('.city'), function(i, val){
$(val).find('option:not(:selected)').remove();
})
</script>
I don't pass the topic as solved in case I'm wrong

Related

How to getData() on a collectionType field - Symfony 4

I have a collectionType (Equipements) on a Symfony 4 ORM form (intervention).
For example, this equipement field :
<input type="file" id="intervention_equipements_0_photoGraffiti" name="intervention[equipements][0][photoGraffiti]">
I try to get the data of this field with :
$fileToTransfer = $form['intervention_equipements_0_photoGraffiti']->getData();
or
$fileToTransfer = $form['intervention[equipements][0][photoGraffiti]']->getData();
I have the error Child "intervention[equipements][0][photoGraffiti]" does not exist.
Here is my intervention Type :
$builder
->add('idInstallation', EntityType::class, [
'class' => Installation::class,
'choice_label' => 'numeroInstallation',
'required' => false,
])
->add('equipements', CollectionType::class, array(
'entry_type' => EquipementInterventionType::class,
'entry_options' => array('label' => false),
))
And the equipement entity:
$builder
->add('nom', TextType::class, array(
'error_bubbling' => true,
'required' => false,
'disabled' => true
))
->add('numero', TextType::class, array(
'error_bubbling' => true,
'required' => false,
'disabled' => true
))
->add('etatEquipement', ChoiceType::class, array(
'choices' => $choices_controle,
'expanded' => false,
'multiple' => false,
'required' => false,
'error_bubbling' => true,
))
->add('photoEquipement', FileType::class, array(
'error_bubbling' => true,
'required' => false,
'disabled' => false,
'data_class' => null
))
->add('graffiti', CheckboxType::class, array(
'required' => false,
'error_bubbling' => true,
))
->add('photoGraffiti', FileType::class, array(
'error_bubbling' => true,
'required' => false,
'disabled' => false,
'data_class' => null
))
...
How can I fix this, please ?
Directly use $form->getData(). This will return the array with all your form datas that you are looking for.
in your form controller
$equipements = $intervention->getEquipement();
will return the array of collections then
foreach ($equipements as $equipement) {
$fileToTransfer = $equipement->getPhotoGraffiti()
}
Hope you have getter and setter in your entity class

How to set min value for field in form?

I have form with integer field - price.
$builder->add('list',
CollectionType::class,
[
'required' => false,
'allow_add' => true,
'error_bubbling' => true,
])
->add('price',
IntegerType::class,
[
'required' => false,
'error_bubbling' => true,
]);
How to set, for example, for validation i need min value for price 0 or greater?
I tried this one, but it's not work:
'constraints' => [
new GreaterThanOrEqual(50)
],
Thanks for all help.
controller Action
public function getProductAction(Request $request)
{
$variables = $request->get('list');
$price = $request->get('price');
$form = $this->createForm(ProductForm::class, null, ['csrf_protection' => false, 'allow_extra_fields' => true]);
$form->submit(['variables' => $variables, 'prices' => $price]);
if(!$form->isValid()) {
$errors = '';
foreach ($form->getErrors() as $error) {
$errors = $error->getMessage();
}
return new JsonResponse([
'errors' => $errors
],Response::HTTP_BAD_REQUEST);
} else {
$product = $this->getDoctrine()
->getRepository(Product::class)
->findByListAndPrice($list, $price);
if (!$product) {
return new JsonResponse([
'errors' => 'Product not found.'
],Response::HTTP_BAD_REQUEST);
}
return new JsonResponse($product);
}
}
Form not validate, and don't show errors, $form->isValid() === true
According to https://github.com/symfony/symfony/issues/3533 you can use min and max for the IntegerType even though the documentation might not mention this.
$builder->add('list',
CollectionType::class,
[
'required' => false,
'allow_add' => true,
'error_bubbling' => true,
])
->add('price',
IntegerType::class,
[
'required' => false,
'error_bubbling' => true,
/*'min' => 50*/
'attr' => [
'min' => 50
]
]);
EDIT: According to the documentation the 'min' property has to be inside 'attr' tag. This will add the min inside the input in the HTML.
You can use RangeType instead of IntegerType.
use Symfony\Component\Form\Extension\Core\Type\RangeType;
// ...
$builder->add('list',
CollectionType::class,
[
'required' => false,
'allow_add' => true,
'error_bubbling' => true,
])
->add('price',
RangeType::class,
[
'required' => false,
'error_bubbling' => true,
'min' => 0,
'max' => 50
]);

Symfony query builder with post submit data is not changing the values in EventListener

I have a filter form which is filtering for markets, types and airlines. When submitted, I'd like another dropdown (documentlist) to be filtered after the selected values. Therefore I created a post_submit eventlistener which is working (I dumped the values as you can see in the following code). But when then trying to update the documentlist values through a query builder and with the data from my filters, it's not working. Any ideas:
<?php
namespace DocumentBundle\Form\Document;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM;
use Doctrine\ORM\EntityRepository;
use Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use DocumentBundle\Form\Document\DocumentFilterType;
class DocumentDeactivationType extends DocumentFilterType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type', 'choice', array('choices' => array(
'document_types.contract' => 1,
'document_types.general'=>2,
'document_types.goodwill_policy'=>3,
'document_types.pricesheet'=>4,
'document_types.yq_update'=>5,
'document_types.contract_addendum'=>6),
'choices_as_values' => true, 'label' => 'label.types',
'expanded' => false, 'multiple' => true,
'label' => 'label.type', 'required' => false,
'translation_domain' => 'Documents'))
->add('airlines', 'entity', array(
'class' => 'AppBundle:Airline', 'property' => 'id',
'query_builder' => function (EntityRepository $er){
return $er->createQueryBuilder('a')
->addOrderBy('a.id', 'ASC');
},
'choice_value' => 'id',
'choice_label' => 'id', 'label' => 'label.airlines',
'expanded' => false, 'multiple' => true, 'required' => false,
'translation_domain' => 'Documents'))
->add('markets', 'entity', array(
'class' => 'AppBundle:Market', 'property' => 'id',
'query_builder' => function (EntityRepository $er){
return $er->createQueryBuilder('m')
->addOrderBy('m.id', 'ASC');
},
'choice_value' => 'id',
'choice_label' => 'id', 'label' => 'label.markets',
'expanded' => false, 'multiple' => true, 'required' => false,
'translation_domain' => 'Documents'))
->add('documentlist', EntityType::class, array(
'class' => 'DocumentBundle:Document',
'property' => 'name',
'expanded' => false, 'multiple' => true,
'label' => 'label.document_list',
'empty_value' => "Select document",
'required' => false,
'mapped' => false,
'translation_domain' => 'Documents'));
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use ($builder)
{
$form = $event->getForm();
$data = $event->getData();
$markets = $data['markets'];
$type = $data['type'];
$airlines = $data['airlines'];
dump($markets, $type);
$builder
->add('documentlist', EntityType::class, array(
'class' => 'DocumentBundle:Document',
'property' => 'name',
'expanded' => false, 'multiple' => true,
'label' => 'label.document_list',
'empty_value' => "Select document",
'required' => false,
'mapped' => false,
'translation_domain' => 'Documents',
'query_builder' => function (EntityRepository $er) use ($markets, $type, $airlines){
return $er->createQueryBuilder('e')
->where('e.markets = :markets')
->andWhere('e.airlines IN (:airlines)')
->andWhere('e.products IN (:products)')
->setParameter('e.markets', $markets)
->setParameter('e.airlines', $airlines)
->setParameter('e.type', $type);
},
));
});
}
public function getName()
{
return 'document_deactivation';
}
}
You cannot change the form (add or remove fields) in a POST_SUBMIT event. But you can in a PRE_SUBMIT event, so you just need to change POST_SUBMIT with PRE_SUBMIT.
But be careful because your form data won't be normalized in PRE_SUBMIT, so you will have to work with raw data from your form (an array), or to normalize manually your data.

[sonataAdmin]: test the select imput value

I want to retrieve the value of select input and make the test on this value. If this value corresponds to what I am looking for in my case "freelance" I display a block if no I display another
this is my code:
$emr = $this->modelManager->getEntityManager('PivotalBOBundle:Role');
$queryr = $kernel->getContainer()->get('doctrine')->getRepository('PivotalBOBundle:Role')->findAll();
$choises=array();
foreach ($queryr as $res){
$choises[$res->getRole()]=$res->getRole();
}
->tab('Spécifique')
->with('Type d\'utilisateur', array('class' => 'col-md-12'))
->add('type', 'choice', array('label' => 'Type',
'choices' => $choises))
->end()
->with('Freelancer', array('class' => 'col-md-12 Freelancer'))
->add('type', 'checkbox', array('required' => false, 'label' => 'Freelancer'))
->add('categories', 'sonata_type_model', array(
"multiple" => true,
'label' => 'Catégorie',
'required' => true,
'query' => $query))
->add('outilsEtTechnologie', 'sonata_type_model', array(
'required' => false,
"multiple" => true,
'label' => 'Outils et Technologie'))
->add('niveauCompetences', 'sonata_type_collection', array(
'required' => false,
'label' => 'Niveau des compétences',
'by_reference' => true), array(
'edit' => 'inline',
'inline' => 'table'
))
->add('niveauLangue', 'sonata_type_collection', array(
'required' => false,
'label' => 'Niveau des langues',
'by_reference' => false), array(
'edit' => 'inline',
'inline' => 'table'
))
->add('formation', 'sonata_type_collection', array(
'required' => false,
'label' => 'Formations',
'by_reference' => false), array(
'edit' => 'inline',
'inline' => 'table'
))
->add('mobilite', 'entity', array(
'class' => 'Pivotal\BOBundle\Entity\Mobilite',
'required' => true,
'label' => 'Mobilite'
))
->add('frequence', 'entity', array(
'class' => 'Pivotal\BOBundle\Entity\Frequence',
'required' => true,
'label' => 'Frequence'
))
->add('niveauExperience', 'entity', array(
'class' => 'Pivotal\BOBundle\Entity\NiveauExperience',
'required' => true,
'label' => 'Niveau Experience'))
->add('missionetranger', 'entity', array(
'class' => 'Pivotal\BOBundle\Entity\Missionetranger',
'required' => true,
'label' => 'Mission Etranger'))
->add('note', null, array('label' => 'Note'))
->end()
->with('Jobowner ', array('class' => 'col-md-12 Jobowner'))
->add('type', 'checkbox', array('required' => false, 'label' => 'Jobowner'))
->add('societe', 'entity', array(
'class' => 'Pivotal\BOBundle\Entity\Societe',
'property' => 'nom',
'label' => 'Societe',
'required' => false,
))
->end();
this is an image of my render the case 1
and this is my objectif :
the freelancer option
the jobowner option
You can access the current subject with $this->getSubject().
So you could modify your Form by using:
$subject = $this->getSubject();
// with "Type d'utilisateur"
if ($subject && $subject->getType() === 'freelance') {
// with "Freelancer"
}
// other tabs/form-fields
This only works if you gonna re-open your form view. If you need to instantly modify your form if you select "freelance" without reloading the page, you need to write it in Javascript. Depending on your needs, this varies a lot (e.g. hiding the tab/with-section and show it via Javascript).
to do this we should use a java script code like this :
->tab('Spécifique')
->with('Type d\'utilisateur', array('class' => 'col-md-12'))
->add('type', 'choice', array('label' => 'Type',
'choices' => $choises))
->end()
->with('Freelancer', array('class' => 'col-md-12 userFreelancer'))
->add('categories', 'sonata_type_model', array(
"multiple" => true,
'label' => 'Catégorie',
'required' => true,
'query' => $query))
->add('outilsEtTechnologie', 'sonata_type_model', array(
'required' => false,
"multiple" => true,
'label' => 'Outils et Technologie'))
->add('niveauCompetences', 'sonata_type_collection', array(
'required' => false,
'label' => 'Niveau des compétences',
'by_reference' => true), array(
'edit' => 'inline',
'inline' => 'table'
))
->add('niveauLangue', 'sonata_type_collection', array(
'required' => false,
'label' => 'Niveau des langues',
'by_reference' => false), array(
'edit' => 'inline',
'inline' => 'table'
))
->add('formation', 'sonata_type_collection', array(
'required' => false,
'label' => 'Formations',
'by_reference' => false), array(
'edit' => 'inline',
'inline' => 'table'
))
->add('mobilite', 'entity', array(
'class' => 'Pivotal\BOBundle\Entity\Mobilite',
'required' => true,
'label' => 'Mobilite'
))
->add('frequence', 'entity', array(
'class' => 'Pivotal\BOBundle\Entity\Frequence',
'required' => true,
'label' => 'Frequence'
))
->add('niveauExperience', 'entity', array(
'class' => 'Pivotal\BOBundle\Entity\NiveauExperience',
'required' => true,
'label' => 'Niveau Experience'))
->add('missionetranger', 'entity', array(
'class' => 'Pivotal\BOBundle\Entity\Missionetranger',
'required' => true,
'label' => 'Mission Etranger'))
->add('note', null, array('label' => 'Note'))
->end();
$formMapper->with('Societe', array('class' => 'col-md-12 Societe'))
->add('societe', 'entity', array(
'class' => 'Pivotal\BOBundle\Entity\Societe',
'property' => 'nom',
'label' => 'Societe',
'required' => false,
))
->end()
->end();
and we should overide the template
like that :
public function getTemplate($name)
{
if ($name == "edit") {
return 'admin/edit.html.twig';
}
return parent::getTemplate($name);
}
and add a javascript code in edit.html.twig :
<script type="text/javascript">
$(document).ready(function () {
var type = $("#{{ admin.uniqId }}_type");
var freelancer = $(".userFreelancer");
var jobowner = $(".userJobowner");
var redacteur = $(".userRedacteur");
var Supperjobowner = $(".userSupperjobowner");
var Nactif = $(".userNactif");
var Societe = $(".Societe");
hideUserBlock()
type.change(function () {
displayUserBlock();
}); // Bind the function to displayBlock
type.change(); // Manual trigger to display block.
function displayUserBlock() {
hideUserBlock();
var type =$("#{{ admin.uniqId }}_type").val()
switch(type) {
case 'Freelancer':
freelancer.css("display", "block");
freelancer.trigger("create");
break;
case 'Jobowner':
Societe.css("display", "block");
Societe.trigger("create");
break;
case 'Super Jobowner':
Societe.css("display", "block");
Societe.trigger("create");
break;
case 'Jobowner non actif':
Societe.css("display", "block");
Societe.trigger("create");
break;
case 'Rédacteur':
Societe.css("display", "block");
Societe.trigger("create");
break;
}
}
function hideUserBlock() {
freelancer.css("display", "none");
freelancer.trigger("create");
Societe.css("display", "none");
Societe.trigger("create");
}
});
</script>

Generate data attribute to each checkbox in Symfony type

->add('research_subject', EntityType::class, array(
'mapped' => false,
'class' => Subject::class,
'label' => 'Research Subject',
'expanded' => true,
'multiple' => true,
'query_builder' => function (EntityRepository $er) {
$db = $er->createQueryBuilder('w');
$db ->where($db->expr()->andX(
$db->expr()->isNotNull('w.pid')
));
$db->orderBy('w.pid', 'ASC');
return $db;
},
'choice_label' => 'name_system',
))
I need to add to each check box data attribute. Is that is possible?
I need that for extra sort of checkboxes in twig latter. I need to group checkboxes by pid value in separate div section.
'choice_attr' => function($val, $key, $index) {
return ['data-pid' => $val->getPid()];
},
I had use this as solution, like https://stackoverflow.com/users/4224384/yceruto
Suggest.
You would use the "choice_attr" option for this:
[...]
'expanded' => true,
'multiple' => true,
'choice_attr' => function($val, $key, $index) {
return ['data' => $key];
},
[...]

Resources