Custom Symfony ChoiceList way of rendering choices (grouped in divs) - symfony

I have a choice form field that has a lot of options that I need to group somehow so that they will be rendered divided in groups, maybe placed inside different divs.
I'm currently trying to implement the ChoiceListInterface to achieve that, but I don't know how to implement the methods so I can render the choices divided by groups, but the docs does not clarify how to do that..
The choices always get rendered together.

You have this array
$grouped_choices = array(
'Swedish Cars' => array(
'volvo' => 'Volvo',
'saab' => 'Saab',
),
'German Cars' => array(
'mercedes' => 'Mercedes',
'audi' => 'Audi'
)
);
First way: quick and simple
$builder->add($name, 'choice', array(
'choices' => $grouped_choices),
)
But I don't thinks it works with 'expanded' => true
So there is another way, more customizable (maybe more dirty)
In your FormType
foreach($grouped_choices as $name => $choices) {
$builder->add($name, 'choice', array(
'choices' => $choices),
'expanded' => true, //custom the widgets like you wants
);
}
Let the controller send the array to the view, then in the view
{% for name, choices in grouped_choices %}
<div class="whatever">
{{ form_row(name) }}
</div>
{% endfor %}

Related

Drupal 9 How to add pager with entityQuery

We've a Drupal 9 installation and are trying to add a pager using the pagerer module for articles entityQuery, the aim is to list tagged articles in a tag page, but it’s not working. It returns null.
When we dump the data without the pager, using default drupal query, it returns the data of all tagged articles properly.
The code is added in the theme file themeName_preprocess_page hook and being called in page--page.html.twig template file.
Here’s our code:
$query = \Drupal::entityQuery('node')
->condition('status', 1)
->condition('type', 'article');
->pager(2);
$nids = $query->sort('created', 'DESC')
->execute();
if($nids):
$nodesNews = \Drupal\node\Entity\Node::loadMultiple($nids);
$pathNews = base_path();
$pager = [
'articles_data' => $nodesNews,
'results' => [
'#theme' => 'news_pagination',
'#items' => $nodesNews,
'#path' => $pathNews,
'#tag' => $tag
],
'pager' => [
'#type' => 'pager',
'#quantity' => 5
],
];
return $pager;
endif;
And here is the code that calls the query:
<div>
{{ articles_data }}
{{ pager }}
</div>
The above code returns only one page in the navigation and we’ve 10 articles, so given that we set 2 articles per page, the output should be 5 pages instead of 1.
Also articles_data attribute returns null. Could you please help me to find what’s wrong with the code? Happy to share more information as needed, thank you.
Just reading the docs for this module here,
it would seem that you are missing at least the #theme and #style keys in your render array for the pager.
A more likely to succeed version of the above render array would be
$pager = [
'articles_data' => $nodesNews,
'results' => [
'#theme' => 'news_pagination',
'#items' => $nodesNews,
'#path' => $pathNews,
'#tag' => $tag
],
'pager' => [
'#type' => 'pager',
'#theme' => 'pagerer_base',
'#style' => 'standard',
'#config' => [
'display_restriction' => 0,
],
'#quantity' => 5
],
];

symfony2 FromBuilder multiple checkboxes from entity instead of using "choice"

How can I get multiple Cehckboxes instead a choice field in Symfony? Actually, I'm using:
->add('usergroups', 'entity', array('class' => 'PrUserBundle:Group','property' => 'name','required' => true))
This will output a select-Field..
In this case, it would be better to output checkboxes as it is easier to handle for the user.
http://symfony.com/doc/current/reference/forms/types/checkbox.html at, there is nothing helpful about multiple select....
Do I have to build an array and place it by myself?
Use the expanded option for choice and entity fields.
->add('usergroups', 'entity', array('class' => 'PrUserBundle:Group','property' => 'name','required' => true, 'expanded' => true,))

Symfony2 : Radio buttons in a collection

