Render custom entity type field in form (Symfony) - forms

I got stuck while trying to customize the rendering of a specific field in my form.
It looks like this:
$builder->add('players', 'entity', array(
'class' => 'Acme\Bundle\Entity\Player',
'expanded' => true,
'multiple' => true,
'required' => false,
));
The form itself is beeing rendered with a simple:
{% block form_content %}
{% form_theme form 'AcmeBundle:Form:fields_child.html.twig' %}
{{ form_widget(form) }}
{% endblock %}
Now inside fields_child.html.twig i'm extending from another form template but there is nothing special there.
My HTML looks like this:
Players:
- [checkbox-input] 1
Where 1 equals the id of the only player in the database. However instead of rendering the ID im trying to render his picture and full name after the checkbox.
I have tried many combinations of the form theming to override it but failed each time.
Could someone post the twig block to render what i want here?
Thanks

You have to create custom form field type together with custom widget template for it.
http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html

I recently ran into this problem (was a little bit different situation. I needed to show products as table with checkboxes..), Form's child data always returned null value that's why I ended up with this (dirty:)) solution:
Controller action:
...
$productRepository = $entityManager->getRepository('VendorMyBundle:Product');
$products = [];
$formChildren = $productListForm->createView()->children;
foreach ($formChildren['products'] as $formProduct) {
$formProductId = $formProduct->vars['value'];
$productEntity = $productRepository->find($formProductId);
$products[$formProductId] = $productEntity;
}
...
return $this->render('TEMPLATE', [
'productListForm' => $productListForm->createView(),
'products' => $products,
]);
Template:
...
{% for productForm in productListForm.products %}
{% set id = productForm.vars.value %}
<tr>
<td class="check">
{{ form_widget(productForm) }}
</td>
<td class="photo">
{% if products[id].getImages().isEmpty() == false %}
{% set productImage = products[id].getImages().first() %}
<img src="{{ productImage.getWebPath() | imagine_filter('st_product_cabinet_thumbnail') }}" />
{% else %}
<span class="no-image">No image</span>
{% endif %}
</td>
<td class="title">
{{ products[id].getName() }}
</td>
<td class="status">
{{ products[id].getStatusName(products[id].getStatus()) }}
</td>
<td class="price">
<ul>
{% for productPrice in products[id].getPrices() %}
<li>{{ productPrice.getValue() ~ ' ' ~ productPrice.getCurrencyCode() }}</li>
{% endfor %}
</ul>
</td>
</tr>
{% endfor %}
...

Related

Customize data-prototype attribute in Symfony 2 forms

