Set the Input Labels for each Field as Headers in

2019-07-21 17:02发布

问题:

I have a form collection to add multiple "routes" on an editing page. For visual purposes I want this to be a horizontal list of inputs. So I've customized the collection_widget and it works great, it's integrated with jquery to add and delete records, etc. And very importantly I have headers above each column, with help text popups.

Problem

However, if the record does not have any routes yet, nothing shows up except the add button (i.e. the necessary headers are gone). And if I "add" a new route, it adds the prototype row, but it does NOT add the header titles. You'll see how I'm creating the <thead> below, and I simply do not understand enough about the variables available to know how to iterate through the available fields to get the names, even if there are no records yet.

Question

So how can I cycle through all the rows, and get the label for each input, so that I can create headers? Even if there is no data recorded yet.

Code

fields.html.twig

{% block collection_widget %}
    {% spaceless %}

        {% if prototype is defined %}
            {% set attr = attr|merge({'data-prototype': block('collection_item_widget') }) %}
        {% endif %}
        {# We add the collection class to encapsulate the whole container #}
        {% set attr = attr|merge({'class': (attr.class|default('') ~ ' collection')|trim}) %}

        <div {{ block('widget_container_attributes') }}>
            {{ form_errors(form) }}
            <table class="table table-striped table-hover table-condensed">
                <thead>
                <tr>
                    {% set fieldNum = 1 %}
                    {% for rows in form  %}
                        {# Only Loop through the headers once, regardless of how many rows there are. #}
                        {% if fieldNum == 1 %}
                            {% for row in rows %}
                                {# row.vars.label #}
                                <td class="heading{{ fieldNum }} heading_{{ row.vars.name }}">
                                    <h4 class="collectionHeading">
                                        {{ form_label(row) }}
                                    </h4>
                                </td>
                            {% endfor %}
                        {% endif %}
                        {% set fieldNum = fieldNum + 1 %}
                    {% endfor %}
                    {# for delete row #}
                    <td class="heading{{ fieldNum }} heading_action">&nbsp;</td>
                </tr>
                </thead>
                <tbody>
                {% for prototype in form %}
                    {{ block('collection_item_widget') }}
                {% endfor %}

                </tbody>
                <tfoot>
                    <tr>
                        <td colspan="{{ fieldNum }}">
                            <div class="actionContainer">
                                <span class="action addCollection btn btn-primary btn-xs" title="Add"><span class="glyphicon glyphicon-plus"></span> Add</span>
                            </div>
                        </td>
                    </tr>
                </tfoot>
            </table>
            {{ form_rest(form) }}

        </div>
        <div class="clear"></div>
    {% endspaceless %}
{% endblock collection_widget %}

{% block collection_item_widget %}
    {% spaceless %}
        <tr class="collectionEntity">
            {% set fieldNum = 1 %}
            {% for row in prototype %}
                <td class="field{{ fieldNum }} field_{{ row.vars.name }}">
                    {{ form_widget(row) }}
                </td>
                {% set fieldNum = fieldNum + 1 %}
            {% endfor %}
            <td class="field{{ fieldNum }} field_action">
                <span class="action removeCollection btn btn-default glyphicon glyphicon-trash" title="Remove"></span>
            </td>
        </tr>
    {% endspaceless %}
{% endblock collection_item_widget %}

Outputted HTML

<div id="contentSeoEditType_routing" data-prototype="    <tr class=&quot;collectionEntity&quot;><td class=&quot;field1 field_furl&quot;><div class=&quot;input-group&quot;><span class=&quot;input-group-addon&quot;>/</span><input type=&quot;text&quot;     
   id=&quot;contentSeoEditType_routing___name___furl&quot; name=&quot;contentSeoEditType[routing][__name__][furl]&quot; required=&quot;required&quot; style=&quot;min-width: 200px&quot; class=&quot;form-control inputType-text&quot;
   /></div></td><td class=&quot;field2 field_flagPrimary&quot;><input type=&quot;radio&quot;     
   id=&quot;contentSeoEditType_routing___name___flagPrimary&quot; name=&quot;contentSeoEditType[routing][__name__][flagPrimary]&quot; class=&quot;radioExclusive form-control inputType-checkbox&quot; data-radioexclusive=&quot;routingFlagPrimary&quot;
   value=&quot;1&quot; /></td><td class=&quot;field3 field_action&quot;><span class=&quot;action removeCollection btn btn-default glyphicon glyphicon-trash&quot; title=&quot;Remove&quot;></span></td></tr>" class="collection">
   <table class="table table-striped table-hover table-condensed">
      <thead>
         <tr>
            <td class="heading1 heading_furl">
               <h4 class="collectionHeading">
                  <label for="contentSeoEditType_routing_0_furl" class="required">Friendy URL</label><span class="required" title="This field is Required">*</span><span rel="popover" data-target=".popoverContent.help-contentSeoEditType_routing_0_furl" data-placement="right" class="popoverButton help-contentSeoEditType_routing_0_furl glyphicon glyphicon-question-sign" data-original-title="" title=""></span>
                  <div class="popoverContent help-contentSeoEditType_routing_0_furl"><em>Versioning:</em> Friendly URLs are <strong>not</strong> managed by the versioning system. Changes to the route will effect both the live and staging website. However, you can create extra aliases for testing and then change which is the primary route when you are ready to make them live.</div>
               </h4>
            </td>
            <td class="heading1 heading_flagPrimary">
               <h4 class="collectionHeading">
                  <span rel="popover" data-target=".popoverContent.help-contentSeoEditType_routing_0_flagPrimary" data-placement="right" class="popoverButton help-contentSeoEditType_routing_0_flagPrimary glyphicon glyphicon-question-sign" data-original-title="" title=""></span>
                  <div class="popoverContent help-contentSeoEditType_routing_0_flagPrimary"><em>Primary Route:</em> A page will have one primary Friendly URL. Additional aliases will redirect back to the primary URL.</div>
               </h4>
            </td>
            <td class="heading3 heading_action">&nbsp;</td>
         </tr>
      </thead>
      <tbody>
         <tr class="collectionEntity">
            <td class="field1 field_furl">
               <div class="input-group"><span class="input-group-addon">/</span><input type="text" id="contentSeoEditType_routing_0_furl" name="contentSeoEditType[routing][0][furl]" required="required" style="min-width: 200px" class="form-control inputType-text" value="Turkish-We-Love-You"></div>
            </td>
            <td class="field2 field_flagPrimary"><input type="radio" id="contentSeoEditType_routing_0_flagPrimary" name="contentSeoEditType[routing][0][flagPrimary]" class="radioExclusive form-control inputType-checkbox" data-radioexclusive="routingFlagPrimary" value="1" checked="checked"></td>
            <td class="field3 field_action"><span class="action removeCollection btn btn-default glyphicon glyphicon-trash" title="Remove"></span></td>
         </tr>
         <tr class="collectionEntity">
            <td class="field1 field_furl">
               <div class="input-group"><span class="input-group-addon">/</span><input type="text" id="contentSeoEditType_routing_1_furl" name="contentSeoEditType[routing][1][furl]" required="required" style="min-width: 200px" class="form-control inputType-text" value="A-Dog-Named-Turkish"></div>
            </td>
            <td class="field2 field_flagPrimary"><input type="radio" id="contentSeoEditType_routing_1_flagPrimary" name="contentSeoEditType[routing][1][flagPrimary]" class="radioExclusive form-control inputType-checkbox" data-radioexclusive="routingFlagPrimary" value="1"></td>
            <td class="field3 field_action"><span class="action removeCollection btn btn-default glyphicon glyphicon-trash" title="Remove"></span></td>
         </tr>
      </tbody>
      <tfoot>
         <tr>
            <td colspan="3">
               <div class="actionContainer"><span class="action addCollection btn btn-primary btn-xs" title="Add"><span class="glyphicon glyphicon-plus"></span> Add</span></div>
            </td>
         </tr>
      </tfoot>
   </table>
</div>

Ongoing Dilemma

This gets back to an overall frustration about the ongoing inability to do anything beyond the basics in the form template customizations. I can see what fields are available in any given context through documentation and iterating through the _context:

<ol>
    {% for key, value in _context %}
        <li>{{ key }} :
            {% if value is not iterable%}
                {{ value }}
            {% else %}
                {# WARNING! This causes the page to go blank white with no error. #}
                {{ dump(value) }}
            {% endif %}
        </li>
    {% endfor %}
</ol>

But this is a manual hack and does very little good because if the element is an object (which is when you really want to know), you can't dump it's value (it breaks the page, blank white with no error, which is another rabbit hole I got lost in for a day). And if I create nested for loops, they still don't print out much useful value information.

And just because the variable exists, it is sometimes empty in some block contexts and not others. And I have no idea how these blocks relate to one another or how the variables pass around between themselves, e.g.

{% block form_label %}
    {{ parent() }}
    {% if required is not empty %}
        <span class="required" title="This field is Required">*</span>
    {% endif %}
    {{ block('form_help') }}
{% endblock %}

{# Custom Block for Help #}
{% block form_help %}
    {% if help is not null %}
        <span rel="popover" data-target=".popoverContent.help-{{ id }}" data-placement="right" class="popoverButton help-{{ id }} glyphicon glyphicon-question-sign"></span>
        <div class="popoverContent help-{{ id }}">{{ help | raw }}</div>
    {% endif %}
{% endblock %}

In some contexts I can call {{ block('form_help) }} and it works, but in others (e.g. from the {% block collection_widget %} it doesn't. I can print out _context in the {% block form_help %} and it will show a help variable, but it's empty. Why?

Variables Reference

There is documentation that explains how to access any public variable for a field called myFieldName by referencing the vars field, i.e. form.myFieldName.vars.publicVariable

<label for="{{ form.name.vars.id }}"
    class="{{ form.name.vars.required ? 'required' : '' }}">
    {{ form.name.vars.label }}
</label>

Additional variables are available for different field types, e.g. the collection Type has a prototype variable which contains a template of what the collection will look like.

{# this probably only works in your own template #}
{{ form_row(form.emails.vars.prototype) }}

{# this appears to work in the fields.html.twig master block template #}
{{ form_row(prototype) }}

But that doesn't help me figure out how to access the field labels to add a header. I also don't understand how these blocks pass variables back and forth to each other, e.g.

Reference

  • Documentation on Form Variables
  • Full List of Form Variables

Environment Details:

  • Using Symfony 2.5