When a form doesn't validate, I need to access the submitted data inside a Form Class in order I can set some options in a custom field.
I have tried with
$data = $builder->getForm()->getData();
$data = $builder->getData();
but $data has the empty object. So... what is the correct form to access the submitted data by the user after validation error in the form class?
Thanks
The problem is you're trying to access submitted data when it has not be handled yet. Basically, when you are in a builder (buildForm for the abstract types), you are building your form structure. It has nothing to do with form submission/binding. This is why you get the initial data when you call $builder->getData() because it only know the initial data at this state.
Knowing that the form component allows you to access the submitted data via events. You can attach a listener to your builder and rely on one of the *_submit event. The FormEvent class will given you the submitted data with $event->getData().
See this doc for more information: http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html
Look into $options variable (var_dump it)
As I remeber you are looking for
$options['data']
Using Form Events.
For those who wonder how Form Events are used.
Here is an example where you can modify the form after the user has tapped the submit button.
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
// ...
/* Listener to order to set a price if it does not exist yet */
$builder->get('price')->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
// modify it as you wish
$event->setData($data);
});
The FormEvents::PRE_SUBMIT event is dispatched at the beginning of the
Form::submit() method.
If needed, here is an example where you can modify the form price before you display it.
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
// ...
/* Listener to order to set a price if it does not exist yet */
$builder->get('price')->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$data = $event->getData();
if (null === $data) { $data = '0.00'; }
$event->setData($data);
});
The FormEvents::PRE_SET_DATA event is dispatched at the beginning of
the Form::setData() method.
Related
Goal:
I have a DataObject called "Event". This is in a managed_model for "EventsAdmin" (extending ModelAdmin). When editing an Event, I want a tab on the record called "Moderation" that has a few fields and two buttons: "Approve" and "Reject". These two buttons call an action each that performs relevant actions.
Event extends DataObject
public function getCMSFields() {
$fields = parent::getCMSFields();
$eventStatus = $fields->dataFieldByName("EventStatus")
->setTitle('Current Status')
->setDisabled(true);
$approveButton = FormAction::create('doApproveEvent', _t('SiteBlockAdmin.Approve', 'Approve'))
->setUseButtonTag(true)
->addExtraClass('btn-outline-success font-icon-check-mark-circle');
$rejectButton = FormAction::create('doRejectEvent', _t('SiteBlockAdmin.Reject', 'Reject'))
->setUseButtonTag(true)
->addExtraClass('btn-outline-danger font-icon-cancel-circled');
$fields->addFieldsToTab('Root.Moderation', [
$eventStatus,
$approveButton,
$rejectButton
]);
return $fields;
}
This displays the buttons just fine. But they don't do anything. So I am trying to work out how they can plug into action methods doApproveEvent and doRejectEvent (And where they should go)
I did find docs that led me to adding the buttons to the action bar at the bottom of the CMS page via updateFormActions(). But this isn't what I want as the other fields I am adding above the buttons are part of the Approve/Reject process. Here is the code for this method. This works fine barring the buttons are not in a logical place for the process I'm trying to create.
class CMSActionButtonExtension extends DataExtension
{
public function updateFormActions(FieldList $actions)
{
$record = $this->owner->getRecord();
if (!$record instanceof Event || !$record->exists()) {
return;
}
$approveButton = FormAction::create('doApproveEvent', _t('SiteBlockAdmin.Approve', 'Approve'))
->setUseButtonTag(true)
->addExtraClass('btn-outline-success font-icon-check-mark-circle');
$rejectButton = FormAction::create('doRejectEvent', _t('SiteBlockAdmin.Reject', 'Reject'))
->setUseButtonTag(true)
->addExtraClass('btn-outline-danger font-icon-cancel-circled');
$actions->push($approveButton);
$actions->push($rejectButton);
}
public function doApproveEvent($data, $form) {
$record = $this->owner->getRecord();
// Approve logic
}
public function doRejectEvent($data, $form) {
$record = $this->owner->getRecord();
// Reject logic
}
}
The above Extension is attached to GridFieldDetailForm_ItemRequest
extension.yml
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest:
extensions:
- My\Namespace\CMSActionButtonExtension
Interestingly, if I have both sets of buttons on the page at the same time, the updateFormActions option works while my desired option still doesn't. Despite the buttons being of identical markup and sitting inside the exact same form tag. I assume that has something to do with how Silverstripe loads the main content panel and the DOM.
Any thoughts on achieving this? Anyone seen a button added to the main CMS panel in a module that I could take a look at? I found this post from 5 years ago, but it's for SS3 and the answer doesn't work for me.
Short answer:
you have to add custom FormActions through an Extension on the Controller that controls the form (or on the form itself
Long Answer:
A bit of background on how SilverStripe does forms:
Generally speaking, forms are always served through Controllers/RequestHandlers (they need to be accessible on some route, usually that's an Action on a Controller that is often named Form, EditForm, ItemEditoForm, ...).
Fields
Inside the CMS you rarely ever have to create your own form, that's done by the CMSs built in Controllers/RequestHandlers for the admin area (GridFieldDetailForm_ItemRequest in this case).
Basically (pseudo code here), what those controllers do is:
public function EditForm() {
$fields = $myCurrentlyEditingDataObject->getCMSFields();
$actions = ...;
$validator = ...;
$this->updateFormActions(&$actions);
$form = new Form('ItemRequestForm', $fields, $actions, $validator);
$this->updateItemEditForm(&$form); // or $this->updateEditForm()
return $form;
}
So, getCMSFields() and in some cases getCMSActions()/getCMSValidator() (not sure if those 2 are still used in SilverStripe 4.x), you can add things to the form, without ever seeing the form object.
Also, the getCMSFields() will always be put into the ``` section of the Form, that's why your button is somewhere in the middle with all the fields and not with the other actions.
Submission
When a form is submitted (eg to /admin/pages/edit/EditForm/265/field/NameOfMyGridField/item/542/ItemEditForm), it will call the action GridFieldDetailForm_ItemRequest->ItemEditForm() which returns the Form object where subsequently FormRequestHandler->httpSubmission() is called. This will then look at the submitted data to figure out what action was clicked (eg $_REQUEST['action_doApproveEvent']) and try to find that action.
The way it tries to find that, is checking if it itself has a method called doApproveEvent, if that fails, it will try Form->getController()->doApproveEvent() or something like that. In the case of a GridField, that controller is GridFieldDetailForm_ItemRequest which means it will try to call GridFieldDetailForm_ItemRequest->doApproveEvent()
So, that means DataObject->getCMSFields() lets you easily add FormFields (and FormActions) into your form body.
But it does not provide a means of adding a method to handle the submission.
That's why, for custom actions you need to modify the Controller (GridFieldDetailForm_ItemRequest in this case).
You are doing this by creating a Extension which you attached to GridFieldDetailForm_ItemRequest.
Any method in your Extension is added to the thing it's attached to, so if you add a method called updateFormActions, it will kind of become GridFieldDetailForm_ItemRequest->updateFormActions().
And if you recall from earlier, the controller will call $this->updateFormActions() during the creation of the form.
Additionally, as I explained earlier, when a FormAction is named doApproveEvent it will look for a GridFieldDetailForm_ItemRequest->doApproveEvent(), which now exists because you added it through that Extension.
So, in summary: you have to add custom FormActions through an Extension on the Controller that controls the form (or on the form itself
PS: the old post from
bummzack you linked to worked in 3.x, because the Controller in his example that created the form was an instance of LeftAndMain.
Looking for a straightforward way to add constraints dynamically to all of my form fields. So far I've hit upon the idea of using a form type extension, which kind of works: I can modify the form view and then manually check the view on form submission.
However, is there a smarter way to add real Symfony-based constraints in real-time?
(Note that the constraints need to be added to the form in real-time as the form loads based on user configuration in the database.. Predefined form groups and the like won't work.)
I would suggest to use form events.
Use the PRE_SUBMIT event to edit the form before validation.
Recreate your fields with $event->getForm()->add(...) adding your constraints.
Of course you can automatically add the listener to all form using a FormExtension which adds the listener.
EDIT : Some examples from Alsatian67/FormBundle
Your extension should looks like :
class ExtensibleExtension extends AbstractTypeExtension
{
private $extensibleSubscriber;
public function __construct($extensibleSubscriber) {
$this->extensibleSubscriber = $extensibleSubscriber;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Only apply on base form
if($builder->getForm()->isRoot())
{
$builder->addEventSubscriber($this->extensibleSubscriber);
}
}
public function getExtendedType()
{
return FormType::class;
}
}
And your EventListener / EventSubscriber should iterate on all the children :
foreach($event->getForm()->all() as $child){
$childName = $child->getName();
$type = get_class($child->getConfig()->getType()->getInnerType());
$options = $child->getConfig()->getOptions();
$options['constraints'] = array(/* ... */);
$form->add($childName,$type,$options);
}
I need to modify a field in the SUBMIT form event, but when I do any validation rules on the field are lost.
This is all that's happening in the form type (the title field isn't actually being changed I'm just using it as an example):
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add("title");
$builder->addEventListener(FormEvents::SUBMIT, function(FormEvent $event) {
$form = $event->getForm();
$form->add("title");
});
}
Any validation rules for 'title' are now lost, either annotation rules defined with the entity or using a separate validator class.
Can I do anything to keep the validation or is it intended that validation rules don't get run for fields which are modified in the SUBMIT event?
If you can handle the FormEvents::POST_SUBMIT event instead of FormEvents::SUBMIT you will keep the validation. You will need to make sure that the listener is on the child form that you want to edit, otherwise you will have an issue with not being able to add a field to a submitted form.
In this instance you're not actually modifying a field you're adding a new one with $form->add('title') which will replace the existing 'title' field within the form (which is why the validation constraints are disappearing). You might want to look into validation groups for the type of functionality you're aiming for unless you want to elaborate on what you're doing within the submit event?
I have a custom content type called events which has a few fields defined in it.
The field name is field_store_name. I can get all the options from these check boxes using this code:
$form['field_store']['und']['#options']
This is how I get the option(s) that are selected/checked. Is this the correct way of doing this?
$form_state['build_info']['args']['0']->field_store['und']
Thanks
When user submits form your custom submitter could be called.
To add custom form submitter to any form you should use:
/* Implements hook_form_alter(). */
function moduleName_form_alter($form, $form_state) {
// ...
$form['#submit'][] = 'moduleName_submitterName';
// ...
}
So in custom submitter you will have all submitted values under $form_state['values']:
function moduleName_submitterName($form, $form_state) {
dpm($form_state['values']);
}
This index will apper in $form_state array only when you submit form and will contain submitted values. $form array will still contain default values shown at form before you've changed them and submitted form.
Read more:
An example of form submitter: https://www.drupal.org/node/717740.
hook_form_alter(): https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_form_alter/7
Value you need should be $form_state['values']['field_store]['und'][0].
I use Form Component and have a ChoiceType field on the form which is rendered to a select field.
On the client-side I use select2 plugin which initializes a select with tags: true allowing the addition of new values to it.
But if I add a new value then a validation on the server fails with an error
This value is not valid.
because the new value isn't in the choice list.
Is there a way to allow the addition of new values to a ChoiceType field?
The problem is in a choice transformer, which erases values that don't exist in a choice list.
The workaround with disabling the transformer helped me:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('choiceField', 'choice', ['choices' => $someList]);
// more fields...
$builder->get('choiceField')->resetViewTransformers();
}
Here's an example code in case someone needs this for EntityType instead of the ChoiceType. Add this to your FormType:
use AppBundle\Entity\Category;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
if (!$data) {
return;
}
$categoryId = $data['category'];
// Do nothing if the category with the given ID exists
if ($this->em->getRepository(Category::class)->find($categoryId)) {
return;
}
// Create the new category
$category = new Category();
$category->setName($categoryId);
$this->em->persist($category);
$this->em->flush();
$data['category'] = $category->getId();
$event->setData($data);
});
No, there is not.
You should implement this manually by either:
using the select2 events to create the new choice via ajax
catching the posted options before validating the form, and add it to the options list