infinite-networks / InfiniteFormBundle

A collection of useful form types and extensions for Symfony.
169 stars 40 forks source link

TWIG Templating details #49

Closed 3kynox closed 8 years ago

3kynox commented 8 years ago

Hello again,

sorry about asking a lot of things, but since my work require that PolyCollection type, it's possible I got some more answers after these ones.

Anyway, 2 things needed here.

A. I can't get out why inside my TWIG template I can't set a class attribute for an individual field.

{{ form_row(form.numero, {label: 'Numéro'}, { 'attr': { 'class': 'col-sm-8' }}) }}

Provides nothing with that class in source code. Same result on a 'form_widget'.

Any reason for that ? (the only one I see is my twig theme extends bootstrap_3_horizontal_layout.html.twig, but I would like to be able to act directly inside a label, row or widget without override bootstrap 3 blocks).

B. According to my previous issue (nb https://github.com/infinite-networks/InfiniteFormBundle/issues/48), I need to display my telephones, emails & adresses inside bootstrap tabs.

I first simply tried to include the code :

{% for subForm in form %}
    {{ form_row(subForm) }}
{% endfor %}

... inside simple divs.

Now, when I click on 'Add Telephone' button and then I put a telephone number in new input it created, and if I use Update button, instead of creating a new telephone in database, it replaces the last existing. If I remove the divs, it works as expected.

Now, If I try to add an email or adress, it loads an error :

This form should not contain extra fields.

I got the same results when building my tabs ...

{% block _brunobundle_bruno_moyensComm_widget %}
    <div class="collection">
        <div class="items">
            <ul class="nav nav-tabs">
                <li class="active"><a data-toggle="tab" href="#telephone">Telephone</a></li>
                <li><a data-toggle="tab" href="#email">Email</a></li>
                <li><a data-toggle="tab" href="#adresse">Adresse</a></li>
            </ul>
            <div class="tab-content">
                <div id="telephone" class="tab-pane fade in active">
                    {% for subForm in form %}
                        {% if subForm.vars.value.numero is defined %}
                            {{ form_row(subForm) }}
                        {% endif %}
                    {% endfor %}
                </div>
                <div id="email" class="tab-pane fade in">
                    {% for subForm in form %}
                        {% if subForm.vars.value.email is defined %}
                            {{ form_row(subForm) }}
                        {% endif %}
                    {% endfor %}
                </div>
                <div id="adresse" class="tab-pane fade in">
                    {% for subForm in form %}
                        {% if subForm.vars.value.adresse is defined %}
                            {{ form_row(subForm) }}
                        {% endif %}
                    {% endfor %}
                </div>
            </div>
        </div>
        <hr />
        <a class="btn btn-default add_item" href="#" data-prototype="{{ form_row(form.vars.prototypes.brunobundle_telephone) | escape }}">Ajouter Téléphone</a>&nbsp;
        <a class="btn btn-default add_item" href="#" data-prototype="{{ form_row(form.vars.prototypes.brunobundle_email) | escape }}">Ajouter Email</a>&nbsp;
        <a class="btn btn-default add_item" href="#" data-prototype="{{ form_row(form.vars.prototypes.brunobundle_adresse) | escape }}">Ajouter Adresse</a>
    </div>
{% endblock %}

Thanks again for help and time. As this bundle get more and more interest from me, I'll be glad to help adding some help and content to this project in a near future.

Best Regards.

jmclean commented 8 years ago

A. To put a class on the form element use {{ form_row(form.numero, {label: 'Numéro', 'attr': { 'class': 'col-sm-8' }}) }} (there is no third argument). In your case you might need it on the wrapping div instead, in which case you'll need to override one small block in bootstrap_3_horizontal_layout:

{{ form_row(form.numero, {label: 'Numéro', 'group_class': 'col-sm-8' }) }}
...
{% block form_group_class group_class | default(parent()) %}

B. This one is a bit trickier since our collection JS expects all the items to be children of <div class="items">, and for there to be no other divs there. It's not compatible with Bootstrap tabs but you could try something like this:

    <div class="collection">
        <ul class="nav nav-tabs">
            <li class="active"><a data-toggle="items" data-item-type="brunobundle_telephone" href="#">Telephone</a></li>
            <li><a data-toggle="items" data-item-type="brunobundle_email" href="#">Email</a></li>
            <li><a data-toggle="items" data-item-type="brunobundle_adresse" href="#">Adresse</a></li>
        </ul>
        <div class="items">
            {% for subForm in form %}
                {{ form_row(subForm) }}
            {% endfor %}
        </div>

...

{% block _brunobundle_bruno_moyensComm_entry_row %}
    <div class="item" data-item-type="{{ form._type.vars.value }}"{% if form._type.vars.value != 'brunobundle_telephone' %} style="display: none"{% endif %}>
...

And some extra JS:

        $(function () {
            $('a[data-toggle="items"]').on('click', function (e) {
                e.preventDefault();

                var itemType = $(this).attr('data-item-type');
                $(this).parent().addClass('active').siblings().removeClass('active');

                $(this).closest('.collection').find('.item').css({
                    display: function () { return $(this).attr('data-item-type') == itemType ? '' : 'none' }
                });
            });
            $('.items').on('infinite_collection_add', function (e) {
                // Switch to correct tab and adjust display state of newly added row
                var coll = $(this).closest('.collection');
                setTimeout(function () {
                    coll.find('a[data-item-type="' + e.$row.filter('.item').attr('data-item-type') + '"]').click();
                }, 0);
            });
        });

Even this isn't perfect since if there's a validation error in the third tab then the user won't see it until they click through the tabs. Personally I'd be tempted to just keep all three types visible at once. :)