I am trying to follow the Symfony 2.7 docs to create Embed a Collection of Forms using a custom Collection Prototype.
Problem is, that I am not able to create a custom collection prototype as described in the docs.
As in the example there are two simple classes:
A Task class that manages the description of the task and additionally any number of tags, represented by its own Tag class
class Task {
protected $description;
protected $tags;
public function __construct() {
$this->tags = new array();
}
// Getter & Setter for description + additional addTag & removeTag methods
// ...
// Tags getter
public function getTags() {
return $this->tags;
}
}
class Tag {
protected $name;
// ... setName(...), getName()...
}
These are the custom form types:
class TaskType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('description');
$builder->add('tags', 'collection', array('type' => new TagType()));
}
public function getName() {
return 'task';
}
// ...
}
class TagType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('name');
}
public function getName() {
return 'tag';
}
// ...
}
Twig file to render the form
{{ form_start(form) }}
{# render the task's only field: description #}
{{ form_row(form.description) }}
{# render tags - use table instead of ul as in example #}
<div class="table-responsive">
<table class="table">
<thead>
<th>{{ 'task.tag.headline'|trans }}</th>
</thead>
<tbody class="tags-container" data-prototype="{{ form_widget(form.tags.vars.prototype)|e('html_attr') }}">
{{ form_row(form.rules) }}
</tbody>
</table>
</div>
{{ form_end(form) }}
This works fine and renders the tags list inside the table. However, this code uses the default prototype, that (of course) does not create table rows for the different tags.
I tried to add the code to use a custom prototype as described in the docs. How ever the docs does not say anything about where to add this code or how to use it:
Twig code WITH custom prototype code
{{ form_start(form) }}
{# Custom Prototype Code from docs #}
{% form_theme form _self %}
{% block _tags_entry_widget %}
<tr>
<td>{{ form_widget(form.name) }}</td>
</tr>
{% endblock %}
{# render the task's only field: description #}
{{ form_row(form.description) }}
{# render tags - use table instead of ul as in example #}
<div class="table-responsive">
<table class="table">
<thead>
<th>{{ 'task.tag.headline'|trans }}</th>
</thead>
<tbody class="tags-container" data-prototype="{{ form_widget(form.tags.vars.prototype)|e('html_attr') }}">
{{ form_row(form.rules) }}
</tbody>
</table>
</div>
{{ form_end(form) }}
Using the custom prototype code like this results in the error:
Method "name" for object "Symfony\Component\Form\FormView" does
not exist in "MyAppBundle:Task:task.html.twig"
This sounds reasonable, since name belongs to the Tag class and not to the Task class.
Problem 1: How to use/access the Tag form inside the template?**
I removed <td>{{ form_widget(form.name) }}</td> from the prototype template and replaced it with <td>Test</td> to see if the template is used. The result: The template is NOT used and has no effect.
Problem 2: What is the correct way to set/activate the prototype template?
I found other threads dealing with prototype question/problems. The answers propose different solutions using macros, external twig files, etc. Since the Symfony docs seems to offer a solution within the same file without using hacks like macros, I would like to know implement this solution.
This is working for me - I am sorry - it is Symfony 3, but you should be able to translate.
In the buildForm method of my PersonType class, I have a CollectionType of emails
->add( 'emails', CollectionType::class, [
'label' => 'common.email',
'entry_type' => AppEmailType::class,
'by_reference' => true,
'required' => false,
'label' => false,
'empty_data' => null,
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'mapped' => false,
'prototype_name' => '__email__'
] )
The template to render the PersonType includes the emails form like so
{% include 'common/emails.html.twig' with {'form': form.emails } %}
common/emails.html.twig
This template is the container for the collection
<div class="emails">
<span class="sub-form-legend">{{'common.email'|trans}}</span>
{{form_row(form)}}
{% if form.vars.allow_add %}
<div class="add-one-more-row">{{ 'common.add_one_more'|trans}}</div>
{% endif %}
</div>
In fields.html.twig, I have an entry_row template specific to the form, which uses a common email template defined in the same file.
{% block _user_person_emails_entry_row %}
{{ block('_emails_entry_row') }}
{% endblock %}
{% block _emails_entry_row %}
{% spaceless %}
<div class="form-row email">
<span class="type-select">{{ form_widget(form.type) }}</span>
<span class="input">{{ form_widget(form.email) }}</span>
<span class="comment">{{ form_widget(form.comment) }}</span>
<span class="remove-form-row" title="{{'common.remove'|trans}}" id="email-__email__"><i class="fa fa-remove"></i></span>
</div>
{% endspaceless %}
{% endblock %}
To find out the name, trace the names of your forms and prefix them with an underscore. Use {{dump(form)}} to get the unique name.
The HTML will be placed in the data-prototype attribute.

Symfony-Twig: insert fontawesome icon in a form_widget

To validate a form I am using a standard:
{{ form_widget(form.save, {'attr': {'class': 'btn btn-sm btn-danger'}, 'label': 'Submit form'}) }}
I want to insert a fontawsome icon in the button. I tried:
{{ form_widget(form.save, {'attr': {'class': 'btn btn-sm btn-danger'}, 'label': '<i class="fa fa-envelope-o"></i> Submit form'}) }}
But it is not working; obviously
Any idea how to that?
I would define a new form template in the same view (or in a template if you need to reuse the code). More details here
{% extends '::base.html.twig' %}
{% form_theme form _self %}
{%- block submit_widget -%}
{%- set type = type|default('submit') -%}
{%- if label is empty -%}
{%- if label_format is not empty -%}
{% set label = label_format|replace({
'%name%': name,
'%id%': id,
}) %}
{%- else -%}
{% set label = name|humanize %}
{%- endif -%}
{%- endif -%}
<button type="{{ type|default('button') }}" {{ block('button_attributes') }}>
<i class="fa fa-envelope-o"></i>
{{ label|trans({}, translation_domain) }}
</button>
{%- endblock submit_widget -%}
{% block content %}
{# ... render the form #}
{{ form_row(form.age) }}
{% endblock %}
EDIT
You can also extend ButtonType to allow icon_before and icon_after in order to add icons easily in form definition :
$form->add('submitReportV2Show', SubmitType::class, array(
'label' => 'My test',
'icon_before' => 'fa-refresh',
'icon_after' => 'fa-refresh',
'attr' => array('class' => 'btn btn-sm btn-success'
)));
Create a new class src/bundle/Form/Extension:
namespace YourBundle\ToolBoxBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class IconButtonExtension extends AbstractTypeExtension
{
public function getExtendedType()
{
return ButtonType::class;
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['icon_before'] = $options['icon_before'] ?? '';
$view->vars['icon_after'] = $options['icon_after'] ?? '';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'icon_before' => null,
'icon_after' => null
]);
}
}
Declare it in service src/bundle/Resources/config/service.yml
bundle.tools.form.type_extension.icon_button:
class: YourBundle\ToolBoxBundle\Form\Extension\IconButtonExtension
tags:
- { name: 'form.type_extension', extended_type: 'Symfony\Component\Form\Extension\Core\Type\ButtonType' }
app/Resources/views/Form/fields.html.twig
{%- block button_widget -%}
{%- if label is empty -%}
{%- if label_format is not empty -%}
{% set label = label_format|replace({
'%name%': name,
'%id%': id,
}) %}
{%- elseif label is same as(false) -%}
{% set translation_domain = false %}
{%- else -%}
{% set label = name|humanize %}
{%- endif -%}
{%- endif -%}
<button type="{{ type|default('button') }}" {{ block('button_attributes') }}>
{% if icon_before is defined and icon_before is not null %}
<i class="fa {{ icon_before }}"></i>
{% endif %}
{{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }}
{% if icon_after is defined and icon_after is not null %}
<i class="fa {{ icon_after }}"></i>
{% endif %}
</button>
{%- endblock button_widget -%}
sdespont's answer is the correct answer and worthy of being selected. I have, however, extended the functionality of it to include adding the custom fa class icon as well as whether the icon is placed to the left or right of the button text.
Since this functionality accepts variables the best thing to do is to create a template to be reused instead of customising just the view.
Form template: app/Resources/views/form/submit.html.twig
{# app/Resources/views/form/submit.html.twig #}
{% block submit_widget %}
{% set type = type|default('submit') %}
{% if label is empty %}
{% if label_format is not empty %}
{% set label = label_format|replace({
'%name%' : name,
'%id%' : id,
}) %}
{% else %}
{% set label = name|humanize %}
{% endif %}
{% endif %}
<button type="{{ type|default('button') }}" {{ block('button_attributes') }}>
{% if fa is defined %}
{% if left is defined and left %}
<i class="fa {{ fa }}"></i>
{% endif %}
{{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }}
{% if right is defined and right %}
<i class="fa {{ fa }}"></i>
{% endif %}
{% else %}
{{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }}
{% endif %}
</button>
{% endblock submit_widget %}
Controller:
$form = $this->createFormBuilder($user)
...
->add('submit', SubmitType::class, array(
'attr'=> array('class'=>'myclass')
))
->getForm();
Twig template:
{{ form_widget(form.submit, {'fa' : 'fa-long-arrow-right','right' : true}) }}
You can set any old fa icon and even sizing like so: fa-long-arrow-right fa-2x
The easiest, you can put your button with html and form vars:
<button type="submit" name="{{ form.send.vars.full_name }}" id="{{ form.send.vars.id }}" class="btn btn-sm btn-danger"><i class="fa fa-envelope-o"></i></button><
You can just add a new custom service css class per icon
/*
* css selector for a class attribute that starts with "btn-fa-" or has " btn-fa-" in it:
*/
[class^="btn-fa-"]:before,
[class*=" btn-fa-"]:before
{
font-family: "Font Awesome 5 Free";
font-weight: bold;
margin: 0 6px 0 2px;
}
/*
* And then only 1 setting per font awesome class
*/
.btn-fa-plus:before {
content: '\f067';
}
And add the class to the ButtonType
->add('Add an item', ButtonType::class, [
'attr' => [
'class' => 'btn btn-primary btn-fa-plus',
]
])
In YourFormType.class -> buildForm
->add('submit', SubmitType::class, [
'attr' => [
'class' => 'main-btn primary-btn',
],
'label' => '<i class="fas fa-search"></i> Search',
'label_html' => true,
])

Placeholder for form_widget choices on Twig

Using PHP on controller side, i can add a placeholder to my field using the createFormBuilder():
$form = $this->createFormBuilder($company)
->add('name', 'text')
->add('cities', 'entity', array(
'class' => 'NexBaseBundle:City',
'property' => 'name',
'placeholder' => 'Choose an option',
))
->getForm();
I am looking to add a placeholder using Twig, for simple fields i can use attr like this :
{{ form_widget(form.name, {'attr': {'placeholder': 'My placeholder'} }) }}
I have tried with this but no luck:
{{ form_widget(form.cities, {'attr': {'placeholder': 'Choose your city'} }) }}
You can't because the "select box" on html5 does not have an attribute "placeholder", your attribute "placeholder" in your formbuilder is an alternative of empty_value
https://github.com/symfony/symfony/issues/5791
On the createFormHandler its the empty_data option. You can use the placeholder option but its possible that you get some trouble in older Browsers.
Normally i disable that HTML5 functions and do it with Javascript.
http://www.hagenburger.net/BLOG/HTML5-Input-Placeholder-Fix-With-jQuery.html
Here is a cool jQuery plugin to fix that problem in older Browsers.
Setting the value from your controller make not really sense. You can set a key in your form and translate them for example.
You can add the attribute only on text fields. Your second method on a Selext-Box is not working because there is not attribute placeholder on that element. So you can use the placeholder in your FormType but that is changed to an option field in background.
Here is an example how I am currently adding placeholders (My select list can be different then yours, but you can play around of this snippet):
{% form_theme cart_form _self %}
{% block choice_widget %}
{% if form.vars.choices is defined %}
{% for choices in form.vars.choices %}
<div class="row">
<div class="col-md-6">
<select name="{{ form.vars.full_name }}" class="form-control">
<option value="__none">--Choose</option>
{% for choice in choices %}
<option value="{{ choice.value }}">{{ choice.label }}</option>
{% endfor %}
</select>
</div>
</div>
{% endfor %}
{% endif %}
{% endblock %}

Get form from rendered controller

I have such problem with symfony2.
I have base.html.twig and this base is the template for others twigs.
I want to render controller, which contains form with locale-type input field.
The form will be used to change language of site.
My LanguageController:
<?php
namespace Soczewki\PlatformaBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class LanguageController extends Controller {
public function createAction(Request $Request)
{
$form = $this->createFormBuilder()
->setMethod('POST')
->setAction(null)
->add('locale', 'locale', array(
'label' => ' ',
'choices' => array('pl' => 'Polski', 'de' => 'Deutsch', 'en' => 'English'),
'data' => $this->getRequest()->getLocale(),
'required' => true))
->add('submit', 'submit')
->getForm();
$form->handleRequest($Request);
if($form->isValid()) {
$this->getRequest()->setLocale('en');
}
return $this->render("SoczewkiPlatformaBundle::myForm.html.twig",
array(
'form' => $form->createView(),
'req' => $r
));
}
}
The whole base.html.twig:
<html>
<head>
<title>{% block pageTitle %}{% endblock %}</title>
{#js#}
<script src="{{ asset('bundles/soczewkiplatforma/js/jquery.js') }}"></script>
<script src="{{ asset('bundles/soczewkiplatforma/js/bootstrap.js') }}"></script>
{#/js#}
{% block stylesheets %}
<link href="{{ asset('bundles/soczewkiplatforma/css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ asset('bundles/soczewkiplatforma/css/my-style.css') }}" rel="stylesheet">
{% endblock stylesheets %}
</head>
<body>
{% if app.security.getToken().getUser().getAuthKey() is defined %}
{% if app.security.getToken().getUser().getAuthKey() is not empty %}
{% set diff = ( app.security.getToken().getUser().getEndDate()|date('U') - "now"|date('U') ) / 3600 %}
<div style="width: 300px; height: 140px; position: absolute; bottom: 90px; right: 50px;" class="alert alert-danger alert-error">
<button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<strong>Uwaga!</strong> Twoje konto nadal nie zostało aktywowane!<br />
Pozostało Ci: <span class="badge">{{ diff[0:2] }} godzin {{ ( diff[3:] * 60 )[0:2] }} minut </span>
<br /><br />
<button type="button" class="btn btn-danger">Wpisz kod</button>
<button type="button" class="btn btn-default" data-dismiss="alert">Zamknij</button>
</div>
{% endif %}
{% set hasParams = app.request.get('id') %}
{% if hasParams is empty %}
{% set currentPath = url( app.request.attributes.get( '_route', app.request.attributes.get('_route_params'))) %}
{% else %}
{% set currentPath = url( app.request.attributes.get( '_route', app.request.attributes.get('_route_params')),{'id': 0} ) %}
{% endif %}
{% set dirs = currentPath|split('/') %}
{% set flag = "" %}
{% set url = "" %}
<ol class="breadcrumb">
<li>Główna</li>
{% for key, dir in dirs %}
{% if url is not empty %}
{% set url = url ~ '/' ~ dir %}
{% else %}
{% set url = url ~ dir %}
{% endif %}
{% if flag == true %}
{% if '=' in dir %}
{% set _temp = dir|split('=') %}
{% else %}
{% set _temp = dir|split(' ') %}
{% endif %}
{% if key + 1 == dirs|length %}
<li class="active"> {{ _temp[0]|capitalize|replace('-',' ') }} </li>
{% else %}
<li> {{ _temp[0]|capitalize|replace('-',' ') }} </li>
{% endif %}
{% endif %}
{% if dir == 'app_dev.php' %}
{% set flag = true %}
{% endif %}
{% endfor %}
<li class="dropdown" style="float: right !important;">
Zalogowano jako: {{ app.security.getToken().getUser().getName() }} <span class="caret"></span>
<ul class="dropdown-menu" role="menu">
<li>Moje konteło</li>
<li>Wyloguj</li>
</ul>
</li>
</ol>
{% else %}
<ol class="breadcrumb">
<li>Główna</li>
<!--- logowanie --->
<li class="dropdown" style="float: right !important;">
Zaloguj <span class="caret"></span>
<div class="dropdown-menu" style="padding: 15px; padding-bottom: 0px; left: -200px;">
<form action="{{ path('login_check') }}" method="post">
<label for="username">Email/NIP:</label>
<input type="text" id="username" name="_username" />
<br />
<label for="password">Hasełko:</label>
<input type="password" id="password" name="_password" />
<br /><br />
<button type="submit">Loguj do Afganistanu</button>
</form>
</div>
</li>
<!--- log ---->
</ol>
{% endif %}
{{ render(controller('SoczewkiPlatformaBundle:Language:create')) }}
{% block pageContainer %}
{% endblock %}
</body>
</html>
Nothing shows and I get too any error.
Why is it like that?
// codes updated
myForm.html.twig:
{{ dump(req) }}
{{ form(form) }}
and LocaleListener.php:
<?php
namespace Soczewki\PlatformaBundle\Translations;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class LocaleListener implements EventSubscriberInterface
{
private $defaultLocale;
public function __construct($defaultLocale = 'pl')
{
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
// try to see if the locale has been set as a _locale routing parameter
if ($locale = $request->attributes->get('_locale')) {
$request->getSession()->set('_locale', $locale);
} else {
// if no explicit locale has been set on this request, use one from the session
$request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
}
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
);
}
}
You have created a situation where you are calling the same method an infinite amount of times until the page just dies.
You original base is being rendered which is in the end rendering the SoczewkiPlatformaBundle::base.html.twig template. Within this template a render() is calling an action that is then rendering the SoczewkiPlatformaBundle::base.html.twig template, which then calls the render(), which then render SoczewkiPlatformaBundle::base.html.twig, and on and on...
You need to specify a template for the form to be rendered in rather than the base and then call that in your changeAction.
For example...
Soczewki\PlatformaBundle\Controller\LanguageController
public function changeAction(Request $Request)
{
// ..
return $this->render("SoczewkiPlatformaBundle:Locale:change_form.html.twig",
array(
'form' => isset($form) ? $form->createView() : null,
));
}
SoczewkiPlatformaBundle:Locale:change_form.html.twig
{{ form_start(form, {'method': 'POST', 'action': 'your_locale_change_route' }) }}
{{ form_row(form.locale) }}
{{ form_end(form) }}
Your next problem would be that your changeAction isn't actually performing an action (apart from creating a form), although you may just be wanting that page to show before you get to that part.
You need to add this code where you want that the form appear
{{ form(form) }}
Edited after the comments below;
Create a file named myForm.html.twig and put in SoczewkiPlatformaBundle;
Now write this code inside:
{{ form(form) }}
now your controller should be:
public function changeAction(Request $Request)
{
$form = $this->createFormBuilder()
->add('locale', 'locale', array(
'label' => 'user.locale',
'required' => true,
'choices' => array('en' => 'English', 'de' => 'Deutsch', 'pl' => 'Polish')
))
->getForm();
$form->handleRequest($Request);
return $this->render("SoczewkiPlatformaBundle::myForm.html.twig",
array(
'form' => $form->createView(),
));
}

How to customize the data-prototype attribute in Symfony 2 forms

Since umpteens days, I block on a problem with Symfony 2 and forms.
I got a form of websites entities. "Websites" is a collection of website's entities and each website contains two attributes : "type" and "url".
If I want to add more of one website in my database, I can click on a "Add another website" link, which add another website row to my form. So when you click on the submit button, you can add one or X website(s) at the same time.
This process to add a row use the data-prototype attribute, which can generate the website sub-form.
The problem is that I customize my form to have a great graphic rendering... like that :
<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>
But the data-prototype doesn't care about this customization, with HTML and CSS tags & properties. I keep the Symfony rendering :
<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>
Does anyone have an idea to make that hack ?
A bit old, but here is a deadly simple solution.
The idea is simply to render the collection items through a Twig template, so you have full ability to customize the prototype that will be placed in your data-prototype="..." tag. Just as if it was a normal, usual form.
In yourMainForm.html.twig:
<div id="collectionContainer"
data-prototype="
{% filter escape %}
{{ include('MyBundle:MyViewsDirectory:prototype.html.twig', { 'form': form.myForm.vars.prototype }) }}
{% endfilter %}">
</div>
And in MyBundle: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>
Credit: adapted from https://gist.github.com/tobalgists/4032213
I know this question is quite old, but I had the same problem and this is how I soved it. I'm using a twig macro to accomplish this. Macros are like functions, you can render them with different 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 %}
now you can render this macro wherever you want. Note that information_prototype() is just the name of the macro, you can name it whatever you want. If you want to use the macro to render the given items and the prototype the same way, do something like this:
<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 holds the prototype data of the form with the prototype_name you specified. Use _self.+macroname if you want to use the macro in the same template.
You can find out more about macros in the Twig documentation
You probably have found out since but here is the solution for others.
Create a new template and copy/paste this code in it:
https://gist.github.com/1294186
Then in the template containing the form you want to customise, apply it to your form by doing this:
{% form_theme form 'YourBundle:Deal:Theme/_field-prototype.html.twig' %}
I've run into similar problem recently. Here's how you can override the collection prototype without having to explicitly set it in the html:
{% set customPrototype %}
{% filter escape %}
{% include 'AcmeBundle:Controller:customCollectionPrototype.html.twig' with { 'form': form.collection.vars.prototype } %}
{% endfilter %}
{% endset %}
{{ form_label(form.collection) }}
{{ form_widget(form.collection, { 'attr': { 'data-prototype': customPrototype } }) }}
You can do whatever you want then in your custom twig. For example:
<div data-form-collection="item" data-form-collection-index="__name__" class="collection-item">
<div class="collection-box col-sm-10 col-sm-offset-2 padding-top-20">
<div class="row form-horizontal form-group">
<div class="col-sm-4">
{{ form_label(form.field0) }}
{{ form_widget(form.field0) }}
</div>
<div class="col-sm-3">
{{ form_label(form.field1) }}
{{ form_widget(form.field1) }}
</div>
<label class="col-sm-3 control-label text-right">
<button data-form-collection="delete" class="btn btn-danger">
<i class="fa fa-times collection-button-remove"></i>{{ 'form.collection.delete'|trans }}
</button>
</label>
</div>
</div>
Useful when you only have to do it in specific places and don't need a global override that's applicable to all collections.
I know that answer is very late but it maybe useful for visitors.
on your theme file you can simply use one block for rendering every collection entry of websites widget as following:
{% block _jobcast_profilebundle_websitestype_websites_entry_widget %}
<div class="informations_widget">{{ form_widget(form.type.code) }}</div>
<div class="informations_error">{{ form_errors(form.type) }}</div>
<div class="informations_widget">{{ form_widget(form.url) }}</div>
<div class="informations_error">{{ form_errors(form.url) }}</div>
{% endblock %}
also create theme block for your collection widget row as following:
{% block _quiz_question_answers_row %}
{% if prototype is defined %}
{%- set attr = attr | merge({'data-prototype': form_row(prototype) }) -%}
{% endif %}
{{ form_errors(form) }}
{% for child in form %}
{{ form_row(child) }}
{% endfor %}
{% endblock %}
now the prototype and the rendered collection entry will be the same.
I had a somewhat similar issue. You might have to tweak this to work for your case, but someone may find it helpful.
Create a new template file to hold your custom form 'theme'
./src/Company/TestBundle/Resources/views/Forms/fields.html.twig
Normally, you can use the form_row function to display a field's label, error, and widget. But in my case I just wanted to display the widget. As you say, using the data-prototype feature would also display the label, so in our new fields.html.twig type your custom code for how you want the field to look:
{% block form_row %}
{% spaceless %}
{{ form_widget(form) }}
{% endspaceless %}
{% endblock form_row %}
I removed the container div, and the label and error, and just left the widget.
Now in the twig file that displays the form, simply add this after the {% extends ... %}
{% form_theme form 'CompanyTestBundle:Form:fields.html.twig' %}
And now the form_widget(form.yourVariable.var.prototype) will only render the field and nothing else.
Application wide form theming will be applied to the prototype.
See Making Application-wide Customizations
Here is sample code for custom data-prototype:
{{ form_widget(form.emails.get('prototype')) | e }}
where emails — your collection.
To customize differently existing collection items VS prototype, you can override collection_widget like this:
{%- block collection_widget -%}
{% if prototype is defined %}
{%- set attr = attr|merge({'data-prototype': form_row(prototype, {'inPrototype': true} ) }) -%}
{% endif %}
{{- block('form_widget') -}}
{%- endblock collection_widget -%}
And then in your custom entry:
{% block _myCollection_entry_row %}
{% if(inPrototype is defined) %}
{# Something special only for prototype here #}
{% endif %}
{% endblock %}
If you do not need to define a template system-wide, simply set a template in your twig template, and ask twig to use it.
{# using the profiler, you can find the block widget tested by twig #}
{% block my_block_widget %}
<div >
<p>My template for collection</p>
<div >
{{ form_row(form.field1)}}
</div>
<div>
{{ form_row(form.field2)}}
</div>
</div>
{% endblock %}
{% form_theme form.my_collection _self %}
<button data-form-prototype="{{ form_widget(form.my_collection.vars.prototype) )|e }}" >Add a new entry</button>
There are two blocks that you can possibly target to add a custom theme to a collection field:
_myCollection_row and _myCollection_entry_row
_myCollection_row - renders the whole collection.
_myCollection_entry_row - renders a single item.
The prototype relies on _myCollection_entry_row so if you theme _myCollection_row only your theme will appear in the form, but not the prototype. The prototype uses _myCollection_entry_row.
So it's best to theme _myCollection_entry_row first, and then theme _myCollection_row only if required. BUT - if you theme _myCollection_row make sure it calls _myCollection_entry_row to render each item in your collection.
This post focuses on using pre-existing conventions within the twig template.
Basing off "How to Embed a Collection of Forms" from the Symfony Cookbook (http://symfony.com/doc/master/cookbook/form/form_collections.html), you can just enter whatever html_escaped form data you wish in the data-prototype (maybe considered a hack, but works wonderfully) and only pages using that template will change.
In the example, they tell you to put:
<ul class="tags" data-prototype="{{ form_widget(form.tags.vars.prototype)|e }}">
...
</ul>
This can be successfully replaced with something such as:
<table class="tags" data-prototype="<tr> <td><div><input type="text" id="task_tags__name__tagId" name="task[tags][__name__][taskId]" disabled="disabled" required="required" size="10" value="" /></div></td> <td><div><input type="text" id="task_tags__name__tagName" name="task[tags[__name__][tagName]" required="required" value="" /></div></td></tr>">
<tr>
<th>Id</th>
<th>Name</th>
</tr>
<tr>
...pre existing data here...
</tr>
</table>
Where the data-type attribute of the table with the class "tags" above is the html-escaped version (and line breaks removed though spaces are ok and required) of:
<tr>
<td><div><input type="text" id="task_tags__name__tagId" name="task[tags][__name__][taskId]" disabled="disabled" required="required" size="10" value="" /></div></td>
<td><div><input type="text" id="task_tags__name__tagName" name="task[tags[__name__][tagName]" required="required" value="" /></div></td>
</tr>
...but you must also adjust the javascript in the example to add tr's instead of li elements:
function addTagForm(collectionHolder, $newLinkTr) {
...
// Display the form in the page in an tr, before the "Add a question" link tr
var $newFormTr = $('<tr></tr>').append(newForm);
...
};
...
// setup an "add a tag" link
var $addTagLink = $('Add a tag');
var $newLinkTr = $('<tr></tr>').append($addTagLink);
...
For me, the next step is figuring out how to define the prototype in an external file that I can somehow call in the twig template for the data-prototype that dynamically works with the form. Something like:
<table class="tags" data-prototype="{{somefunction('App\Bundle\Views\Entity\TagsPrototypeInTable')}}">
So if one of the other posts is describing this and I am too dense or if someone knows how to do such, say so!
There is a link to something from gitHub from Francois, but I didn't see any explanation so I think that is probably the more dynamic method I'll get to one of these near-future days.
Peace,
Steve
Update:
One can also use just parts of the prototype:
data-prototype="<tr> <td>{{ form_row(form.tags.vars.prototype.tagId) | e }}</td> <td>{{ form_row(form.tags.vars.prototype.tagName) | e }}</td></tr>"
Where the data-type attribute of the table with the class "tags" above is the html-escaped version (and line breaks removed though spaces are ok and required) of:
<td>{{ form_row(form.tags.vars.prototype.tagId) | e }}</td>
<td>{{ form_row(form.tags.vars.prototype.tagName) | e }}</td>
(I used http://www.htmlescape.net/htmlescape_tool.html.)
Symfony will replace the information between the {{}} with an html_escaped (because of the "|e") rendered field when the page is rendered. In this way, any customization at the field-level is not lost, but! you must manually add and remove fields to the prototype as you do so with the entity :)