Symfony2 SontataAdminBundle change filter label - symfony

How to change filter label on AdminBundle? Example in documentations doesn't work
->add('tags', null, array('label' => 'les tags')

You're changing the label used with filters. Use configureListFields() method instead of configureDatagridFilters().
Edit: Also, you should use name instead of label.
/**
* #param Sonata\AdminBundle\Datagrid\ListMapper $listMapper
*
* #return null
*/
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('title')
->add('isPublished', null, array('name' => 'Is published?'))
;
}

Now, in the last version of Sonata, you should use label instead of name.
That seems more logic.

Related

Symfony 4. How do I extend the DateType to be HiddenType insted

I have created a data transformation string to date, but I really wanted to try and extend the DateType as it has all the logic for handling dates, has anyone done this before or should I just use a DataTransformer (which is what I have implemented)?
I tried doing the following:
Class HiddenDateType extends DateType {
public function getParent() {
return HiddenType::class;
}
}
But this didn't work, when I looked into the DateType Class it was a little more complicated than I was expecting and didn't know where to hook into the returned type.
I found a few solutions but most of them where pretty hacky, ie; Hiding the entire field via CSS, or changing the type inside the twig template.
This is my transformer, it's VERY redemantry, its frustrating that there is code RIGHT inside the DateType but my skills are lacking :(
class StringToDateTransformer implements DataTransformerInterface
{
/**
* #param \DateTime|null $value
* #return string
*/
public function transform($value)
{
if(null === $value) {
return '';
}
return $value->format('Y-m-d');
}
/**
* #param string $value
* #return \DateTime
*/
public function reverseTransform($value)
{
try {
return new \DateTime($value);
} catch (\Exception $e) {
throw new TransformationFailedException(sprintf(
'Invalid Format for %s format yyyy-mm-dd',
$value
));
}
}
}
On the actual rendered page there is a single div which has a daterangepicker javascript event (on click) is attached to it, this event is triggered when clicked which allows the user to select a start and end date.
The javascript will then populate two hidden DateType fields (startDate, endDate) within the form.
This is the div that the daterangepicker picker is attached to.
<div id="selectedRange">
<i class="fa fa-calendar"></i>
<span></span> <i class="fa fa-caret-down"></i>
</div>
You should be able to do this in your formType class buildForm() method
// ./src/form/yourType.php
$builder->add('hidden_date_field_name', DateType::class, [
'attr' => [
'hidden' => 'hidden',
'disabled' => 'disabled',
],
]);
You should be fine using a DateType or DateTimeType if you do not plan to use something like bootstrap timepicker or any other JS modification of your input.
However, if that's the case and you want to make a better looking date input, the simple way to do it is to use it as a string like so :
$builder->add('date', TextType::class, array(
'required' => true,
'label'=>'Date',
'attr' => array(
'class' => 'datetimepicker',
'data-provide' => 'datepicker',
'data-format' => 'dd-mm-yyyy HH:ii'
),
));
And be sure to add your ModelTransformer to the input in your FormType like so:
$builder->get('date')
->addModelTransformer(new StringToDateTransformer());
Note that you can specify the date format for your js component in the input attribute.

Sonata Admin Bundle: Editable suggest field in list view

