Can I put html inside a Symfony form button with Twig? - twig

I'm trying to put html inside a form button with twig like:
{{ form_widget(form.jiraStatus, {
'label': '<i class="fa fa-bug"></i>Bug',
'attr':{'class': 'btn btn-large btn-default btn-block' }
}) }}
But doing this, the rendeded button shows like this:
<button type="submit" name="SolveTask[taskTypesFormObj][bugStatus]"
class="btn btn-large btn-default btn-block">
<i class="fa fa-bug"></i>Bug
</button>
As you can see, the html inside the button is encoded. I tried to use the raw filter, but the effect is the same. There is a way to do this?
Thanks!

Yes, but you'll have to customise your form theme.
Note: This answer has been edited to be compatible with Symfony 2.8 3.x and 4.x. For older versions, please see the edit history.
A nice way to support icons in buttons is using form extensions. First create a form extension class that defines a new property icon that you can use in your forms:
<?php
namespace Foo\BarBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ButtonTypeIconExtension extends AbstractTypeExtension
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->setAttribute('icon', $options['icon']);
}
/**
* #param FormView $view
* #param FormInterface $form
* #param array $options
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['icon'] = $options['icon'];
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(['icon' => null]);
$resolver->setDefined(['icon']);
}
/**
* Returns the name of the type being extended.
*
* #return string The name of the type being extended
*/
public function getExtendedType()
{
return ButtonType::class; // Extend the button field type
}
}
Register this extension in your services.yml (or xml file). The alias must correspond with the string returned by the above getExtendedType() method.
# Form extension for adding icons
foobar.form_extension.icon:
class: Foo\BarBundle\Form\Extension\ButtonTypeIconExtension
tags:
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\ButtonType }
Next, override your form_div_layout.html.twig. (See link above) You can now use icon as a variable in these themes. For buttons we override the button_widget block:
{% block button_widget -%}
{% set attr = attr|merge({class: (attr.class|default('') ~ ' btn')|trim}) %}
{% 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 -%}
{% if icon|default %}
{% set iconHtml = '<i class="fa ' ~ icon ~ '"></i> ' %}
{% else %}
{% set iconHtml = '' %}
{% endif %}
<button type="{{ type|default('button') }}" {{ block('button_attributes') }}>{{ iconHtml|raw }}{{ label|trans({}, translation_domain) }}</button>
{%- endblock button_widget %}
Finally, you can use the icon option in your template:
{{ form_widget(form.jiraStatus, {
'icon': 'fa-bug',
'label': 'Bug',
'attr':{'class': 'btn btn-large btn-default btn-block' }
}) }}
Or in your form classes:
$builder
->add('jiraStatus', SubmitType::class, [
'label' => 'Bug',
'icon' => 'fa-bug',
'attr' => [
'class' => 'btn btn-large btn-default btn-block',
],
]
);
Note: Is is generally better to add the icon in the template since icons are a matter of presentation, and your form classes should really be about buisness logic.
Make it even more generic:
By returning the FQCN of ButtonType in getExtendedType() we tell Symfony that we are extending all possible form elements that inherit from ButtonType such as SubmitType. Unfortunately there is no type we can use to target all possible form elements but we can add an extra extension that targets FormType. All form fields like input boxes and select elements inherit from this type. So if you want it to work with both form fields and buttons, I suggest the following:
Create an abstract class abstract class AbstractIconExtension extends AbstractTypeExtension with exactly the same content as above but leave out the getExtendedType method. Then create two classes that extend from this class (e.g. FieldTypeIconExtension and ButtonTypeIconExtension) which only contain the getExtendedType method. One returning the FQCN of FormType and the other returning the FQCN of ButtonType:
Foo/BarBundle/Form/Extension/ButtonTypeIconExtension.php:
<?php
namespace Foo\BarBundle\Form\Extension;
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
class ButtonTypeIconExtension extends AbstractIconExtension
{
/**
* Returns the name of the type being extended.
*
* #return string The name of the type being extended
*/
public function getExtendedType()
{
return ButtonType::class; // extend all buttons
}
}
Foo/BarBundle/Form/Extension/FieldTypeIconExtension.php:
<?php
namespace Foo\BarBundle\Form\Extension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
class FieldTypeIconExtension extends AbstractIconExtension
{
/**
* Returns the name of the type being extended.
*
* #return string The name of the type being extended
*/
public function getExtendedType()
{
return FormType::class; // extend all field types
}
}
Register these two classes in you services using the corresponding alias:
# Form extensions for adding icons to form elements
foobar.form_extension.button_icon:
class: Foo\BarBundle\Form\Extension\ButtonTypeIconExtension
tags:
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\ButtonType }
foobar.form_extension.form_icon:
class: Foo\BarBundle\Form\Extension\FieldTypeIconExtension
tags:
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FormType }
Now you can use the icon variable in other places in your form themes as well. For instance, to add icons to labels you can override the form_label block:
{% block form_label -%}
{% if label is not sameas(false) -%}
{% if not compound -%}
{% set label_attr = label_attr|merge({'for': id}) %}
{%- endif %}
{% if required -%}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %}
{%- endif %}
{% 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 -%}
{% if icon|default %}
{% set iconHtml = '<i class="fa ' ~ icon ~ '"></i> ' %}
{% else %}
{% set iconHtml = '' %}
{% endif %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ iconHtml|raw }}{{ label|trans({}, translation_domain) }}</label>
{%- endif %}
{%- endblock form_label %}
And then add an icon to the label of that field in your form class:
$builder
->add('mytextfield', TextType::class, [
'label' => 'My fancy text field',
'icon' => 'fa-thumbs-o-up'
]
);

