54 votes

Comment personnaliser l'attribut data-prototype dans les formulaires Symfony 2

Depuis plusieurs jours, je bloque sur un problème avec Symfony 2 et les formulaires.

J'ai un formulaire d'entités de sites web. "Websites" est une collection d'entités de sites web et chaque site web contient deux attributs : "type" et "url".

Si je veux ajouter plusieurs sites Web dans ma base de données, je peux cliquer sur un lien "Ajouter un autre site Web", qui ajoute une autre ligne de site Web à mon formulaire. Ainsi, lorsque vous cliquez sur le bouton d'envoi, vous pouvez ajouter un ou plusieurs sites Web en même temps.

Ce processus pour ajouter une ligne utilise l'attribut data-prototype, qui peut générer le sous-formulaire du site web.

Le problème est que je personnalise mon formulaire pour avoir un super rendu graphique... comme ça :

<div class="informations_widget">{{ form_widget(website.type.code) }}</div>
<div class="informations_error">{{ form_errors(website.type) }}</div>
<div class="informations_widget">{{ form_widget(website.url) }}</div>
<div class="informations_error">{{ form_errors(website.url) }}</div>

Mais le prototype de données ne se soucie pas de cette personnalisation, avec des balises et des propriétés HTML et CSS. Je garde le rendu Symfony :

<div>
<label class=" required">$$name$$</label>
<div id="jobcast_profilebundle_websitestype_websites_$$name$$">
<div>
<label class=" required">Type</label>
<div id="jobcast_profilebundle_websitestype_websites_$$name$$_type">
<div>
<label for="jobcast_profilebundle_websitestype_websites_$$name$$_type_code" class=" required">label</label>
<select id="jobcast_profilebundle_websitestype_websites_$$name$$_type_code" name="jobcast_profilebundle_websitestype[websites][$$name$$][type][code]" required="required">
<option value="WEB-OTHER">Autre</option>
<option value="WEB-RSS">Flux RSS</option>
...
</select>
</div>
</div>
</div>
<div>
<label for="jobcast_profilebundle_websitestype_websites_$$name$$_url" class=" required">Adresse</label>
<input  type="url" id="jobcast_profilebundle_websitestype_websites_$$name$$_url" name="jobcast_profilebundle_websitestype[websites][$$name$$][url]" required="required" value="" />
</div>
</div>
</div>

Quelqu'un a-t-il une idée pour réaliser ce hack ?

76voto

Jivan Points 1136

C'est un peu vieux, mais voici une solution très simple.

L'idée est simplement de rendre les éléments de la collection par le biais d'un modèle Twig, de sorte que vous avez la possibilité de personnaliser le prototype qui sera placé dans votre data-prototype="..." tag. Comme s'il s'agissait d'un formulaire normal et habituel.

Dans votreMainForm.html.twig :

<div id="collectionContainer"
     data-prototype="
         {% filter escape %}
             {{ include('MyBundle:MyViewsDirectory:prototype.html.twig', { 'form': form.myForm.vars.prototype }) }}
         {% endfilter %}}">
</div>

Et dans MonBundle:MyViewsDirectory:prototype.html.twig :

<div>
    <!-- customize as you wish -->
    {{ form_label(form.field1) }}
    {{ form_widget(form.field1) }}
    {{ form_label(form.field2) }}
    {{ form_widget(form.field2) }}
</div>

Crédit : adapté de https://gist.github.com/tobalgists/4032213

1 votes

La balise twig Endfilter doit se terminer par un crochet, et non deux. @Jivan Pouvez-vous l'éditer car nous ne pouvons pas éditer le message pour un seul caractère ?

2 votes

Très utile, merci ! De plus, c'est peut-être une question de bon sens, mais n'incluez pas les fonctions form_start et form_end dans le modèle twig si vous suivez cette voie. J'ai découvert à la dure que leur inclusion entraîne des problèmes de persistance dans la base de données.

0 votes

Belle solution ! Merci !

49voto

Akkumulator Points 410

Je sais que cette question est assez ancienne, mais j'ai eu le même problème et voici comment je l'ai résolu. J'utilise une brindille macro pour y parvenir. Les macros sont comme des fonctions, vous pouvez les rendre avec différents arguments.