In the list field you can make a field editable by setting the attribute "editable" to "true" in the configureListFields action. Is it possible (with onboard sonata admin tools) to make a field editable that contains multiple values as in a one-to-many relation?
Example:
I have a list of pupils listed in the list view. Every pupil has multiple classes listet in the classes column of the pupils list view. Via click on the classes I want a popover open (like it works with a normale string) with a suggest field like you can have it in the edit view.
Using the properties like in the configFormFields action doesn't work:
$listMapper->add(
'classes',null, array(
'editable' => true,
'type' => 'sonata_type_model_autocomplete',
'multiple' => true,
'property' => 'name'
)
);
That snippet is written inside the PupilsAdmin class in the configureListFields action.
Is it possible or do I have to create a custom template?
The documentation doesn't point me in the right direction: https://sonata-project.org/bundles/admin/2-2/doc/reference/field_types.html
If i understand you right, you want to edit a one-to-many relation inline in a list view of sonata. As far as i know, thats only possible for simple types like text, int or choices and so on. They point it out on no 18. in your link
Theses types accept an editable parameter to edit the value from within the list action. This is currently limited to scalar types (text, integer, url...).
So related objects can not be in that list, merely their scalar properties. For all other things you have to write your own template ...
I don't know what you want to achieve with this suggested list, but for me it makes no sense to edit a one-to-many property in the list view like its done in the edit view.
You just need to create new type. Something like "entity"
'header_class' => 'col-lg-1',
'class' => Employee::class,
'editable' => true,
])
Next step is to override fixFieldDescription method in listBuilder and handle it
class EntityListBuilder extends ListBuilder
{
/**
* #var Registry
*/
private $doctrine;
/**
* #param AdminInterface $admin
* #param FieldDescriptionInterface $fieldDescription
*/
public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInterface $fieldDescription)
{
parent::fixFieldDescription($admin, $fieldDescription);
if ($fieldDescription->getType() === 'entity') {
$class = $fieldDescription->getOption('class');
if (!$class) {
throw new RuntimeException("Type entity must contain 'class' argument");
}
$objects = $this->doctrine->getRepository($class)->findAll();
$choices = [];
foreach ($objects as $object) {
$choices[$object->getId()] = $object->__toString();
}
$fieldDescription->setOption('choices', $choices);
$fieldDescription->setType('choice');
}
}
/**
* #param Registry $doctrine
*/
public function setDoctrine(Registry $doctrine)
{
$this->doctrine = $doctrine;
}
/**
* #param string $type
*
* #return string
*/
private function getTemplate($type)
{
return $this->templates[$type] ?? '';
}
Now, you have to override template for your "entity" type
{% extends '#SonataAdmin/CRUD/list_choice.html.twig' %}
{% set value = admin.id(value) %}
It's need to set already chosen value to select box
Okay, last thing is to add our type to xeditable types of Twig
Add it to OverrideServiceCompilerPass :
$definition = $container->getParameter('sonata.admin.twig.extension.x_editable_type_mapping');
$definition['entity'] = 'select';
$container->setParameter('sonata.admin.twig.extension.x_editable_type_mapping', $definition);
And the last one just match your type with template
templates:
types:
list:
...
entity: AppBundle:CRUD:list_entity.html.twig
Now you ready to edit it inline :)

Use ManyToOne relation as multiple choice in Symfony Form (with Doctrine)

Say, there is a simple many-to-one relations: Model has hair type and eyes color:
/**
* #ORM\Entity
*/
class Model
{
/**
* #ORM\ManyToOne(targetEntity="Hair")
* #ORM\JoinColumn(name="hair_id", referencedColumnName="id")
*/
protected $hair;
/**
* #ORM\ManyToOne(targetEntity="Eyes")
* #ORM\JoinColumn(name="eyes_id", referencedColumnName="id")
*/
protected $eyes;
For example, hair could be: blonde, brown, black, red;
The eyes: blue, green, gray, brown.
In the search form I want user to be able to select multiple hair types and eyes at once. I use 'multiple' property:
class ModelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('hair', EntityType::class, [
'class' => 'AppBundle:Hair',
'choice_label' => 'name',
'multiple' => true,
])
->add('eyes', EntityType::class, [
'class' => 'AppBundle:Eyes',
'choice_label' => 'name',
'multiple' => true,
])
->getForm();
;
}
The form renders like this:
Of course, when selecting multiple values and submitting it causes an error:
Expected argument of type "AppBundle\Entity\Hair", "Doctrine\Common\Collections\ArrayCollection" given
Perhaps, this is not for using in such a case?
Are any best practices for building search forms in Symfony? Didn't find any...
The problem is not the form but your mapping.
I assume your form is binded with the Model Entity.
That's why the ManyToOne relation accepts only one related entity.
Solution :
Don't bind your form to the Model Entity, just use a form without class :
http://symfony.com/doc/current/form/without_class.html
Model can have many hair color and many eyes color ?
In this case you have to use many-to-many relation instead of many-to-one
If not, you have to remove 'multiple' => true, in you ModelType

Symfony 2.8 - how to create "terms & conditions" check-box which isn't mapped to the underlying model