If you're looking for a simpler solution, just insert this into your form theme:
{%- block button_widget -%}
{% set attr = attr|merge({class: (attr.class|default('btn-default') ~ ' btn')|trim}) %}
{%- 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') }}>{{ label|trans({}, translation_domain)|raw }}</button>
{%- endblock button_widget -%}
You can then go ahead and insert HTML in your button label:
{{ form_widget(searchForm.search, {'label': '<span class="glyphicon glyphicon-search" aria-hidden="true"></span>'}) }}

this is how I solved and tested with Symfony 4.
In twig template:
{{form_start(form)}}
{{form_widget(form)}}
<div class="class row">
<div class="class col-sm-offset col-sm-10">
<button name='create' type='submit' value='create' class='btn btn-primary'>Save</button>
<button name='cancel' type='submit' value='cancel' class='btn btn-cancel' formnovalidate='formnovalidate'>Cancel</button>
</div>
</div>
{{form_end(form)}}
In my PHP controller form I did not add any button but just input fields.
$form = $this->createFormBuilder($article)
->add('title',TextType::class, array(
'data' => $article->getTitle(),
'attr' => array('class' => 'form-control')))
->add('body', TextareaType::class, array(
'data' => $article->getBody(),
'required' => false,
'attr' => array('class' => 'form-control')))
->getForm();
What I did to check the form submission is:
if($form->isSubmitted() ){
if($request->request->get('create') && $form->isValid()){
$article = $form->getData();
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($article);
$entityManager->flush();
} //l'alternativa può solo essere il cancel
return $this->redirectToRoute('article_list');
}
Hope this can help. Good to say that even the problem of buttons alignment is solved because div is not added for every button as form add method does.

On Symfony 5.1, you can add html content into button label like this:
{{ form_widget(form.submit, { 'label': '<i class="fas fa-calculator"></i> Calculate prices', 'label_html' : true })}}
You need to pass the option "label_html" : true
You can view the original Symfony/Twig code from form_div_layout.html.twig file to understand it:
{%- 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 not same as(false) -%}
{% set label = name|humanize %}
{%- endif -%}
{%- endif -%}
<button type="{{ type|default('button') }}" {{ block('button_attributes') }}>
{%- if translation_domain is same as(false) -%}
{%- if label_html is same as(false) -%}
{{- label -}}
{%- else -%}
{{- label|raw -}}
{%- endif -%}
{%- else -%}
{%- if label_html is same as(false) -%}
{{- label|trans(label_translation_parameters, translation_domain) -}}
{%- else -%}
{{- label|trans(label_translation_parameters, translation_domain)|raw -}}
{%- endif -%}
{%- endif -%}
</button>
{%- endblock button_widget -%}

An even simpler solution might be to leave the buttons out of the form type and set name and value attributes. Then retrieve them as you would normal post parameters in the controller.
In your template:
{{ form_start(form) }}
<button name="clicked" value="saveDraft" class="btn btn-warning">
<i class="fa fa-square-o"></i> Save as Draft
</button>
<button name="clicked" value="saveComplete" class="btn btn-warning">
<i class="fa fa-check-square-o"></i> Save as Complete
</button>
Then in your controller
if ($form->isSubmitted() && $form->isValid()) {
$clicked = $request->request->get('clicked');
}

Related

how to remove line break in twig template containing a for loop and an include?

I would like to remove a line break between multiple html tags (<a>) from a twig template which contains a for loop and an include of another twig template.
{% for child in field.value %}
{% include '#EasyAdmin/crud/field/association_link.html.twig' with {
field: {
value: child,},
} %}
{%- if loop.index < field.value|length -%}
,
{%- endif %}
{% endfor %}
And the twig templated included :
{% set url = ea_url()
.setController('App\\Controller\\Admin\\' ~ get_class(field.value)|split('\\')|last ~ 'CrudController')
.setEntityId(field.value.id)
.setAction('edit') %}
{% if url -%}
{{ field.value|markdown }}
{%- else -%}
{{ field.value|markdown }}
{%- endif -%}
It renders such a result :
<a href=url2>name1<a/>
,
<a href=url2>name2<a/>
How to get this result instead ?
<a href=url2>name1<a/>,
<a href=url2>name2<a/>
2nd row, 6th field : a break line before and after the comma

checboxes doesn't add checked attribute

Having some problems overriding the Grav CMS checboxes.
The problem is that checked attribute doesn't appear. For this purpose Grav uses the {% if checked %}checked="checked"{% endif %} in twig template. But it doesn't work. Just nothing is added after a click.
It doesn't work with my code.
The overridden themes/child-theme/templates/forms/fields/checkboxes/checkboxes.html.twig
{% extends "forms/field.html.twig" %}
{% set originalValue = value %}
{% set value = (value is null ? field.default : value) %}
{% if field.use == 'keys' and field.default %}
{% set value = field.default|merge(value) %}
{% endif %}
{% block global_attributes %}
{{ parent() }}
data-grav-keys="{{ field.use == 'keys' ? 'true' : 'false' }}"
data-grav-field-name="{{ (scope ~ field.name)|fieldName }}"
{% endblock %}
{% block input %}
{% for key, text in field.options %}
{% set id = field.id|default(field.name)|hyphenize ~ '-' ~ key %}
{% set name = field.use == 'keys' ? key : id %}
{% set val = field.use == 'keys' ? '1' : key %}
{% set checked = (field.use == 'keys' ? value[key] : key in value) %}
{% set help = (key in field.help_options|keys ? field.help_options[key] : false) %}
<div class="checkboxes {{ form_field_wrapper_classes }} {{ field.wrapper_classes }}">
<input type="checkbox"
id="{{ id|e }}"
value="{{ val|e }}"
name="{{ (scope ~ field.name)|fieldName ~ '[' ~ name ~ ']' }}"
class="{{ form_field_checkbox_classes }} {{ field.classes }}"
{% if checked %}checked="checked"{% endif %}
{% if field.disabled or isDisabledToggleable %}disabled="disabled"{% endif %}
>
<label style="display: inline" for="{{ id|e }}">
{% if help %}
<span class="hint--bottom" data-hint="{{ help|t|e('html_attr') }}">{{ text|t|e }}</span>
{% else %}
{{ text|t|e }}
{% endif %}
</label>
</div>
{% endfor %}
{% endblock %}
The checkbox definitions in md
-
name: planner_project_type_checkbox
type: checkboxes
label: false
options:
Text Option: 'Text Value'
Text Option: 'Text Value'
Text Option: 'Text Value'
outerclasses: 'fields-group-controls onyx-checkboxes'
classes: onyx-checkboxes
DOM elements, no checked="checked" after click:
Nor with quark theme. Checkboxes definitions:
---
form:
name: myform
fields:
-
name: myField
type: checkboxes
options:
option1: Option 1
option2: Option 2
---
DOM elements, no checked="checked" after click:
"DOM elements, no checked="checked" after click"
That's how a checkbox works by design... See MDN docs about the checkbox
checked
A Boolean attribute indicating whether or not this checkbox is checked by default (when the page loads). It does not indicate whether this checkbox is currently checked: if the checkbox's state is changed, this content attribute does not reflect the change. (Only the HTMLInputElement's checked IDL attribute is updated.)
Note: "indicating whether or not this checkbox is checked by default"
Also, try the checkbox sample at the top of the page. There is no change in the DOM when the checkbox is checked or unchecked.