{% macro information_prototype(website) %}
    <div class="informations_widget">{{ form_widget(website.type.code) }}</div>
    <div class="informations_error">{{ form_errors(website.type) }}</div>
    <div class="informations_widget">{{ form_widget(website.url) }}</div>
    <div class="informations_error">{{ form_errors(website.url) }}</div>
{% endmacro %}

Vous pouvez maintenant rendre cette macro où vous voulez. Notez que information_prototype() est juste le nom de la macro, vous pouvez la nommer comme vous le souhaitez. Si vous voulez utiliser la macro pour rendre les éléments donnés et le prototype de la même manière, faites quelque chose comme ceci :

<div class="collection" data-prototype="{{ _self.information_prototype(form.websites.vars.prototype)|e }}">
    {% for website in form.websites %}
        {{ _self.information_prototype(website) }}
    {% endfor %}
    <button class="add-collection">Add Information</button>
</div>

form.websites.vars.prototype contient les données du prototype du formulaire avec le nom prototype_name que vous avez spécifié. Utilisez _self.+macroname si vous voulez utiliser la macro dans le même modèle.

Vous pouvez obtenir plus d'informations sur les macros dans la section Documentation de Twig

0 votes

Je n'arrive pas à le faire fonctionner. Savez-vous si c'est toujours valable pour Symfony2.5 ?

2 votes

Cette réponse devrait être marquée comme la meilleure et la plus simple ! Merci !

0 votes

Je n'obtiens que "Impossible d'invoquer une méthode ("widget_prototype") sur une variable de type chaîne ("SendisPresentationFrontendBundle:DeliverySlip:deliverySlipForm.html.twig")". Avez-vous des suggestions sur ce que je peux faire à ce sujet ?

25voto

François Points 1177

Vous l'avez probablement découvert depuis, mais voici la solution pour les autres.

Créez un nouveau modèle et copiez/collez ce code dans celui-ci : https://gist.github.com/1294186

Ensuite, dans le modèle contenant le formulaire que vous souhaitez personnaliser, appliquez-le à votre formulaire en procédant comme suit :

{% form_theme form 'YourBundle:Deal:Theme/_field-prototype.html.twig' %}

2 votes

Le modèle est parfait pour itérer sur des collections et rendre la collection entière comme vous le souhaitez. Merci

1 votes

Joli ! J'ai même trouvé comment faire ça sans dupliquer le code : gist.github.com/eikes/5788231

2voto

targnation Points 516

J'ai eu un problème similaire. Il se peut que vous deviez modifier cette méthode pour qu'elle fonctionne dans votre cas, mais quelqu'un pourrait la trouver utile.

Créez un nouveau fichier modèle pour contenir le "thème" de votre formulaire personnalisé.

./src/Company/TestBundle/Resources/views/Forms/fields.html.twig

Normalement, vous pouvez utiliser le formulaire_row pour afficher l'étiquette, l'erreur et le widget d'un champ. Mais dans mon cas, je ne voulais afficher que le widget. Comme vous le dites, l'utilisation de la fonction data-prototype permettrait également d'afficher le libellé. Dans notre nouveau fields.html.twig, saisissez votre code personnalisé pour l'aspect que vous souhaitez donner au champ :

{% block form_row %}
{% spaceless %}
        {{ form_widget(form) }}
{% endspaceless %}
{% endblock form_row %}

J'ai enlevé le div conteneur, et le label et l'erreur, et j'ai juste laissé le widget.

Maintenant, dans le fichier twig qui affiche le formulaire, ajoutez simplement ceci après le {% extends ... %}

{% form_theme form 'CompanyTestBundle:Form:fields.html.twig' %}

Et maintenant, le form_widget(form.yourVariable.var.prototype) ne rendra que le champ et rien d'autre.

1voto

Evgeny Smirnov Points 156

Voici exemple de code pour le prototype de données personnalisé :

{{ form_widget(form.emails.get('prototype')) | e }}

emails - votre collection.

8 votes

Comment est-elle personnalisée ? N'est-ce pas un prototype de données par défaut ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X