I'm using Symfony 2.8 and I'm trying to create a registration form containing a "terms & conditions" check-box which isn't mapped to the underlying data model.
I've followed this cookbook article:
How to Implement a Simple Registration Form
Everything in the form validation works, except for the the "terms & conditions" check-box. After submitting the form, the check-box validation doesn't get triggered.
This is my code:
namespace Likestripe\AdminBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
use Symfony\Component\Validator\Constraints\IsFalse;
use Symfony\Component\Validator\Constraints\IsTrue;
class RegistrationFormType extends AbstractType { //BaseType
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
// call parent constructor
//parent::buildForm($builder, $options);
// add your custom fields
$builder->add('salutation', ChoiceType::class, array('choices' => array('Herr' => 0, 'Frau' => 1), 'choices_as_values' => true));
$builder->add('firstname', TextType::class, array());
$builder->add('lastname', TextType::class, array());
$builder->remove('username');
$builder->add('company', new CompanyFormType());
$builder->add('conditions', CheckboxType::class, array('mapped' => false, 'constraints' => new IsTrue(array("message" => "Bitte akzeptieren Sie noch die AGB und Nutzungsbedingungen."))));
$builder->add('submit', SubmitType::class, array('label' => 'Registrieren'));
} // end function
public function getParent() {
return 'FOS\UserBundle\Form\Type\RegistrationFormType';
// Or for Symfony < 2.8
// return 'fos_user_registration';
}
/**
* function deprecated since 2.8 https://github.com/symfony/symfony/blob/2.8/UPGRADE-2.8.md#form
*
* #return string
*/
public function getName() {
//return 'likestripe_user_registration';
return $this->getBlockPrefix();
} // end function
public function getBlockPrefix() {
return 'likestripe_user_registration';
}
} // end class
I can't see any difference between my code and the code demonstrated in the cookbook article.
Screen capture of my Symfony debug console:
UPDATE:
As Kamil proposed, I've checked if the 'conditions' check-box form parameter gets posted after submitting the form.
The param gets posted als long as the check-box is checked, but if not, the "conditions" form parameter doesn't get posted at all... this behavior reminds me of this case.
I'm still wondering why the official Symfony documentation proposes a isTrue Validator which doesn't seem to be the solution to check for an unchecked check-box, any suggestions how to fix this?
Checkbox checked:
Checkbox unchecked:
Thanks in advance for your help
ninsky
If you let the 'required' of your CheckboxType to true, the constraints isTrue is not useful because the checkbox will always be true !
If change change that to :
$builder->add('conditions', CheckboxType::class, array('mapped' => false, 'required' => false, 'constraints' => new IsTrue(array("message" => "Bitte akzeptieren Sie noch die AGB und Nutzungsbedingungen."))));
With this configuration you can submit the form and if the box is not checked the constraints will send your message.
Hope this help ...
As a workaround, you can add a form listener to make sure you submit the value. Add following to the bottom of your buildForm() function:
$builder->addEventListener(FormEvents::PRE_BIND, function (FormEvent $event) {
$data = $event->getData();
if (!isset($data['conditions'])) {
$data['conditions'] = false;
}
$event->setData($data)
});
As another workaround, you can perform a check in controller and show flash message if request headers do not contain 'conditions'.
Symfony docs mention it:
$builder
->add('email', EmailType::class);
// ...
->add('termsAccepted', CheckboxType::class, array(
'mapped' => false,
'constraints' => new IsTrue(),
))
);
Well, I don't see why is not working for you so in the meantime I give you an alternative way :)
$builder->add('conditions', CheckboxType::class , array('mapped' => false, 'required' => true, 'attr'=>array('onchange'=>"try{setCustomValidity('')}catch(e){}",'oninvalid'=>"setCustomValidity('Bitte akzeptieren Sie noch die AGB und Nutzungsbedingungen.')")));
Hope this work for you.
I've run your code with everything but the CompanyFormType line and it is working as expected.
Could you remove this CompanyFormType line and give it a try ?
You have to set the validation group Overriding Default FOSUserBundle Forms
By default, the Registration validation group is used when validating
a new user registration. Unless you have overridden this value in the
configuration, make sure you add the validation group named
Registration to your name property.
How to do that you can find out here: Overriding Default FOSUserBundle Validation

Sonata admin - "order by" field in related table

I have a Product admin class. The Product entity has a many-to-one relationship with a Category entity, i.e. a product is associated with a category.
In the admin "list" page for products, I need to sort by the category name (alphabetically) that each product is associated with.
Setting the default field to sort by is easy if the field is on the entity itself (see Sonata admin bundle order for how to do this). But I cannot figure out how to sort by a field in a related table.
Any help is appreciated.
It seems a workaround, but it works. You have to add a join overriding createQuery() method, than assign a default sortBy overriding $datagridValues:
<?php
use Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery;
class ExpenseAdmin extends Admin
{
protected $datagridValues = array(
'_page' => 1,
'_sort_order' => 'ASC', // sort direction
'_sort_by' => 'c.name' // field name
);
/**
* #return \Sonata\AdminBundle\Datagrid\ProxyQueryInterface
*/
public function createQuery($context = 'list')
{
$query = parent::createQuery($context);
return new ProxyQuery($query
->join(sprintf('%s.category', $query->getRootAlias()), 'c'));
}
}
Asume name is the property of entity Category by wich you want to sort. You may do this in you ProductAdmin.php
protected function configureListFields(ListMapper $listMapper)
{
$listMapper->add('category.name', null, array(
'sortable' => true,
));
...
}
This way you leverage the ordering links in the header of the list, generated by Sonata.
Edit
If you would also like to have a link on the category name in products list to quickly edit the Category entity, assuming you have created a CategoryAdmin class, you should write your code like this:
protected function configureListFields(ListMapper $listMapper)
{
$listMapper->add('category', null, array(
'sortable' => 'category.name',
));
...
}
And in your Category class you should implement the __toString() method like this:
public function __toString()
{
return $this->getName();
}

Resources