"block checkbox_widget" draw a checkbox

I'm customizing the checkbox_widget in one form. In the template that draw this form I have:
{%- block checkbox_widget -%}
{% set emailmult1a00 = app.session.get('emailmult1a00') %}
<input type="checkbox" name="{{ emailmult1a00 }}" />
{%- endblock checkbox_widget -%}
It works fine in my form, but it draws a checkbox in place where the block is in the template.
How can I delete this checkbox?
I resolve the problem using a separate Template:
{# templates/form/fields.html.twig #}
{%- block checkbox_widget -%}
{% set emailmult1a00 = app.session.get('emailmult1a00') %}
<input type="checkbox" name="{{ emailmult1a00 }}" />
{%- endblock checkbox_widget -%}
And then, in my form template:
{% form_theme formulario 'form/fields.html.twig' %}

variables {% set %} in a skeleton file's 'block' don't get recognized , and can't be used when extnding the file?

I have a symfony3/twig skeleton template
page1/skeleton.twig
{# set default values #}
{% block content %}
{% set test = {
sec1: {
title: "null",
content: 'null'
},
}
%}
{% endblock %}
<ul>
19 {% for sec in test[0:] %}
<li>
<p>{{ sec.title }}</p>
<div>
<p>{{ sec.content }}</p>
</div>
</li>
{% endfor %}
</ul>
I then create a layout template that extends the skeleton with 'real' data
page1/layout.html.twig
{% extends 'page1/skeleton.html.twig' %}
{% block content %}
{% set test = {
sec1: {
title: "title1",
content: 'content2'
},
sec2: {
title: "title2",
content: 'content2'
}
%}
{% endblock %}
But when I generate/publish the page, Symfony fires an error
Variable "test" does not exist in :page1:skeleton.html.twig at line 19
500 Internal Server Error - Twig_Error_Runtime
complaining about the skeleton itself.
That 'test' array is defined in the skeleton. Afaict from reading the docs on 'block', 'extends' & 'set', and can't figure out what exactly the problem is.
What do I need to change to eliminate this error?
blocks in twig have their own variable scope.Variables created inside a block can't be accessed outside of it.
Imo you should only test if the variable exist and otherwise create the default value :
skeleton.twig
{% if not test is defined %}
{%
set test = {
sec1: {
title: "null",
content: 'null'
},
}
%}
{% endif %}
<ul>
{% for sec in test[0:] %}
<li>
<p{{ sec.title }}</p>
<div>
<p>{{ sec.content }}</p>
</div>
</li>
{% endfor %}
</ul>
controller.php
<?php
echo $twig->render('page/page.twig', array(
'foo' => [
'title' => 'title1',
'content' => content1',
],
);
Change this in the file page1/skeleton.twig:
{% for sec in test %}
Then it will work.
I tried it. Make sure you understand why!

Twig: remove div around each checkbox fields of a form

Edit: see the solution I've found at the end of my question ;-)
I'm looking since hours for a solution but I do not really find what I'like. I'm sure it is easy to do.
I'm building a form with Symfony 3.1. My form does not send anything in a database. Everything seems okay. But when rendering my twig template all my checkbox are surrounded by a <div> html tag.
I just would like twig renders me a form without that <div> tag.
edit: this is what Twig render me: this is ok for me but I would like to remove div tags
<form name="form" method="post">
<div id="form"><div><label for="form_1">1</label><input type="checkbox" id="form_1" name="form[1]" class="ballsCheckBox" value="1" /></div>
</form>
Here is my twig template:
{% extends "::base.html.twig" %}
{% block title %}SimulotoBundle:Lotto:lotto{% endblock %}
{% block body %}
<h1>Welcome to the Lotto:result page</h1>
{{form(form)}}
{% endblock %}
I build the form directly in a controller. See it:
public function LottoSimulationFormAction()
{
$lt = new LottoSimulation();
$data = [];
$formBuilder = $this->createFormBuilder($data);
/** building Lotto grid with checkboxes * */
for ($i = 1; $i <= $lt->getMaxNb(); $i++)
{
$formBuilder->add($i, CheckboxType::class, [
'label' => $i,
'required' => false,
'attr' => [
'class' => 'ballsCheckBox'
]
]);
}
/** adding submit button **/
$formBuilder->add('Envoyer', SubmitType::class, [
'attr' => [
'class' => 'save'
]
]);
$form = $formBuilder->getForm();
return $this->render("SimulotoBundle:Lotto:lotto.html.twig", [
"form" => $form->createview()
]);
}
}
Here is the soluton to solve this problem.
I need to Customize the form rendering as it is explained in the Synfony cookBook on this page http://symfony.com/doc/current/form/form_customization.html
Go to vendor\Symfony\Bridge\Twig\Ressources\views\Form\form_div_layout.html and modify the block:
{%- block form_row -%}
<div>
{{- form_label(form) -}}
{{- form_errors(form) -}}
{{- form_widget(form) -}}
</div>
{%- endblock form_row -%}
You can remove it but a better way is to copy/past that block to your teplate and overide it. Like this:
{% extends "::base.html.twig" %}
{% form_theme form _self %} //don't forget this line of code
{%- block form_row -%}
{{- form_label(form) -}}
{{- form_errors(form) -}}
{{- form_widget(form) -}}
{%- endblock form_row -%}

Resources