Symfony renders an entity
field type like a choice
dropdown - a select
, basically. However, the CSS framework that I'm using defines a sort of 'select' as a ul
and li
as the options. The Custom Field Type documentation gives no help on this scenario.
I'm converting my code from manual HTML rendering of the form dropdown to symfony form's version using twig and form_widget()
. However, I want a ul
and li
instead of a select
.
The manual way of creating my dropdown is:
<ul class='dropdown-menu'>
{% for locator in locators %}
<li>
<a href="#" data-id="{{locator.getId() }}">
{{ locator.getName() }}
</a>
</li>
{% endfor %}
</ul>
That's how I would render my dropdown manually before using symfony forms. It looks like this:
I like it. I think it looks awesome. Now, if I'm using Symfony forms, I can just use this instead:
{{ form_start(form) }}
{{ form_widget(form.locator) }} {# This is my locator dropdown #}
{{ form_widget(form.target) }} {# Ignore this #}
{{ form_end(form) }}
The problem is that this renders this instead:
I can't add my custom CSS here because this is rendered as a select
instead of an unordered list and li
s.
In case it may help, here's my form type being built:
/**
* {@inheritDoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('target')
->add('locator', 'entity', [
'class' => 'Application\Model\Entity\Locator',
'query_builder' => function(EntityRepository $repo) {
return $repo->createQueryBuilder('e');
},
'empty_value' => 'Locator'
])
->add('save', 'submit', ['label' => 'Save']);
$builder->setAction($this->urlGenerator->generate('page_create_element', [
'suiteId' => $options['suiteId'], 'pageId' => $options['pageId']
]))->setMethod('POST');
}
The Question: Is there any way I can have the form commands above auto-generate my ul / li requirement instead of selects, or do I have to render this manually instead and ignore the symfony forms component for this?
Thanks to some of the posters above, there was some information from Form Theming, but it wasn't exactly enough to go along with so I had to do a little bit of digging on github.
According to the documentation, Symfony uses twig templates to render the relevant bits of a form and it's containing elements. These are just {% block %}
s in twig. So the first step was to find where a select button is rendered within the symfony codebase.
Form Theming
Firstly, you create your own theme block in it's own twig file and you apply this theme to your form with the following code:
{% form_theme my_form_name 'form/file_to_overridewith.html.twig %}
So if I had overridden {% block form_row %}
in the file above, then when I called {{ form_row(form) }}
it would use my block instead of Symfony's default block.
Important: You don't have to override everything. Just override the things you want to change and Symfony will fall back to it's own block if it doesn't find one in your theme.
The Sourcecode
On github I found the source code for Symfony's "choice widget". It's a little complex but if you follow it through and experiment a little bit you'll see where it goes.
Within the choice_widget_collapsed
block, I changed the select to ul
s and options to li
s. Here's the theme file I created, note the minor differences described above:
{# Symfony renders a 'choice' or 'entity' field as a select dropdown - this changes it to ul/li's for our own CSS #}
{%- block choice_widget_collapsed -%}
{%- if required and empty_value is none and not empty_value_in_choices and not multiple -%}
{% set required = false %}
{%- endif -%}
<ul {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
{%- if preferred_choices|length > 0 -%}
{% set options = preferred_choices %}
{{- block('choice_widget_options') -}}
{%- if choices|length > 0 and separator is not none -%}
<li disabled="disabled">{{ separator }}</li>
{%- endif -%}
{%- endif -%}
{%- set options = choices -%}
{{- block('choice_widget_options') -}}
</ul>
{%- endblock choice_widget_collapsed -%}
{%- block choice_widget_options -%}
{% for group_label, choice in options %}
{%- if choice is iterable -%}
<optgroup label="{{ group_label|trans({}, translation_domain) }}">
{% set options = choice %}
{{- block('choice_widget_options') -}}
</optgroup>
{%- else -%}
<li value="{{ choice.value }}"{% if choice is selectedchoice(value) %} selected="selected"{% endif %}><a href="#">{{ choice.label|trans({}, translation_domain) }}</a></li>
{%- endif -%}
{% endfor %}
{%- endblock choice_widget_options -%}
Rendering
Now I can render my form with the following:
{{ form_widget(form.locator, {'attr': {'class': 'dropdown-menu'}}) }}
This uses my theme for the choice dropdown which contains ul
and li
tags instead of select
and option
ones. Pretty simple once you know where to look for the original code! The rendered HTML:
<ul id="elementtype_locator" name="elementtype[locator]" required="required" class="dropdown-menu">
<li value="1"><a href="#">id</a></li>
<li value="2"><a href="#">name</a></li>
<li value="3"><a href="#">xpath</a></li>
</ul>
I also had to remove one of the lines that put 'Locator' at the top of the dropdown as there were four dropdown choices (including the empty_data one) instead of three.