3kynox commented 8 years ago

Hello jm,

_A._ : First example works as expected, but for the group class, it has no effect

{% block brunobundle_telephone_widget %}
    <br />
    {{ form_row(form.numero, {label: 'Numéro', 'group-class': 'col-sm-4' }) }}
{% endblock %}

{% block form_group_class group_class | default(parent()) -%}
{# ... #}

Also, if I try to set an attribute here :

{% block _brunobundle_bruno_moyensComm_entry_row %}
    <div class="item">
        <a type="button" class="btn btn-danger btn-xs btn-round remove_item" style="position: absolute;right: 5px; margin-top: 10px; z-index: 1;" href="#"><span class="glyphicon glyphicon-trash" style="top: 2px;"></span></a>
        {{ form_widget(form._type) }}
        {{ form_widget(form, { 'attr': { 'class': 'col-sm-8' }}) }}
        {{ form_errors(form) }}
        <hr />
    </div>
{% endblock %}

It has no effects too.

_B._ :

Really nice way of displaying stuff. I especially like the second jquery part ($('.items').on('infinite_collection_add'...) which manage another need I'll have.

Thanks again and looking carrefully at your code to learn more about finding great solutions.

jmclean commented 8 years ago

In the first one you have 'group-class' but you need 'group_class'. All the keys in that array get turned into variables for the row block.

The second one isn't working because the {% block brunobundle_telephone_widget %} blocks aren't doing anything with the attr variable. You might as well add a <div class="col-sm-8"> in those blocks manually.

3kynox commented 8 years ago

Thanks jm, my first fault was bad!

_C_ : Now I try to add items below tabs inside a table.

If you got a trick to manage dynamic columns generation, depends which tab is selected, I'm open to it. I started this :

{% use "bootstrap_3_horizontal_layout.html.twig" %}

{% block _brunobundle_bruno_moyensComm_widget %}
    <div class="collection">
        <ul class="nav nav-tabs">
            <li class="active"><a data-toggle="items" data-item-type="brunobundle_telephone" href="#">Telephone</a></li>
            <li><a data-toggle="items" data-item-type="brunobundle_email" href="#">Email</a></li>
            <li><a data-toggle="items" data-item-type="brunobundle_adresse" href="#">Adresse</a></li>
        </ul>
        <table class="table">
            <thead>
            {# Managing here dynamic columns but don't works... #}
            {% for key, subForm in form %}
                {% if key == 0 %}
                    <tr class="item" data-item-type="{{ subForm._type.vars.value }}"{% if subForm._type.vars.value != 'brunobundle_telephone' %} style="display: none"{% endif %}>
                        <th> <strong>#</strong> </th>
                        <th> Id </th>
                        <th> Numéro </th>
                        <th> Actions </th>
                    </tr>
                {% endif %}
            {% endfor %}
            </thead>
            <tbody class="items">
                {% for subForm in form %}
                    <tr class="item" data-item-type="{{ subForm._type.vars.value }}"{% if subForm._type.vars.value != 'brunobundle_telephone' %} style="display: none"{% endif %}>
                        <td><strong> {{ subForm.vars.name }} </strong></td>
                        <td><div> {{ subForm.vars.data.id }} </div></td>
                        <td>{{ form_row(subForm) }}</td>
                        <td><a type="button" class="btn btn-danger btn-xs btn-round remove_item" href="#"><span class="glyphicon glyphicon-trash" style="top: 3px;"></span></a></td>
                    </tr>
                {% endfor %}
            </tbody>
        </table>
        <a class="btn btn-default add_item" href="#" data-prototype="{{ form_row(form.vars.prototypes.brunobundle_telephone) | escape }}">Ajouter Téléphone</a>&nbsp;
        <a class="btn btn-default add_item" href="#" data-prototype="{{ form_row(form.vars.prototypes.brunobundle_email) | escape }}">Ajouter Email</a>&nbsp;
        <a class="btn btn-default add_item" href="#" data-prototype="{{ form_row(form.vars.prototypes.brunobundle_adresse) | escape }}">Ajouter Adresse</a>
    </div>
{% endblock %}

{% block _brunobundle_bruno_moyensComm_entry_row %}
    <div class="item" data-item-type="{{ form._type.vars.value }}"{% if form._type.vars.value != 'brunobundle_telephone' %} style="display: none"{% endif %}>
        {{ form_widget(form._type) }}
        {{ form_widget(form) }}
        {{ form_errors(form) }}
    </div>
{% endblock %}

{% block form_group_class group_class | default(parent()) %}

{% block brunobundle_telephone_widget %}
    {{ form_widget(form.numero, { 'group_class': 'col-sm-8' }) }}
{% endblock %}

{% block brunobundle_email_widget %}
    {{ form_widget(form.email, {label: 'Email'}) }}
{% endblock %}

{% block brunobundle_adresse_widget %}
    {{ form_widget(form.adresse, {label: 'Adresse'}) }}
    {{ form_widget(form.codePostal, {label: 'Code Postal'}) }}
    {{ form_widget(form.ville, {label: 'Ville'}) }}
{% endblock %}
jmclean commented 8 years ago

Maybe you could have three different table header rows that can be shown and hidden whenever the items are. (Just output them manually - no need for a loop.) Also you should merge the <tr class="item" ...> code into the entry_row block so that newly added rows can have the table markup too.

To be honest this is getting a bit beyond what polycollection was intended for. Maybe you'd be better off with three entirely separate collections?

3kynox commented 8 years ago

To be honest this is getting a bit beyond what polycollection was intended for. Maybe you'd be better off with three entirely separate collections?

I can't, my UML diagram is using an abstract class MoyenCommunication (communication ways in english). A contact can have many MoyensComm that can be Telephones (maybe to extends to mobiles), Emails or Adresses. I suppose a polycollection type is needed displaying these childs entities.

3kynox commented 8 years ago

I followed what you explained and table now working correctly.

A last thing for this topic because it concerns js, I would like to add a button to edit input field of desired row. I first set all text inputs 'readonly' and now searching best way to set 'readonly' off when clicking on edit button on specific row. Once input edit finished, input field(s) will be back to 'readonly'.

I start build my buttons with an ID that point to my moyenComm input field ID (which have the form of _id="brunobundle_bruno_moyensComm_0numero") :

<td>
    {% if form.vars.value.id is defined %}
        <a type="button" class="btn btn-info btn-xs btn-round edit_item" id="{{ form.vars.name }}" href="#"><span class="glyphicon glyphicon-pencil" style="top: 3px;"></span></a>&nbsp;&nbsp;
    {% endif %}
    <a type="button" class="btn btn-danger btn-xs btn-round remove_item" href="#"><span class="glyphicon glyphicon-trash" style="top: 3px;"></span></a>
</td>

That gives

I started to write the required javascript to select the correct input fields but I stopped because I saw before you used a nice way to get items using _coll.find()_ and filter (to get the input field id number in my case for example) and I'm not really familiar with this.

I need also that new lines (accessed by add Telephone, emails, ... buttons) not to be in 'readonly'.

3kynox commented 8 years ago

Finally, I did the trick :

$('.collection').find('input').prop('readonly', true);
$('.edit_item').on('click', function(e) {
    var $childs = $(this).parents('tr').find('input[type="text"]');
    $childs.prop('readonly',false);
    $childs.first().focus();

    $('body').on('click', function(e) {
        var $target = $(e.target);
        if (!$target.hasClass('moyenComm') && !$target.hasClass('edit_item') && !$target.parent().hasClass('edit_item')) {
            $childs.prop('readonly', true);
        }
    });
})
{% block brunobundle_telephone_widget %}
    {#{{ form_widget(form.numero, { 'group_class': 'col-sm-8' }) }}#}
    <td>{{ form_widget(form.numero, { 'attr': { 'class': 'moyenComm' }}) }}</td>
{% endblock %}

{% block brunobundle_email_widget %}
    <td>{{ form_widget(form.email, { 'attr': { 'class': 'moyenComm' }}) }}</td>
{% endblock %}

{% block brunobundle_adresse_widget %}
    <td>{{ form_widget(form.adresse, { 'attr': { 'class': 'moyenComm' }}) }}</td>
    <td>{{ form_widget(form.codePostal, { 'attr': { 'class': 'moyenComm'}}) }}</td>
    <td>{{ form_widget(form.ville, { 'attr': { 'class': 'moyenComm'}}) }}</td>
{% endblock %}

Closing here, thanks again for your time jm.