Symfony 3 - EntityType / Dojo - Select Integration

If you use Dojo to provide a polished interface with a Symfony 3 application programmatically, you may use a data- attribute on a select tag to pass the options from Symfony 3 to Dojo.

The entity in this case is phone number type. This is a table with two columns, an id and the type which is a string.

dev=# \d+ phone_number_type
                          Table "public.phone_number_type"
 Column |         Type          | Modifiers | Storage  | Stats target | Description 
--------+-----------------------+-----------+----------+--------------+-------------
 id     | integer               | not null  | plain    |              | 
 type   | character varying(16) | not null  | extended |              | 
Indexes:
    "phone_number_type_pkey" PRIMARY KEY, btree (id)

dev=# select * from phone_number_type;
 id |   type    
----+-----------
  1 | office
  2 | home
  3 | mobile
  4 | alternate
  5 | emergency
  6 | on-call
  7 | other
  8 | security
(8 rows)

The form uses the entity to allow the user to indicate the type of phone number.

    public function buildForm( FormBuilderInterface $builder, array $options )
    {

        $builder
                ->add( 'type', EntityType::class, [
                    'class' => 'AppBundle:PhoneNumberType',
                    'choice_label' => 'type',
                    'multiple' => false,
                    'expanded' => false,
                    'required' => true,
                    'choice_translation_domain' => false
                ] )
                ->add( 'phonenumber', TextType::class, [
                    
                ] )
                ->add( 'comment', TextType::class )
        ;
    }

Next, the twig block is customized to render the options as a data- attribute on the select tag, rather than the options:

{%- block choice_widget_collapsed -%}
    {%- if required and placeholder is none and not placeholder_in_choices and not multiple and (attr.size is not defined or attr.size <= 1) -%}
        {% set required = false %}
    {%- endif -%}
    <select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple" {% else %} {% endif %}
        {%- if placeholder is not none -%}
            {% set blank_value = (placeholder != '') ? (translation_domain is same as(false) ? placeholder : placeholder|trans({}, translation_domain)) %}
            {% set blank = { 'value': '', 'label': '{{ blank_value|raw }}'} %}
        {%- endif -%}
        {%- if preferred_choices|length > 0 -%}
            {% set choices = preferred_choices|merge(choices) %}
            {%- if choices|length > 0 and separator is not none -%}
                {% set choices = choices|merge([{'label':'{{separator}}','disabled':true}]) %}
            {%- endif -%}
        {%- endif -%}
        {%- set options = choices -%}
        data-options="{{options|json_encode()}}" data-selected="{% if required and value is empty %}{else}-1{% endif %}">
    
{%- endblock choice_widget_collapsed -%}

On the client side, the options are read into a MemoryStore

        // select is the select node
        data = JSON.parse(domAttr.get(select, "data-options"));
        // Convert the data to an array of objects
        storeData = [];
        for( d in data ) {
            storeData.push(data[d]);
        }
        memoryStore = new Memory({
            idProperty: "value",
            data: storeData});
        store = new ObjectStore({objectStore: memoryStore});

        typeSelect = new Select({
            store: store,
            placeholder: core.type,
            required: true
        }, select);
        typeSelect.startup();

This approach is good for cases where there aren't too many options and they are static. If you had a long list, or wanted an autocomplete, an Ajax solution would be better.

A similar approach may be used with other JavaScript libraries and frameworks.