I'm building a form which have default value in some fields:
$builder->add('field', 'text', array('data' => 'Default value');
Then render this form field in Twig like:
{{ form_widget(form.field) }}
It's worked OK, but I don't want 'Default value' set in input tag rendered in HTML, because I only want this default value set internal that End-user doesn't aware about this value. Is there any built-in method in Symfony2 to handle it or I have to make some custom code?
You could modify your entity in order to do this:
class MyEntity{
const DEFAULT_FOO = "Default value";
// ...
private $foo;
// ...
public function setFoo($foo){
if ( $foo === null ){
$foo = self::DEFAULT_FOO;
}
$this->foo = $foo;
}
// ...
}
And then make sure that you set by_reference in order to ensure setter is being invoked each time:
$builder->add('field', 'text', array(
'by_reference' => true
));
As far i understand
try to add required false and handle it in your controller action.
$builder->add('field', 'text', array('required' => false));
Related
It appears to be possible to simply put a string / template to the list view, but is the same possible to the edit view of an entity in Sontata Admin 4?
I found https://docs.sonata-project.org/projects/SonataAdminBundle/en/4.x/reference/templates/#configuring-templates, but it does not give access to the form itself. This is the include I found in base_edit.html.twig:
{% block form %}
{{ block('parentForm') }}
{% endblock %}
I would like to achieve this thou:
How would this be possible?
Ok, it looks like the help attribute of an input can be filled with markup:
src/Admin/PageAdmin.php:
class PageAdmin extends AbstractAdmin {
// ...
protected function configureFormFields(FormMapper $formMapper) : void
{
/** #var Page $page */
$page = $this->getSubject();
$adminHint = '';
if ($page) {
$adminHint = implode(', ',array_map(function (User $admin) {
return "<strong><a href='/admin/sso/user/{$admin->getId()}/show' target='_blank'>{$admin->getUsername()}</a></strong>";
}, $page->getExplicitAdmins()->toArray()));
}
$formMapper
->add('title', TextType::class)
// ....
->add('admins', ModelAutocompleteType::class, [
'multiple' => true,
'required' => false,
'property' => ['username', 'id'],
'btn_add' => false,
'help' => $adminHint ? "$adminHint have already explicit edit rights (might be inherited from parents). Admins entered here will also get access to all subpages." : "Admins entered here will also get access to all subpages.", // <-- some dynamic content
'help_html' => true, // <-- to enable html rendering
])
}
See also https://symfony.com/bundles/SonataAdminBundle/3.x/cookbook/recipe_image_previews.html.
What bugs me a bit is, that this is rather messy. Rendering a template here would make it much cleaner.
I have a DateType field on a form that is set to disabled=true . When a specific checkbox is checked by the user some jquery picks it up and removes the disabled property from the html but the value still doesn't get submitted by the form.
Just wondering if there is a better way to accomplish this in symfony? The date field needs to be disabled for the user unless they check the checkbox. The field is added like this:
$builder->add('overrideDate',DateType::class,[
'required'=>false,
'label'=>'Override Date',
'disabled'=>true
]);
I did try changing it via a SUBMIT event so that symfony would recognise the field as enabled since the html doesn't have the disabled anymore the field should still be getting submitted so i'm assuming that it's symfony ignoring it because of the original disabled =true ?
Here is part of the builder, i am trying to change the date field when it's submitted to disabled = false but the overrideDate is still null
$builder->add('overrideDates',CheckboxType::class,[
'label'=>'Override Dates',
'required'=>false
]);
$builder->add('overrideDate',DateType::class,[
'required'=>false,
'label'=>'Override Date',
'disabled'=>true
]);
$builder->addEventListener(FormEvents::SUBMIT,function(FormEvent $event){
$form = $event->getForm();
$data = $form->getData();
if($data->isOverridingDates()){
$form->add('overrideDate',DateType::class,[
'required'=>false,
'label'=>'Override Date',
'disabled'=>false
]);
}
});
SOLUTION
Ok this feels a bit clunky, but it works for what i want to do:
// event listener to enable/disable the overrideDate field if overrideDates === true
$builder->addEventListener(FormEvents::PRE_SUBMIT,function(FormEvent $event){
// get form and data
$form = $event->getForm();
$data = $event->getData();
// add the overridedDate field back into the form
$form->add('overrideDate',DateType::class,[
'required'=>false,
'label'=>'Override Date',
'disabled'=>((bool)$data['overrideDates'] === true ? false : true)
]);
});
You should take a look here : https://symfony.com/doc/current/form/dynamic_form_modification.html
It's the good way to do what you wan to do ! You should use FormEvents::PRE_SUBMIT instead of FormEvents::SUBMIT
// event listener to enable/disable the overrideDate field if overrideDates === true
$builder->addEventListener(FormEvents::PRE_SUBMIT,function(FormEvent $event){
// get form and data
$form = $event->getForm();
$data = $event->getData();
// add the overridedDate field back into the form
$form->add('overrideDate',DateType::class,[
'required'=>false,
'label'=>'Override Date',
'disabled'=>((bool)$data['overrideDates'] === true ? false : true)
]);
});
I have a simpler solution, you have to use the HTML attribute "readOnly". This attribute enables the same function as "disabled" but when symfony get your form data, it takes the "readonly" input into account.
You can add your input in your form builder like this :
$builder->add('overrideDate',DateType::class,[
'required'=>false,
'label'=>'Override Date',
'attr' => ['readonly' => true],
]);
CollectionType field has special 'prototype' variable when 'allow_add' option is set to true. This variable can be used to render prototype html like this:
data-prototype="{{ form_widget(form.collectionfieldname.vars.prototype)|e('html_attr') }}"
It looks like 'prototype' is simply an instance of collection children FormView built with partial data (e.g. name is set to "__name__" while most other vars are left blank).
Where all this magic happens? Is it possible to modify what data is passed to prototype view while building form? For example, I would like to change default value of "value" variable from blank to "__val__" (outside of Twig template).
Answer to own question - values defined in "entry_options" setting are used to build prototype. It is possible to pass these values to form builder like this:
$builder
->add('email', CollectionType::class, array(
...
'entry_options' => array(
'someoption' => 'somevalue',
),
...
))
If this is not enough, default behaviour can be modified by overriding "buildForm" method in "CollectionType" class which is responsible for collecting options and building prototype:
class CollectionType extends AbstractType
{
...
public function buildForm(FormBuilderInterface $builder, array $options)
{
if ($options['allow_add'] && $options['prototype']) {
$prototypeOptions = array_replace(array(
'required' => $options['required'],
'label' => $options['prototype_name'].'label__',
), $options['entry_options']);
if (null !== $options['prototype_data']) {
$prototypeOptions['data'] = $options['prototype_data'];
}
$prototype = $builder->create($options['prototype_name'], $options['entry_type'], $prototypeOptions);
$builder->setAttribute('prototype', $prototype->getForm());
}
...
}
...
}
In a Form say I have a builder option like this:
->add('choice', ChoiceType::class, [
'choices' => [
'Cheese' => 'cheese',
'Plain' => 'plain
]
])
And let's say we are editing this option, in the database they've already selected plain. With twig we can write the widget like this:
{{ form_widget(BurgerForm.choice, {
'value': burger.type
}) }}
This will make the value in the database the pre-selected value for the select. But if you do the same thing with EntityType:
->add('choice', EntityType::class, [
'class' => 'AppBundle:BurgersTypes',
'choice_label' => 'type'
])
And you use the same twig it doesn't pre-select the option from the database. How can I get the value from the database to show as the pre-selected value of the widget?
Pre-selecting a value for this form means setting a value on the underlying data. In your case, the controller ought to look something like:
// src/AppBundle/Controller/MyController.php
namespace AppBundle\Controller\MyController;
use AppBundle\Entity\Order;
use AppBundle\Entity\BurgersTypes;
use AppBundle\Form\Type\FormType;
use Symfony\Component\HttpFoundation\Request;
public function formAction(Request $request)
{
$obj = new Order();
$defaultBurgerChoice = new BurgersTypes('cheese');
$ob->setChoice($defaultBurgerChoice);
$form = $this->create(FormType::class, $obj);
$form->handleRequest($request);
...
// Now, if the form needs to render a value for `choice`,
// it will have the value of BurgerForm.choice determined
// intentionally - by your default, or overridden and
// handled in the request!
return [
'BurgerForm' => $form->createView()
]
}
I'm in a situation where I want to be able to dynamically set the required=true/false option or the array(...other stuff..., 'class' => ' hidden') of a form field.
The context is the following: there is a "Project" entity with some fields. When I create other entities with forms I want to check some attributes of the Project entity and make decisions on the visibility/requiredness of certain fields of the entity I'm creating.
For example if a Project is with attribute "timePressure=high" then a field of a given entity is not required (but is visible). If there are other conditions it becomes invisible etc...
So basically I was hoping to call a function inside each ->add() of the form builder to spit out the relevant portions (e.g. that function would return a string with "required=true" or the other related to the hidden class). The thing is that the function should take as arguments: the projectID (ok, this can be passed as options of the form builder), the class and the field we are talking about to decide. I was envisioning something like:
->add('background', 'textarea', array('attr' => array('rows' => 4, functionToDecideIfInvisible($projectId)), functionToDecideRequiredness($projectId)))
The two function would return the string 'class' => ' hidden' and required=true (or false)
I'd like to avoid to having to specify the field name (in this case background) to avoid code repetition.
Can this be done?
Other suggestions on how to solve the thing?
Thank you!
SN
What you need are Form Events: http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#cookbook-form-events-underlying-data
They allow you to modify your form based on your data.
You create you project form in the controller:
$form = $this->createForm(new ProjectType(), $project, array(
'action' => $this->generateUrl('project.edit'),
'method' => 'POST',
));
Then you add the FormEvents::PRE_SET_DATA listener:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$project = $event->getData();
$form = $event->getForm();
// check timePressure on the Project object
if ('high' === $project->timePressure) {
$form->add('timePressure', 'text', array(
'required' => false
);
}
});
}
I found a way to do it.
My add would be like:
->add('background', 'textarea', array_merge($this->vr->fieldReq(get_class($this),
'background', $projectID), array('attr' => array_merge(array('rows' => 4, ),
$this->vr->fieldCssClass(get_class($this), 'background', $projectID) ) )))
To do that I had to define the form as service, plus I created another class as service (the one which holds the two methods I need).
This is the class:
class FieldReqAndCssClass
{
public function fieldReq($parentEntity, $field, $projectID)
{
$required = array();
$required['required'] = false; //do stuff on the database instead of setting it to false hardcoded
return $required;
}
public function fieldCssClass($parentEntity, $field, $projectID)
{
$cssClass= array();
//do stuff on the database instead of setting the class as hidden hardcoded
//$cssClass['class'] = ' hidden';
$cssClass['class'] = '';
return $cssClass;
}
}
Of course in my form I had to:
public function __construct(\AppBundle\Util\FieldReqAndCssClass $fieldReqAndCssClass)
{
$this->vr = $fieldReqAndCssClass; // vr stands for visibility and requiredness
}
And these are the two services:
app_bundle.field_req_and_css_class:
class: AppBundle\Util\FieldReqAndCssClass
arguments: []
app_bundle.form.type.projectdetail:
class: AppBundle\Form\Type\ProjectDetailFormType
arguments: [#app_bundle.field_req_and_css_class]
tags:
- { name: form.type, alias: ProjectDetail }
Of course here in the first service I'll need to inject the entity manager and add it to the construct, and probably also in the form service, but the basic skeleton is working :)
I'm a happy man :)
EDIT
The only problem of the above is that it makes hidden the widget but not the label. To fix it:
->add('background', 'textarea', array_merge($vr->fieldReq($myClass,
'background', $project), array('label_attr' => $vr->fieldCssClass($myClass,
'background', $project),'attr' => array_merge(array('rows' => 4, ),
$vr->fieldCssClass($myClass, 'background', $project) ) )))
Obviously before I have to declare:
$myClass = get_class($this);
$vr = $this->vr;