I've created a content type using two fields "capacity" and "count".
In a custom module I'd like to validate that field "count" should be less than field "capacity".
function MYMODULE_node_form_validate($form, &$form_state) {
$capacity = $form['capacity']['#value'];
$count = $form['count']['#value'];
if ($count > $capacity) {
form_set_error('title', 'Not possible');
}
}
First add a custom validation function, then fire your logic from there and prevent the form from further processing if necessary. Replace MYMODULE and MYCONTENTTYPE with your machine names.
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function MYMODULE_form_node_form_alter(&$form, &$form_state, $form_id) {
// Find the content type of the node we are editing.
$content_type = $form['#node']->type;
if ($content_type == 'MYCONTENTTYPE') {
// Add an additional custom validation callback.
$form['#validate'][] = 'MYCUSTOM_FORMVALIDATION';
}
}
/**
* Custom MYCONTENTYTPE node form validation.
*/
function MYCUSTOM_FORMVALIDATION($form, &$form_state) {
// Better check isset() and !empty() first. Depends on your needs.
// Convert values to comparable numbers.
// Maybe you prefer intval() or some other logic. Depends on your needs.
$field_a = floatval($form_state['values']['field_a'][LANGUAGE_NONE][0]['value']);
$field_b = floatval($form_state['values']['field_b'][LANGUAGE_NONE][0]['value']);
// Stop the form from further processing if field A < than field B.
if ($field_a < $field_b) {
form_set_error('stop', t('Field A shall be greater then Field B'));
}
}
Related
I have an editing node form. When user enters new value and clicks on submit to edit the node, I first want to get the old node back, manipulate the value and then just save/update the node.
Below is my solution, but it does not work.
function custom_module_form_node_form_alter(&$form, FormStateInterface $form_state) {
$editing_entity = $form_state->getFormObject()->getEntity();
if (!$editing_entity->isNew()) {
$form['actions']['submit']['#submit'][] = 'custom_module_node_form_submit';
}
}
function custom_module_node_form_submit($form, FormStateInterface $form_state) {
$editing_entity = $form_state->getFormObject()->getEntity();
$entity = Drupal::entityTypeManager()->getStorage('node')->load($editing_entity->id());
}
In the form_submit hook, I tried to get the old node back but it is already too late and the node is already updated/saved. How can I get the old node back and manipulate the value before updating/saving the node in Drupal 8?
Try using hook_entity_presave():
/**
* Implements hook_entity_presave().
*/
function YOUR_MODULE_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {
switch ($entity->bundle()) {
// Here you modify only your day content type
case 'day':
// Setting the title with the value of field_date.
$entity->setTitle($entity->get('field_date')->value);
break;
}
}
Solution taken from here: https://drupal.stackexchange.com/questions/194456/how-to-use-presave-hook-to-save-a-field-value-as-node-title
Also you can get old value like: $entity->original. Check it out here:
https://drupal.stackexchange.com/questions/219559/how-to-get-the-original-entity-on-hook-entity-presave
I decide to manipulate the values in the form validate hook as following.
function custom_module_form_node_form_alter(&$form, FormStateInterface $form_state) {
$editing_entity = $form_state->getFormObject()->getEntity();
if (!$editing_entity->isNew()) {
$form['#validate'][] = 'custom_module_node_form_validate';
}
}
function custom_module_node_form_validate(array &$form, FormStateInterface $form_state) {
$old_entity = $form_state->getFormObject()->getEntity();
$old_values = $old_entity->get('field_name')->getValue()
$new_values = $form_state->getValue('field_name');
// Manipulate and store desired values to be save here.
$to_save_value = ['a', 'b', 'c'];
$form_state->setValue('field_name', $to_save_value);
}
Use hook_ENTITY_TYPE_presave, like so:
function yourmodulename_node_presave(Drupal\node\NodeInterface $entity) {
if ($entity->getType() == 'your_content_type') {
$entity->setTitle('Hello');
$entity->set('body', 'this is body');
}
}
This is the best solution, because with hook_form_alter like MilanG you will be changing the value only when the node is saved from the particular form you are altering! If the node is saved programmatically from within the code or by some other method your hook_form_alter will not kick in.
I am attempting to set the "title" value of a content type before it becomes "required" .
So what happens is the title field becomes hidden based on user name, after filling in the "first name" and "last name" fields I need to take those values and then apply them to the "title" field, before drupal states that the field is required. Here's what I have so far
/**
* Implements hook_form_alter().
*/
function editorhide_form_alter(&$form, &$form_state, $form_id){
global $user;
global $fullTitle;
if($form_id == 'artist_node_form'){
if( $user->name == 'Editor'){
drupal_add_js("jQuery(document).ready(function(){
jQuery('#edit-title').hide();
});","inline");
//adding the form handler
$form['#submit'][] = "editorhide_form_submit_handler";
}
}
}
//submit form handler.
function editorhide_form_submit_handler ($form, &$form_state) {
global $fullTitle;
$fullTitle = $form_state['values']['field_firstname']['und']['0']['value'];
$fullTitle .= ' '. $form_state['values']['field_lastname']['und']['0']['value'];
form_set_value($form['#edit-title'], $fullTitle,$form_state);
}
With my current implementation, it isn't doing quite what I want, as it's throwing the "required field" error.
The problem you are having is that your new submit function never gets executed. A form will go through all validation functions first.
You need to set a new validate function, and make sure it is inserted at the begining of the validation process (so you can set title prior to the node validation):
array_unshift($form['#validate'], "editorhide_form_validate_handler");
Then your validation can do the following (may need to adjust array indexes):
function editorhide_form_validate_handler ($form, &$form_state) {
$fullTitle = $form_state['values']['field_firstname']['und']['0']['value'];
$fullTitle .= ' '. $form_state['values']['field_lastname']['und']['0']['value'];
$form_state['values']['title']['und']['0']['value'] = $fullTitle;
}
I am not a HUGE fan of modifying form_state mid validation, but this should work.
Lets see the action (form is based on model)
$this->form->bind ($request->getParameter('task'));
if ($this->form->isValid())
{
// cakk
}
This all works good, its not valid when its really not valid etc.
But I want to edit some fields, for example a date must be always set to now. Or a password must be encoded. How can I do this?
You can override the doSave() method in the form .. something like this :
public function doSave($con = null)
{
$this->values['form field'] = 'newvalue';
parent::doSave($con);
}
$this->values is an array containing the values on the form.
Update
You could use a post validator .. like this (again in the form class) :
$this->validatorSchema->setPostValidator(
new sfValidatorCallback(array('callback' => array($this, 'methodName')))
);
public function methodName($validator, $values)
{
// check / change what you need to
$values['fieldname'] = 'new value';
// return values
return $values;
}
I've got a Form where the user can check a checkbox "create new address" and can then fill out the fields for this new address in the same form.
Now I want to validate the fields of this new address ONLY if the checkbox has been checked. Otherwise, they should be ignored.
How can I do that using Zend_Form with Zend_Validate?
Thanks!
I think that the best, and more correct way to do this is creating a custom validator.
You can do this validator in two different ways, one is using the second parameter passed to the method isValid, $context, that is the current form being validated, or, inject the Checkbox element, that need to be checked for validation to occur, in the constructor. I prefer the last:
<?php
class RequiredIfCheckboxIsChecked extends Zend_Validate_Abstract {
const REQUIRED = 'required';
protected $element;
protected $_messageTemplates = array(
self::REQUIRED => 'Element required'
);
public function __construct( Zend_Form_Element_Checkbox $element )
{
$this->element = $element;
}
public function isValid( $value )
{
$this->_setValue( $value );
if( $this->element->isChecked() && $value === '' ) {
$this->_error( self::REQUIRED );
return false;
}
return true;
}
}
Usage:
class MyForm extends Zend_Form {
public function init()
{
//...
$checkElement = new Zend_Form_Element_Checkbox( 'checkbox' );
$checkElement->setRequired();
$dependentElement = new Zend_Form_Element_Text( 'text' );
$dependentElement->setAllowEmpty( false )
->addValidator( new RequiredIfCheckboxIsChecked( $checkElement ) );
//...
}
}
I have not tested the code, but I think it should work.
I didn't actually run this, but it should work within reason. I've done something similar before that worked, but couldn't remember where the code was.
<?php
class My_Form extends Zend_Form
{
public function init()
{
$checkbox = new Zend_Form_Element_Checkbox("checkbox");
$checkbox->setValue("checked");
$textField = new Zend_Form_Element_Text("text");
$this->addElements(array("checkbox", "text"));
$checkbox = $this->getElement("checkbox");
if ($checkbox->isChecked() )
{
//get textfield
$textField = $this->getElement("text");
//make fields required and add validations to it.
$textField->setRequired(true);
}
}
}
Here's what I do if I need to validate multiple elements against each other
$f = new Zend_Form();
if($_POST && $f->isValid($_POST)) {
if($f->checkbox->isChecked() && strlen($f->getValue('element')) === 0) {
$f->element->addError('Checkbox checked, but element empty');
$f->markAsError();
}
if(!$f->isErrors()) {
// process
...
...
}
}
I've been wondering how to do that in ZF as well, though never had to implement such form feature.
One idea that comes to mind is to create a custom validator that accepts the checkbox field as a parameter, and run it in a validator chain, as documented. If the checkbox is checked, validator could return failure. Then you can check whether all validations failed and only then treat form as having failed validation.
That level of customization of form validation could be inconvenient, so maybe using form's isValidPartial method would be better.
I created a custom validator that will make your element required based on the value of another zend form element.
Here's the full code. I hope this helps someone.
The idea is to create a custom validator and pass in the name of the conditional element and the value of that conditional element into the constructor. Zend_Validor's isValid method already has access to the value of the element you are attaching the validtor to along with all the form element names and values.
So, inside the isValid method you have all the information you need to determine if your form element should be a required element.
On Zend 1 extend the isValid method, where you set required depending on posted data for example:
public function isValid($data)
{
if (!empty($data['companyCar'])) {
$this->getElement('carValue')->setRequired(true);
}
return parent::isValid($data);
}
Thank you JCM for your good solution.
However 2 things I've noticed:
The isValid method of your validator has to return true in case of success.
The validator will not be executed during form validation without pass the allowEmpty option to false to the text field.
Once you're OK with basic record form built after example from Tutorial, you realize you want more professionally designed Record Form. E.g. I don't want to duplicate record form for the same table in User and Admin areas.
1) Does anyone use some mechanism, possibly inheritance, to reduce duplication of almost similar admin and user forms? Is that burdensome or sometimes you better just do with copy-pasting?
2) Has anyone considered it to be a good idea to build some basic Record class
that can determine that among several record forms on this page, the current post is addressed specifically to this record form
that can distinguish between Edit or Delete buttons clicks in some organized fashion.
3) My current practice includes putting all form config code (decorators, validations, initial values) into constructor and form submit handling is put into a separate ProcessSubmit() method to free controller of needless code.
All the above addresses to some expected Record Form functionality and I wonder if there is any guideline, good sample app for such slightly more advanced record handling or people are still reinveting the wheel. Wondering how far you should go and where you should stop with such impovements...
Couple of suggestions:
First of all - Use the init() function instead of constructors to add your elements when you are subclassing the form. The init() function happens after the parameters you pass to the class are set.
Second - Instead of subclassing your form - you can just set an "option" to enable the admin stuff:
class My_Record_Form extends Zend_Form {
protected $_record = null;
public function setRecord($record) {
$this->_record = $record;
}
public function getRecord() {
if ($this->_record === null || (!$this->_record instanceOf My_Record)) {
throw new Exception("Record not set - or not the right type");
}
return $this->_record;
}
protected $_admin = false;
public function setAdmin($admin) {
$this->_admin = $admin;
}
public function getAdmin() { return $this->_admin; }
public function init() {
$record = $this->getRecord();
$this->addElement(......);
$this->addElement(......);
$this->addElement(......);
if ($this->getAdmin()) {
$this->addElement(.....);
}
$this->setDefaults($record->toArray());
}
public function process(array $data) {
if ($this->isValid($data)) {
$record = $this->getRecord();
if (isset($this->delete) && $this->delete->getValue()) {
// delete button was clicked
$record->delete();
return true;
}
$record->setFromArray($this->getValues());
$record->save();
return true;
}
}
}
Then in your controller you can do something like:
$form = new My_Record_Form(array(
'record'=>$record,
'admin'=>My_Auth::getInstance()->hasPermission($record, 'admin')
));
There is nothing "wrong" with making a My_Record_Admin_Form that handles the admin stuff as well - but I found this method keeps all the "record form" code in one single place, and a bit easier to maintain.
To answer section 2: The edit forms in my code are returned from a function of the model: $record->getEditForm() The controller code ends up looking a little like this:
protected $_domain = null;
protected function _getDomain($allowNew = false)
{
if ($this->_domain)
{
return $this->view->domain = $this->_domain;
} else {
$id = $this->_request->getParam('id');
if (($id == 'new' || $id=='') && $allowNew)
{
MW_Auth::getInstance()->requirePrivilege($this->_table, 'create');
$domain = $this->_table->createRow();
} else {
$domain = $this->_table->find($id)->current();
if (!$domain) throw new MW_Controller_404Exception('Domain not found');
}
return $this->view->domain = $this->_domain = $domain;
}
}
public function editAction()
{
$domain = $this->_getDomain(true);
MW_Auth::getInstance()->requirePrivilege($domain,'edit');
$form = $domain->getEditForm();
if ($this->_request->isPost() && $form->process($this->_request->getPost()))
{
if ($form->delete && $form->delete->getValue())
{
return $this->_redirect($this->view->url(array(
'controller'=>'domain',
'action'=>'index',
), null, true));
} else {
return $this->_redirect($this->view->url(array(
'controller'=>'domain',
'action'=>'view',
'id'=>$form->getDomain()->id,
), null, true));
}
}
$this->view->form = $form;
}
So - the actual id of the record is passed in the URI /domain/edit/id/10 for instance. If you were to put multiple of these forms on a page - you should make sure to set the "action" attribute of the form to point to an action specific to that form.
I created a SimpleTable extends Zend_Db_Table and SimpleForm extends Zend_Db_Form classes. Both of these assume that your table has an auto-incrementing ID column.
SimpleTable has a saveForm(SimpleForm $form) function which uses the dynamic binding to match form element names to the columns of the record. I also included an overridable saveFormCustom($form) for any special handling.
The SimpleForm has an abstract setup() which must be overridden to setup the form. I use the init() to do the initial setup (such as adding the hidden ID field).
However, to be honest, I really don't like using the Zend_Form object, I feel like that should be handled in the View, not the Model or Controller.