In Sonata Admin is it possible to set or precompile a value in a form taking it from the query string?
I have a custom page with a calendar and when I click a date on the calendar I would like to be redirected to the create page of an event where the date is already set from the query string. Something like:
http://localhost:8000/admin/app/event/create?date=2017-07-11
I solved it by using the request service and a field marked as 'mapped' => false on my form builder:
protected function configureFormFields(FormMapper $formMapper)
{
$request = $this->getRequest();
$dateParameter = $request->query->get('date');
$date = null;
if ($dateParameter) {
$date = \DateTime::createFromFormat('!Y-m-d', $dateParameter);
}
if (!$date) {
$formMapper
->add('date', DatePickerType::class)
->add('calendarDate', HiddenType::class, array(
'data' => null,
'mapped' => false,
))
;
} else {
$formMapper
->add('date', DatePickerType::class, array(
'data' => $date,
'disabled' => true,
))
->add('calendarDate', HiddenType::class, array(
'data' => $date->format('Y-m-d'),
'mapped' => false,
))
;
}
$formMapper
// other fields
->add('other_field', ModelType::class)
;
}
Then, on the following method I am updating the entity field:
public function prePersist($myEntity)
{
$this->preUpdate($myEntity);
}
public function preUpdate($myEntity)
{
$dateString = $this->getForm()->get('calendarDate')->getData();
if ($dateString) {
$date = \DateTime::createFromFormat('!Y-m-d', $dateString);
if ($date) {
$myEntity->setDate($date);
}
}
}
In this way I can use my form with or without the query string parameter date:
http://localhost:8000/admin/app/event/24/edit?date=2017-07-01
And in the HTML I have this:
<input type="hidden" id="s59664a9ea44ce_calendarDate" name="s59664a9ea44ce[calendarDate]" class=" form-control" value="2017-07-01" />
P.S. when someone gives a -1 it should be nice to explain why...
Related
We are using Symfony Forms for our API to validate request data. At the moment we are facing a problem with the CollectionType which is converting the supplied value null to an empty array [].
As it is important for me to differentiate between the user suppling null or an empty array I would like to disable this behavior.
I already tried to set the 'empty_data' to null - unfortunately without success.
This is how the configuration of my field looks like:
$builder->add(
'subjects',
Type\CollectionType::class,
[
'entry_type' => Type\IntegerType::class,
'entry_options' => [
'label' => 'subjects',
'required' => true,
'empty_data' => null,
],
'required' => false,
'allow_add' => true,
'empty_data' => null,
]
);
The form get's handled like this:
$data = $apiRequest->getData();
$form = $this->formFactory->create($formType, $data, ['csrf_protection' => false, 'allow_extra_fields' => true]);
$form->submit($data);
$formData = $form->getData();
The current behavior is:
Input $data => { 'subjects' => null }
Output $formData => { 'subjects' => [] }
My desired behavior would be:
Input $data => { 'subjects' => null }
Output $formData => { 'subjects' => null }
After several tries I finally found a solution by creating a From Type Extension in combination with a Data Transformer
By creating this form type extension I'm able to extend the default configuration of the CollectionType FormType. This way I can set a custom build ModelTransformer to handle my desired behavior.
This is my Form Type Extension:
class KeepNullFormTypeExtension extends AbstractTypeExtension
{
public static function getExtendedTypes(): iterable
{
return [CollectionType::class];
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder->addModelTransformer(new KeepNullDataTransformer());
}
}
This one needs to be registered with the 'form.type_extension' tag in your service.yml:
PrivateApiBundle\Form\Extensions\KeepNullFormTypeExtension:
class: PrivateApiBundle\Form\Extensions\KeepNullFormTypeExtension
tags: ['form.type_extension']
Please note that you still use the CollectionType in your FormType and not the KeepNullFormTypeExtension as Symfony takes care about the extending...
In the KeepNullFormTypeExtension you can see that I set a custom model transformer with addModelTransformer which is called KeepNullDataTransformer
The KeepNullDataTransformer is responsible for keeping the input null as the output value - it looks like this:
class KeepNullDataTransformer implements DataTransformerInterface
{
protected $initialInputValue = 'unset';
/**
* {#inheritdoc}
*/
public function transform($data)
{
$this->initialInputValue = $data;
return $data;
}
/**
* {#inheritdoc}
*/
public function reverseTransform($data)
{
return ($this->initialInputValue === null) ? null : $data;
}
}
And that's it - this way a supplied input of the type null will stay as null.
More details about this can be found in the linked Symfony documentation:
https://symfony.com/doc/current/form/create_form_type_extension.html
https://symfony.com/doc/2.3/cookbook/form/data_transformers.html
I want to list out all invoice email templates in my custom module. I want to add one dropdown in my custom admin form.
List assume, you have form field like:
$fieldset->addField(
'invoice_template_id', 'select', [
'label' => __('Select Email Template'),
'title' => __('Select Email Template'),
'name' => 'invoice_template_id',
'required' => true,
'class' => 'selectopt',
/* 'css_class' => 'hidden', */
'values' => $this->getEmailTemplate()
]
);
and write function who can get collection of invoice custom email templates:
/**
Email Template List
* */
public function getEmailTemplate() {
$emailList = array();
$collection = $this->emailTemplateCollectionFactory->create();
foreach ($collection as $list) {
if (($list->getOrigTemplateCode() == "sales_email_invoice_template")) {
$emailList[$list->getTemplateId()] = $list->getTemplateCode();
} elseif (($list->getOrigTemplateCode() == "sales_email_invoice_comment_guest_template")) {
$emailList[$list->getTemplateId()] = $list->getTemplateCode();
} elseif ($list->getOrigTemplateCode() == "sales_email_invoice_comment_template") {
$emailList[$list->getTemplateId()] = $list->getTemplateCode();
}elseif ($list->getOrigTemplateCode() == "sales_email_invoice_guest_template") {
$emailList[$list->getTemplateId()] = $list->getTemplateCode();
}
}
return $emailList;
}
It will only return Invoice related custom template list.
How to limit the number of embedded form with the type "sonata_type_collection" ?
$formMapper->add('phones', 'sonata_type_collection',
array(
'required' => true,
'by_reference' => false,
'label' => 'Phones',
),
array(
'edit' => 'inline',
'inline' => 'table'
)
I would like limit to last five phones, I found only this solution for now, limit the display in the template twig "edit_orm_one_to_many", but i don't like that.
I found a solution by rewriting the edit action in the controller,
such in the documentation sonataAdminBundle I created my admin controller class:
class ContactAdminController extends Controller
{
public function editAction($id = null)
{
// the key used to lookup the template
$templateKey = 'edit';
$em = $this->getDoctrine()->getEntityManager();
$id = $this->get('request')->get($this->admin->getIdParameter());
// $object = $this->admin->getObject($id);
// My custom method to reduce the queries number
$object = $em->getRepository('GestionBundle:Contact')->findOneAllJoin($id);
if (!$object)
{
throw new NotFoundHttpException(sprintf('unable to find the object with id : %s', $id));
}
if (false === $this->admin->isGranted('EDIT', $object))
{
throw new AccessDeniedException();
}
$this->admin->setSubject($object);
/** #var $form \Symfony\Component\Form\Form */
$form = $this->admin->getForm();
$form->setData($object);
// Trick is here ###############################################
// Method to find the X last phones for this Contact (x = limit)
// And set the data in form
$phones = $em->getRepository('GestionBundle:Phone')->findLastByContact($object, 5);
$form['phones']->setData($phones);
// #############################################################
if ($this->get('request')->getMethod() == 'POST')
{
$form->bindRequest($this->get('request'));
$isFormValid = $form->isValid();
// persist if the form was valid and if in preview mode the preview was approved
if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved()))
{
$this->admin->update($object);
$this->get('session')->setFlash('sonata_flash_success', 'flash_edit_success');
if ($this->isXmlHttpRequest())
{
return $this->renderJson(array(
'result' => 'ok',
'objectId' => $this->admin->getNormalizedIdentifier($object)
));
}
// redirect to edit mode
return $this->redirectTo($object);
}
// show an error message if the form failed validation
if (!$isFormValid)
{
$this->get('session')->setFlash('sonata_flash_error', 'flash_edit_error');
}
elseif ($this->isPreviewRequested())
{
// enable the preview template if the form was valid and preview was requested
$templateKey = 'preview';
}
}
$view = $form->createView();
// set the theme for the current Admin Form
$this->get('twig')->getExtension('form')->renderer->setTheme($view, $this->admin->getFormTheme());
return $this->render($this->admin->getTemplate($templateKey), array(
'action' => 'edit',
'form' => $view,
'object' => $object,
));
}
}
I have a choice field type named *sub_choice* in my form whose choices will be dynamically loaded through AJAX depending on the selected value of the parent choice field, named *parent_choice*. Loading the choices works perfectly but I'm encountering a problem when validating the value of the sub_choice upon submission. It gives a "This value is not valid" validation error since the submitted value is not in the choices of the sub_choice field when it was built. So is there a way I can properly validate the submitted value of the sub_choice field? Below is the code for building my form. I'm using Symfony 2.1.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('parent_choice', 'entity', array(
'label' => 'Parent Choice',
'class' => 'Acme\TestBundle\Entity\ParentChoice'
));
$builder->add('sub_choice', 'choice', array(
'label' => 'Sub Choice',
'choices' => array(),
'virtual' => true
));
}
To do the trick you need to overwrite the sub_choice field before submitting the form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
...
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$parentChoice = $event->getData();
$subChoices = $this->getValidChoicesFor($parentChoice);
$event->getForm()->add('sub_choice', 'choice', [
'label' => 'Sub Choice',
'choices' => $subChoices,
]);
});
}
this accept any value
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
if(is_array($data['tags']))$data=array_flip($data['tags']);
else $data = array();
$event->getForm()->add('tags', 'tag', [
'label' => 'Sub Choice',
'choices' => $data,
'mapped'=>false,
'required'=>false,
'multiple'=>true,
]);
});
Adding an alternate approach for future readers since I had to do a lot of investigation to get my form working. Here is the breakdown:
Adding a "New" option to a dropdown via jquery
If "New" is selected display new form field "Custom Option"
Submit Form
Validate data
Save to database
jquery code for twig:
$(function(){
$(document).ready(function() {
$("[name*='[custom_option]']").parent().parent().hide(); // hide on load
$("[name*='[options]']").append('<option value="new">New</option>'); // add "New" option
$("[name*='[options]']").trigger("chosen:updated");
});
$("[name*='[options]']").change(function() {
var companyGroup = $("[name*='[options]']").val();
if (companyGroup == 'new') { // when new option is selected display text box to enter custom option
$("[name*='[custom_option]']").parent().parent().show();
} else {
$("[name*='[custom_option]']").parent().parent().hide();
}
});
});
// Here's my Symfony 2.6 form code:
->add('options', 'entity', [
'class' => 'Acme\TestBundle\Entity\Options',
'property' => 'display',
'empty_value' => 'Select an Option',
'mapped' => true,
'property_path' => 'options.optionGroup',
'required' => true,
])
->add('custom_option', 'text', [
'required' => false,
'mapped' => false,
])
To handle the form data we need to use the PRE_SUBMIT form event.
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if (isset($data['options']) && $data['options'] === 'new') {
$customOption = $data['custom_option'];
// todo: handle this better on your own
if (empty($customOption)) {
$form->addError(new FormError('Please provide a custom option'));
return;
}
// Check for a duplicate option
$matches = $this->doctrine->getRepository('Acme\TestBundle\Entity\Options')->matchByName([$customOption]);
if (count($matches) > 0) {
$form->addError(new FormError('Duplicate option found'));
return;
}
// More validation can be added here
// Creates new option in DB
$newOption = $this->optionsService->createOption($customOption); // return object after persist and flush in service
$data['options'] = $newOption->getOptionId();
$event->setData($data);
}
});
Let me know if ya'll have any questions or concerns. I know this might not be the best solution but it works. Thanks!
you cannot not build the sub_choice validation because during you config its validator you don't know which values are valid (values depend on value of parent_choice).
What you can do is to resolve parent_choice into entity before you make new YourFormType() in your controller.
Then you can get all the possible values for sub_choice and provide them over the form constructor - new YourFormType($subChoice).
In YourFormType you have to add __construct method like this one:
/**
* #var array
*/
protected $subChoice = array();
public function __construct(array $subChoice)
{
$this->subChoice = $subChoice;
}
and use provided values in form add:
$builder->add('sub_choice', 'choice', array(
'label' => 'Sub Choice',
'choices' => $this->subChoice,
'virtual' => true
));
Suppose for sub choices you have id's right ?
Create and empty array with a certain number of values and give it as a choice
$indexedArray = [];
for ($i=0; $i<999; $i++){
$indexedArray[$i]= '';
}
then 'choices' => $indexedArray, :)
I have a created a custom form field dropdown list for filtering by year. One of the things I want to do is to allow the user to filter by all years, which is the default option. I am adding this as an empty_value. However, when I render the form, it defaults on the first item that's not the empty value. The empty value is there, just above it in the list. How do I make the page default to, in my case 'All' when the page initially loads? Code is below.
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class YearType extends AbstractType
{
private $yearChoices;
public function __construct()
{
$thisYear = date('Y');
$startYear = '2012';
$this->yearChoices = range($thisYear, $startYear);
}
public function getDefaultOptions(array $options)
{
return array(
'empty_value' => 'All',
'choices' => $this->yearChoices,
);
}
public function getParent(array $options)
{
return 'choice';
}
public function getName()
{
return 'year';
}
}
I'm rendering my form in twig with a simple {{ form_widget(filter_form) }}
Try adding empty_data option to null, so it comes first. I have many fields of this type and it's working, for example:
class GenderType extends \Symfony\Component\Form\AbstractType
{
public function getDefaultOptions(array $options)
{
return array(
'empty_data' => null,
'empty_value' => "Non specificato",
'choices' => array('m' => 'Uomo', 'f' => 'Donna'),
'required' => false,
);
}
public function getParent(array $options) { return 'choice'; }
public function getName() { return 'gender'; }
}
EDIT: Another possibility (i suppose) would be setting preferred_choices. This way you'll get "All" option to the top. But i don't know if it can work with null empty_data, but you can change empty_data to whatever you want:
public function getDefaultOptions(array $options)
{
return array(
'empty_value' => 'All',
'empty_data' => null,
'choices' => $this->yearChoices,
'preferred_choices' => array(null) // Match empty_data
);
}
When I've needed a simple cities dropwdown from database without using relations, I've ended up using this config for city field (adding null as first element of choices array), as empty_data param didn't do work for me:
$builder->add('city',
ChoiceType::class,
[
'label' => 'ui.city',
'choices' => array_merge([null], $this->cityRepository->findAll()),
'choice_label' => static function (?City $city) {
return null === $city ? '' : $city->getName();
},
'choice_value' => static function(?City $city) {
return null === $city ? null : $city->getId();
},
]);