Customize collection using bootstrap - php

I have a collection of an embedded form.
I'd like to customize the embedded form.
I want that each entry of the embedded form to be in 1 line, something like that :
<div class="row">
<div class="col-sm-6">field1</div>
<div class="col-sm-6">field2</div>
</div>
But symfony's doc is, in my opinion, poor for this.
I have a form ApplicationType :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('responsables', 'collection', array(
'label' => ' ',
'type' => new ApplicationResponsablesType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' =>false
))
//...
And ApplicationResponsablesType :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type', null, array(
'required' => true,
))
->add('id')
;
I really don't understand how to use {% block ___ %}.
my Application new.html.twig where I have the form :
{% block body -%}
<div class="container">
<br>
{{ form_start(form) }}
{# some other fields #}
{{ form_row(form.responsables) }}
{# some other fields #}
<div class="pull-right">
{{ form_row(form.submit) }}
</div>
{{ form_end(form) }}
{# ... closing block and tags... #}
I tried some things but since I didn't understand how it works and what I actually tried, I'll not put it...
Can anyone help me or lead me ? Thanks!
Edit :
As you can see on this image :
On the top there is the first part of Application's form.
And if the user add a lot of Responsable, this is huge. So I'd like to have Type and Uid on the same line.
(Here I'm talking only for responsable, but there is other collection on this form so that's why I'd like to simplify it)

Yes, Symfony doc is not very clear regarding this point.
To achieve what you want:
Create a file prototype_layout.html.twig:
{% block _application_responsables_entry_row %}
<div class="row">
<div class="col-sm-6">{{ form_row(form.type) }}</div>
<div class="col-sm-6">{{ form_row(form.id) }}</div>
</div>
{% endblock %}
The name of the block is important. The first part is _application because your parent form is called ApplicationType and the second part _responsables because your form field owning the collection is called so. The third part must always be _entry_row in order for this to work.
To make sure you got the first and second part of the name right, take a look in the DOM at the id of the select html element corresponding to your collection using the inspector tool of your browser.
Declare this file as a global form theme in the twig section of config.yml:
twig:
form_themes:
- 'AppBundle:Form:prototype_layout.html.twig' #adapt this path if you saved your file elsewhere
You can also use the file directly in your form view (in your case new.html.twig):
{% use "AppBundle:Form:prototype_layout.html.twig" %}

To bring those content in Inline, and as discussed in chat.
Here is one of the solution by using pure CSS.
Image reference for your required output:
As you have given the code in https://codeshare.io/GbQPkA , we can achieve the output by CSS.
Just add the below given CSS in your stylesheet, It may works fine.
CSS
div[id*="mybundle_application_responsables_"]{
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
div[id*="mybundle_application_responsables_"] > .form-group {
flex-grow: 1;
width: 100%;
padding: 10px;
}
Hope this may help you.If you prefer I will share the code in snippet with the updated CSS for demo.
Thanks.

Related

Symfony 3.4, MoneyType, and Bootstrap

I'm having a bit of difficulty styling a MoneyType form input the way I want. I want to use Bootstrap's input-group-addon class to prepend a dollar sign to the field, but what's actually happening is that two dollar signs are being rendered... one automatically generated by the MoneyType, and one that I manually wrote in my template:
Template:
<div class="form-group">
{{ form_label(form.canonicalPrice) }}
{{ form_errors(form.canonicalPrice) }}
<div class="input-group">
<span class="input-group-addon">$</span>
{{ form_widget(form.canonicalPrice, { 'attr': {'id': 'price', 'class': 'form-control'} }) }}
</div>
</div>
MoneyType definition in my form type class:
->add('canonicalPrice', MoneyType::class, array('label' => 'Price', 'currency' => 'USD'))
Screenshot:
So, is there a way for me to either:
Hide the automatic currency label that Symfony adds with a MoneyType field? Not specifying any currency defaults to a Euro rather than dollar, which isn't helpful.
Style the automatic label the way I want?
Note: I'm not using Symfony's Bootstrap form themes because I like having complete control over my templates. The fact that MoneyType fields default to showing a currency is very annoying.
Style the automatic label :
The dollar sign that is prepend to the input is from the money_pattern attribute of MoneyType
So as the doc on MoneyType says you can :
Modify in it the view :
{{ form_row(form.value, { 'money_pattern': '{{ widget }} $' }) }}
or like this
{% block money_widget %}
{%- set type = type|default('number') -%}
{{ parent() }}
{% endblock %}
Modify it in the builder :
Use money_pattern in the FormType and set the pattern you want.

Symfony/TWIG: Unable to set custom theme for form with dynamic ID (createNamedBuilder)

I wanted to replace Symfony's default radio button widget with Bootstrap's Radio Button Group. I was trying to achieve that by setting a custom theme for an individual field in Symfony's form created with createNamedBuilder function. I failed, because custom field themes require the ID of the field which is dynamic in my case. I know that having variables in theme names is impossible in TWIG, but maybe there is an alternative approach that I could use to resolve my issue.
CODE
I have a Controller which creates multiple instances of one form type in a loop. The name of the form is dynamically created by concatenating requestform_ and the ID of the form:
public function listAction(Request $request)
{
$entityManager = $this->getDoctrine()->getManager();
$requestForms = $entityManager->getRepository('AppBundle:RequestForm')->findBy(array(), array('id' => 'ASC'));
$forms = array();
foreach ($requestForms as $requestForm) {
$formBuilder = $this->get('form.factory')->createNamedBuilder('requestform_'.$requestForm->getId(), RequestFormType::class, $requestForm);
$form = $formBuilder->getForm()->handleRequest($request);
$forms[] = $form->createView();
}
return $this->render('form/index.html.twig', array('forms' => $forms));
}
buildForm function of the RequestFormType prepares radio button widget and looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('status', EntityType::class, array(
'class' => 'AppBundle:Status',
'choice_label' => 'name',
'expanded' => true,
'multiple' => false
));
}
The Controller renders the following template:
{% block content %}
{% for form in forms %}
{{ form_start(form) }}
{{ form_widget(form.status) }}
{{ form_end(form) }}
{% endfor %}
{% endblock %}
Which is themed to show Bootstrap's button group selector instead of radio buttons:
{% block _requestform_1_status_widget %}
<div id={{ form.vars.id }} class="btn-group" data-toggle="buttons">
{% for status in form.children %}
<label for={{ status.vars.id }} class="btn btn-primary required custom-button-radio">
<input type="radio" id={{ status.vars.id }} name={{ status.vars.full_name }} required="required" value={{ status.vars.value }}>
{{ status.vars.label }}
</label>
{% endfor %}
</div>
{% endblock _requestform_1_status_widget %}
As you may have noticed this will work only for requestForm ID 1 and will not work for other ID values due to TWIG not allowing variables in block names, i.e. {% block _requestform_VARIABLE_status_widget %}.
Any help much appreciated! Thanks
My gut feeling is that you might want to have only one form that includes a CollectionType field with entry_type = RequestFormType::class, and then in the twig, apply a form to the "outer" form as a whole. You should still be able to manage things the way you wanted.

Using TBBC Money/Currency Bundle for Symfony/Bootstrap Forms

I don't think this question has been raised looking at the history and on google...
I'm discovering symfony2 for a personal project and I'm not sure to take the problem the right way when it comes to implementing a form with TBBC Money/Currency bundle (found on packagist).
I have a "Expense" class containing a price field (type "Money") and for which I want to create a form.
In my "ExpenseType" file I have the following:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('title', TextType::class)
->add('actualDate', DateType::class, array('widget' => 'single_text'))
->add('comment', TextareaType::class)
->add('price', MoneyType::class, array())
->add('user', 'entity', array(
'class' => 'VPAccountsBundle:User',
'property' => 'username'))
;
}
On my twig file displaying the form I have:
<div class="row">
<div class="col-lg-3 col-md-3 control-label">
{{ form_label(form.price, "Amount" ) }}
</div>
<div class="col-lg-4 col-md-4">
{{ form_widget(form.price , { 'attr':{ 'class':'form-control', 'placeholder':'Amount' } } ) }}
</div>
{{ form_errors(form.price) }}
</div>
What I get is this.
What I would like to get is a bootstrap input with dropdown button (see mockup). but I really don't know how to proceed.
Has anyone faced this kind of situation? Any help would be appreciated! :)
Thanks a lot.
Theme a widget or form in Symfony is well documented here and IMO you should go through Inside the same Template as the Form. You could extends from form_div_layout.html.twig and then on your custom template overrides how money_widget is render by changing the following block:
{%- block money_widget -%}
{{ money_pattern|replace({ '{{ widget }}': block('form_widget_simple') })|raw }}
{%- endblock money_widget -%}
Hope it helps
Replying to myself...
As correctly indicated in the bundle documentation I had to configure my config.yml to declare the new type to twig:
# config.yml
twig:
form:
resources:
- 'TbbcMoneyBundle:Form:fields.html.twig'
The form elements are rendered much better and should be easily customizable.

Extend the ChoiceType to get a new option in a form

In a form I need to associate each radio button of an input to a different image.
I would call the construction of this form this way:
$builder = $this->formFactory->createBuilder();
$builder->add('input_name', 'my_choice', array(
'data' => 'n',
'choices' => array('c1' => 'choice1', 'c2' => 'choice2'),
'required' => true,
'expanded' => true,
'multiple' => false,
'images' => array('choice1.jpg', 'choice2.jpg')));
$form = $builder->getForm();
Meaning the radio_button choice1 will be associated to choice1.jpg and the radio_button choice2 will be associated to choice2.jpg.
In plaint HTML using Bootstrap I want this result
<form method="post" action="{{ path('my_path')}}" name="myform">
<div class="btn-group btn-group-justified" data-toggle="buttons">
<label id="btn-choice1" class="btn active">
<input type="radio" name="is_choice" id="c1" value="Choice1" checked>
<img id="choice1_img" src="{{ asset('bundles/myBundle/icons/choice1.png') }}" alt="Choice 1">
</label>
<label id="btn-choice2" class="btn">
<input type="radio" name="is_choice" id="c2" value="Choice2"><img id="choice2_img" src="{{ asset('bundles/myBundle/icons/choice2.png') }}" alt="Choice 2">
</label>
</div>
<button class="btn" type="submit" name="submit">Submit</button>
</form>
I read the official Symfony doc related to this: http://symfony.com/fr/doc/2.3/cookbook/form/create_custom_field_type.html
but the example is very poor.
I need to describe to Symfony where to look for this new option but cannot figure out how to achieve this. I read hundreds of examples on the net but nothing explaining the logic behind how to do this.
So far My new Type is like this below:
<?php
namespace myProject\Bundle\MyBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
class MyChoiceType extends AbstractType
{
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars = array_replace($view->vars, array(
'images' => $options['images']
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'images' => array()
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'my_choice';
}
}
Would you be able to explain me how to describe this new Type to Symfony (what methods to call with what parameters).
I breaked the code for hours but Symfony is making so muchfor a simple form creation that I lost the path. FormRegistry, FormType, OptionsResolverInterface, BaseType, FormFactory and so many others classes are involved that it makes it difficult to get the global picture.
Otherwise if one can point me to an architecture document explaining the logical behind this it could also be fine.
Found the solution.
I needed also to access the images new option through a Form theme redefinition. That was the difficult part since I didn't know how and what I could access from the form theme template.
Here is how I succeeded in doing it:
{% block my_choice_widget %}
{% spaceless %}
{% if expanded %}
<ul {{ block('widget_container_attributes') }}>
{% for child in form %}
<li>
{{ form_widget(child) }}
{{ form_label(child) }}
{% if images is defined %} {{images[loop.index-1]}} {% endif %}
</li>
{% endfor %}
</ul>
{% else %}
{# just let the choice widget render the select tag #}
{{ block('choice_widget') }}
{% endif %}
{% endspaceless %}
{% endblock %}
The part I added to the Symfony form them template is:
{% if images is defined %} {{images[loop.index-1]}} {% endif %}
That displays the image name in front of the proper radio button.
That should be refined to make sure there are enough images to populate all the loops.
A simple twig test on images count compared to loop.last element will make it.

Symfony 2: pass custom variable to twig form template

In just started at symfony and Im trying to do something very simple (seems simple to me) using the form templating system, but I cant find a way to do it.
My goal is to render a "back" button besides the submit button. I know how to set the form template and how to override the submit_widget, but the problem is: the back button URL must be defined at the template which is calling the form, so I need to pass this as a variable to the submit_widget somehow, and I cant find a way to do it.
Ideally, it would work like this:
Template:
{% form_theme form 'TutsAdminBundle:Form:bootstrap-horizontal.html.twig' %}
{% block content %}
<h1>User creation</h1>
{{ form(form, { 'attr': {'role': 'form', 'class': 'form-horizontal'}, 'back': path('list_user') }) }}
{% endblock %}
And then, at the form template
{% block submit_widget %}
<div class="col-sm-6 col-sm-offset-1">
<div class="pull-right">
<a class="btn btn-warning" href="{{ back }}"> Back</a>
<button type="submit" class="btn btn-success">Save</button>
</div>
</div>
{% endblock submit_widget %}
But I just cant find a way to access my "back" variable inside the submit_widget block.
How can I achieve that?
UPDATE:
I managed to do what I wanted by:
1- creating a custom field following Chausser advice bellow:
class SaveButtonType extends SubmitType
{
public function __construct($router)
{
$this->_router = $router;
}//constructor
protected $_router;
/* (non-PHPdoc)
* #see \Symfony\Component\Form\AbstractType::buildForm()
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
}//buildForm
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$href = function(Options $options, $value){
return array('href'=>$this->_router->generate($options['route'], $options['params']), 'class'=>'btm btn-warning');
};
$resolver->setDefaults(array('mapped'=>false))->setRequired(array('route', 'params'));
$resolver->setNormalizers(array('attr'=>$href));
}//setDefaultOptions
public function getName()
{
return 'save_button';
}//getName
}//SaveButtonType
Ive made it extends the SubmitType otherwise it would be rendered inside a form_row.
services.yml
tuts_admin.form.field.type.save_button:
class: Tuts\AdminBundle\Form\Field\SaveButtonType
arguments: ["#router"]
tags:
- {name: "form.type", alias: "save_button"}
2- then I defined its template (on that same file Im using to override the form theme
{% block save_button_widget %}
<div class="col-sm-6 col-sm-offset-1">
<div class="pull-right marginL25">
<button type="submit" class="btn btn-success">Save</button>
</div>
<div class="pull-right">
Back
</div>
</div>
{% endblock %}
This type render the submit and the back button at once.
3- finally, since I could not find a way to prevent the rendering of another submit button, I overrided the submit_widget:
{% block submit_widget %}
{% endblock submit_widget %}
A lot more complicated than I was expecting, but it does works.
Thank you Chausser for all your help.
Personally I would write a Custom Field Type and make if for back buttons. Something that will accept a route name and route params.
//Acme/DemoBundle/Form/Type/BackButtonType
<?php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\OptionsResolver\Options;
class BackButtonType extends AbstractType
{
protected $router;
public function __construct($router)
{
$this->router = $router;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$attr = function(Options $options, $value){
return array(
'href'=>$this->_router->generate($options['route'], $options['params']),
'class'=>'btn btn-warning'
);
};
$resolver
->setDefaults(array(
'attr' => $attr,
'mapped' => false
'params' => array(),
))
->setRequired(array(
'route',
))
->setNormalizers(array(
'attr'=>$attr
));
}
public function getName()
{
return 'back_button';
}
}
Then you need to register this as a new form type:
parameters:
acme_demo.form.type.back_button.class: Acme\DemoBundle\Form\Type\BackButtonType
services:
acme_demo.form.type.back_button:
class: %acme_demo.form.type.back_button.class%
arguments: ["#router"]
tags:
- { name: "form.type", alias: "back_button" }
Now you can use this in your normal forms. Last step is to create the twig block to render this form.
//Acme/DemoBundle/Resources/Twig/fields.html.twig
{% block back_button_widget %}
<a {{block('widget_attributes')}}>{{block('form_label')}}</a>
{% endblock %}
Then you need to add this file to the form resources for twig:
//app/config/config.yml
twig:
debug: %kernel.debug%
strict_variables: %kernel.debug%
form:
resources:
- 'AcmeDemoBunlde:Twig:fields.html.twig'
After you have done that you need to clear your caches:
php app/console cache:clear --env=dev
php app/console cache:clear --env=prod
Now how to use this:
//In your Form
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// Add other fields
->add('back', 'back_button',array('route'=>'my_custom_route_name','params'=>array('user_id'=>$this->getUser()->getUserId())))
->add('submit','submit',array('attr'=>array('class'=>'btn btn-primary')));
}
This should work. Hasnt been fully tested but if you have any issues with it let me know.
I believe that you don't need to pass variable, but only make it available (known) to Twig, since form rendering (as opposed to to extending and including other templates) is just block rendering. That is, those variables are in the same scope.
So:
Parent template
{% set back = "MyFooValue" %}
Form template:
<div class="{{ back }}">Some content</div>
Also, I like defining default as fallback in case I forget to (or do not at all) define variable:
<div class="{{ back|default("") }}">Some content</div>
Hope this helps...

Categories