In my application, I created a form using the collection field type :
$builder->add('tags', 'collection', array(
'type' => new TagType(),
'label' => false,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
));
With some JQuery, this code works correctly, but now I would like to select one of this dynamic tag to make it "the main tag".
In my Tag entity, I added a boolean attribute which define if the tag is the main or not :
/**
* #ORM\Column(name="main", type="boolean")
*/
private $main;
But in my view, each row now contains a checkbox. So I can select more than one main tag. How to transform this checkbox in radio button please ?
You're not tackling the problem from the right angle. If there should be a main tag, then this property should not be added in the Tag entity itself, but in the entity that contains it!
I'm speaking of the data_class entity related to the form having the tags attribute. This is the entity that should have a mainTag property.
If defined properly, this new mainTag attribute will not be a boolean, for it will contain a Tag instance, and thus will not be associated to a checkbox entry.
So, the way I see it, you should have a mainTag property containing your instance and a tags property that conatins all other tags.
The problem with that is that your collection field will no longer contain the main tag. You should thus also create a special getter getAllTags that will merge your main tag with all others, and change your collection definition to:
$builder->add('allTags', 'collection', array(
'type' => new TagType(),
'label' => false,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
));
Now, how do we add the radio boxes, you may ask? For this, you will have to generate a new field:
$builder->add('mainTag', 'radio', array(
'type' => 'choice',
'multiple' => false,
'expanded' => true,
'property_path' => 'mainTag.id', // Necessary, for 'choice' does not support data_classes
));
These are the basics however, it only grows more complex from here. The real problem here is how your form is displayed. In a same field, you mix the usual display of a collection and the display of a choice field of the parent form of that collection. This will force you to use form theming.
To allow some room to reusability, you need to create a custom field. The associated data_class:
class TagSelection
{
private mainTag;
private $tags;
public function getAllTags()
{
return array_merge(array($this->getMainTag()), $this->getTags());
}
public function setAllTags($tags)
{
// If the main tag is not null, search and remove it before calling setTags($tags)
}
// Getters, setters
}
The form type:
class TagSelectionType extends AbstractType
{
protected buildForm( ... )
{
$builder->add('allTags', 'collection', array(
'type' => new TagType(),
'label' => false,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
));
// Since we cannot know which tags are available before binding or setting data, a listener must be used
$formFactory = $builder->getFormFactory();
$listener = function(FormEvent $event) use ($formFactory) {
$data = $event->getForm()->getData();
// Get all tags id currently in the data
$choices = ...;
// Careful, in PRE_BIND this is an array of scalars while in PRE_SET_DATA it is an array of Tag instances
$field = $this->factory->createNamed('mainTag', 'radio', null, array(
'type' => 'choice',
'multiple' => false,
'expanded' => true,
'choices' => $choices,
'property_path' => 'mainTag.id',
));
$event->getForm()->add($field);
}
$builder->addEventListener(FormEvent::PRE_SET_DATA, $listener);
$builder->addEventListener(FormEvent::PRE_BIND, $listener);
}
public function getName()
{
return 'tag_selection';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'TagSelection', // Adapt depending on class name
// 'prototype' => true,
));
}
}
Finally, in the form theme template:
{% block tag_selection_widget %}
{% spaceless %}
{# {% set attr = attr|default({})|merge({'data-prototype': form_widget(prototype)}) %} #}
<ul {{ block('widget_attributes') }}>
{% for child in form.allTags %}
<li>{{ form_widget(form.mainTag[child.name]) }} {{ form_widget(child) }}</li>
{% endfor %}
</ul>
{% endspaceless %}
{% endblock tag_selection_widget %}
Lastly, we need to include that in your parent entity, the one that originally contained tags:
class entity
{
// Doctrine definition and whatnot
private $tags;
// Doctrine definition and whatnot
private $mainTag;
...
public setAllTags($tagSelection)
{
$this->setMainTag($tagSelection->getMainTag());
$this->setTags($tagSelection->getTags());
}
public getAllTags()
{
$ret = new TagSelection();
$ret->setMainTag($this->getMainTag());
$ret->setTags($this->getTags());
return $ret;
}
...
}
And in your original form:
$builder->add('allTags', new TagSelection(), array(
'label' => false,
));
I recognize the solution I propose is verbose, however it seems to me to be the most efficient. What you are trying to do cannot be done easily in Symfony.
You can also note that there is an odd "prototype" option in the comment. I just wanted to underline a very useful property of "collection" in your case: the prototype option contains a blank item of your collection, with placeholders to replace. This allow to quickly add new items in a collection field using javascript, more info here.
This is not the right solution, but since you are using jQuery to add/remove...
TagType
->add('main', 'radio', [
'attr' => [
'class' => 'toggle'
],
'required' => false
])
jQuery
div.on('change', 'input.toggle', function() {
div
.find('input.toggle')
.not(this)
.removeAttr('checked');
});
http://jsfiddle.net/coma/CnvMk/
And use a callback constraint to ensure that there is only one main tag.
First thing you should wary about - its that in your scheme if tag become main for one entity it will be main for all entities because the tag store attribute and few entities can be tagged with one tag.
So simplest decision here is to create new property main_tag near tags in your entity, create hidden field main_tag(with id to Tag Data transformer) in your form and populate and change this field with jQuery(for example set it on tag click or clear on main tag delete)
Maybe there is something to do with the multiple form option, but it might require a little tweaking on your collection form and tag entity.

How to disable HTML escaping of labels in KnpMenuBundle

I want to render an HTML label like:
$menu->addChild('Dashboard', array(
'route' => 'dashboard',
'label' => '<i class="fa-icon-bar-chart"></i><span class="hidden-tablet"> Dashboard</span></a>',
'extra' => array('safe_label' => true)
)
);
And I've pass the proper option while rendering:
{{ knp_menu_render('WshCmsHtmlBundle:Builder:mainMenu', {'allow_safe_labels': true} ) }}
But my label is still being escaped. What am I doing wrong?
Ok, the answer is!
You set up extra items on menu item not by 'extra' key but by 'extras' key.
So when you setup the item like this:
$menu->addChild('Dashboard', array(
'route' => 'dashboard',
'label' => '<i class="fa-icon-bar-chart"></i><span class="hidden-tablet"> Dashboard</span></a>',
'extras' => array('safe_label' => true)
)
);
it works fine!
There's two steps to achieve this.
1. MenuBuilder
You have to set safe_label to true in extras. Note that you can now write HTML in your label.
$menu->addChild('Home<i><b></b></i>', array(
'route' => 'homepage',
'extras' => array(
'safe_label' => true
),
));
2. Twig
You have to filter the output of knp_menu_render() so that it prints raw HTML (see documentation).
{{ knp_menu_render('main', {'allow_safe_labels': true}) | raw }}
Warning
Please be aware that this may be dangerous. From the documentation:
Use it with caution as it can create some XSS holes in your application if the label is coming from the user.
I used FyodorX's method to add a strong tag. It works like a charm but I must say that the raw filter is not necessary

Show labels for datetime form fields in Symfony2

I'm using Symfony2.1 (beta). I can't find a way to display labels for datetime fields in forms.
In the reference there is no trace of a label property. How can it be?!
You can show the label of a field "myDate" for example like this:
<div>
{{ form_label(form.myDate, 'Choose a date: (this is the label sentence)') }}
</div>
(Documentation: http://symfony.com/doc/current/book/forms.html)
You can also personalize your form render: http://symfony.com/doc/current/cookbook/form/form_customization.html
Try this. I use it every time and works like a charm. Maybe docs bug?
$builder->add('creation_date', 'date', array(
'label' => 'Creation date',
));
I had same issue with Symfony 2.7
I fixed this way:
->add('issuedAt', 'date', array( 'label_render' => true,
'show_child_legend' => false,
'label' => 'Issued at date:',
...

Resources