I am having one dropdown option in the form called town. Already fetched values will be available in drop down from the databasetable townId. Now I want to check if the user doesn't select any choice from the dropdown and directly goes to the save button, then it should display "please choose your choice in dropdown" like that. The form is named university
I tried this code here:
if(Zend_Form_Element_Submit)
{
if($$townid=='')
{ alert("U Must Choose Town Name Here");
}
else
{
$submit = new Zend_Form_Element_Submit('Save');
$submit->setDecorators($this->submitDecorators)
->setAttrib('class','button slategray');
}
}
Inside models->university.php there are some actions for dropdown I didn't get exactly:
public function setOptions(array $options)
{
$methods = get_class_methods($this);
foreach ($options as $key => $value) {
$method = 'set' . ucfirst($key);
if (in_array($method, $methods)) {
$this->$method($value);
}
}
return $this;
}
Before I edited form->university (the save code already exits)
$submit = new Zend_Form_Element_Submit('Save');
$submit->setDecorators($this->submitDecorators)
->setAttrib('class','button slategray');
}
Thanks in advance.
To work properly with Zend Framework's forms I recommand to try this approach:
Create a form by extending the Zend_Form class
class Form_User extends Zend_Form
Which automatically give you access to a container to manage all your elements from this form and give you acces to a isValid() method which allow you to validate all your form elements at once and to a populate() method which allow you to feed data to your form for editing
In your new class (Form_User) you can define all your form properties and elements in the init() method.
public function init()
{
$this->setName('user')
->setAttrib('id', 'user');
$username = new Zend_Form_Element_Text('username');
$town = new Zend_Form_Element_Select('town');
$town->addMultioptions(array(...));
$submit = new Zend_Form_Element_Select('submit');
$this->addElements(array($username, $town, $submit));
}
Each of these elements can be customized with a label, a description, some validators, some filters, etc.
If you want to make an element mandatory you can set the Required property to true
$town->setRequired(true);
To validate your form after the submit has been cliqued, you can do it as simply as (assuming you do this in the controller) :
$form = new Form_User();
$postData = $this->getRequest()->getPost();
$form->isValid($postData);
This will check for the required fields as well as execute any validators that you have setted on these diverses elements.
It will return true if everything is ok, and false if there is an error. If you get an error a display the form again, error messages will be shown automatically beside each erroneous fields.
If you want to set options values of your form elements, when you initialize your form, you can pass a configuration array like this :
$form = new Form_User(array('townListOptions' => array(...));
The associated method setTownListOptions will be called automatically and will receive the array (or any other object) you assigned it with.
I could explain even further things about forms but as #Rohan stated in his comment, RTM
In asp.net
<asp:DropDownList ID="ddlusertype" runat="server" CssClass="dropdown" ></asp:DropDownList>
<asp:CompareValidator ID="CompareValidator2" runat="server" ControlToValidate="ddlusertype" ErrorMessage="select" Font-Size="XX-Small" Operator="NotEqual" Type="Integer" ValueToCompare="0"></asp:CompareValidator>
Related
I have a preSubmit event in an event subscriber for a form, and for a specific case I want to add an error to a form field. My method within the subscriber is as follows:
public function onPreSubmit(FormEvent $event)
{
$sourceData = $event->getData();
$form = $event->getForm();
$identifier = &$sourceData['identifier'];
if ($identifier) {
if ($this->identifierIsUrl($identifier)) {
$parser = $this->getIdParser();
$identifier = $parser->getIdentifier($identifier);
if (is_null($identifier)) {
$form->get('identifier')->addError(new FormError('You have either entered an incorrect url for the source or it could not be parsed'));
}
}
$event->setData($sourceData);
}
}
However when I print the form error in the view, it is empty. Is it possible to do this in a preSubmit event? Am I looking at this the wrong way?
The issue is related to the Symfony\Component\Form\Form::submit method that removes all of the form field specific errors assigned after the PRE_SUBMIT event.
During Form::submit it iterates over all the forms child objects (which are also themselves Form objects as noted by the other answers) and calls their submit methods individually. Resulting in the form element errors that were added during the parent's PRE_SUBMIT event to be reset to an empty array.
This is why you can use $form->addError() in the parent PRE_SUBMIT event or set the form element to error_bubbling => true and it will display as the parent form errors, but not to a specific form element.
Here's the example of what occurs without looking through the entire codebase for Symfony Forms.
class Form
{
public function addError($error)
{
if($this->parent && $this->config->getErrorBubbling()) {
$this->parent->addError($error); //element had error_bubbling => true, attach the error to the parent.
} else {
$this->errors[] = $error; //add it to the current object's errors array
}
}
public function submit()
{
$this->errors = array(); //resets the errors of the current object
$this->preSubmitEvent();
foreach($this->children as $child) {
$child->submit(); //errors in child object are reset
}
}
}
So it results in
Form:
submitMethod:
preSubmitEvent
children:
submitMethod:
preSubmitEvent
To get around the issue you can add a PRE_SUBMIT event directly to your form element to validate that element and add errors to it.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('identifier', Form\TextType::class);
//...
$builder->get('identifier')->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'validateIdentifier']);
}
Then alter your onPreSubmit method accordingly.
public function validateIdentifier(FormEvent $event)
{
$identifier = $event->getData();
$element = $event->getForm();
if ($identifier) {
if ($this->identifierIsUrl($identifier)) {
$parser = $this->getIdParser();
$identifier = $parser->getIdentifier($identifier);
if (null === $identifier) {
$element->addError(new FormError('You have either entered an incorrect url for the source or it could not be parsed'));
}
}
$event->setData($identifier);
}
}
It should be possible if you look at the Form::submit(), you'll see that the errors are reset before the PRE_SUBMIT data is dispatched. Also Validation Listener doesn't reset the errors on the form, and the Violation Mapper only adds Errors to the form. And AFIK there aren't any listeners that reset the form's errors. So maybe you're doing something else wrong.
It is possible that errors are just not displayed in your layout.
To debug, make sure your form is actually made invalid by your custom check (if you show your controller I may be able to help with actual code).
If that is in fact your issue, you may want to add error_bubbling => true to your field definition.
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.
I'm wondering if there was a way to add a group of elements to a zend form as if they were one element, I guess much like a subform, but it seems the functionality of a subform may be too much...
Here's my use-case. I've created a class that handles multi-page forms. I want to be able to write logic to change the buttons at the bottom of the form based on the page of the form I'm on.
I originally thought that Zend-Form-DisplayGroup would fix my problem, but you have to add the items to the form first and then add them to the display group and can't pass a display group through a function with attached elements. I would like to have a function that would be something like
public function setSubmitButtonGroup($submitButtonGroupElements)
{
/*Code to set button group*/
}
The idea of using an array of elements just hit me right now as opposed to something else and add logic to add that array of elements to the form on render... but does anyone have any "better" ideas or done this before?
BTW, if anyone is wondering... I'm loosely basing my initial design off of this section: Zend Framework Advance Form Usage.
Not sure I understand your problem correctly but this how I do some things.
In a Zend_Form object you can add elements as a group with `addElements($elements) in an array. For the Submit button etc. I have a class where I get the $elements array from and then I simply pop it in. I also add a displayGroup but separately and simply to control where the buttons are. Because a form is an object you can do simple things like the following but I always add a reference to show my intent.
update: shuffled the button manipulation
function addButtons(&$form,$nextName = null) {
$buttons = $this->getButtons(); // this will be an array with your buttons
// make sure you have the element names in your buttons arrays
if ( is_string($nextName) ) {
$buttons['nextButton']->setLabel($nextName);
} elseif ( is_bool($nextName) && false === $nextName ) {
unset($buttons['nextButton'];
}
// repeat for other buttons
$form->addElements($buttons);
$elementNames = array_keys($buttons);
$form->addDisplayGroup($elementNames,'buttonGroup',array('legend'=>'Click some buttons'));
}
$this->addButtons($form,'Finish');
You could make yourself a factory that receive three params, your form element, the current controller and the current action. Then in that factory, you could call a builder based on the controller/action combination and you pass your form.
In your builder you add 1, 2 or 3 buttons based on the corresponding controller/action requirement which are stored in diffrent components. Once it is done, you return your form to the factory and the factory return the form.
My_Form // your Zend_Form object
My_Form_Factory // Your new factory (see below)
My_Form_Factory_Builder_Controller_Action // One of your builder (see below)
My_Form_Factory_Component // Extends the corresponding Zend_Form_Elements
// basic factory that can be called like My_Factory::factory($form, $controller, $action)
class My_Form_Factory {
static public function factory($form, $controller, $action)
$builderClass = "My_Form_Factory_Builder_" . $controller . '_' . $action;
$builder = new $builderClass($form);
return $builder->buildForm();
}
// Basic builder
class My_Form_Factory_Builder_Controller_Action
{
protected $_form;
protected $_previousComponent ;
protected $_nextComponent ;
protected $_cancelComponent ;
public function __construct($form)
{
$this->_form = $form;
$this->_previousComponent = new My_Form_Factory_Component_Previous();
$this->_nextComponent = new My_Form_Factory_Component_Next();
$this->_cancelComponent = new My_Form_Factory_Component_Cancel();
}
public function buildForm()
{
$this->_form->addElement($previousCompnent);
$this->_form->addElement($nextComponent);
$this->_form->addElement($cancelComponent);
return $this->_form;
}
}
If you want to automatize the instanciation you could initialize all the different compoments you might require in an abstract class and in the method buildForm() only add the elements you need for that current interface. (I would rather repeat the code in each builder than rely on this kind of "magic" but it a viable method to do it).
So the complexity of my problem comes with knowing what page of the multipage form. Using an array and the above mentioned addElements() helped.
Simple Answer
The answer to my problem was an array that could be manipulated after the form was "built" so to speak but before it was rendered so that I could add to the form using addElements().
Long Answer
To get the whole picture, imagine each time you hit the next or previous button, you are traversing through an array of subforms. In this case one would need a function to handle the button rendering. I ended up using a case statment, though it's not the best implementation in the world (not reusable in the parent class Form_MultiPage), but it worked:
in my extention of my mulipage form class I have
public function setSubmitControls()
{
$previous = new Zend_Form_Element_Submit('previous',array(
'label'=>'previous',
'required'=>false,
'ignore'=>false,
'order'=>9000
));
$cancel = new Zend_Form_Element_Submit('cancel',array(
'label'=>'Cancel',
'required'=>false,
'ignore'=>false,
'order'=>9003
));
$next = new Zend_Form_Element_Submit('next',array(
'label'=>'Next',
'required'=>false,
'ignore'=>false,
'order'=>9002
));
$finished = new Zend_Form_Element_submit('finish',array(
'label'=>'Finish',
'required'=>false,
'ignore'=>false,
'order'=>9004
));
$submitControls = array();
echo var_dump($this->getCurrentSubForm()->getName());
switch($this->getCurrentSubForm()->getName())
{
case 'billInfo':
$submitControls = array(
$next,
$cancel
);
break;
case 'payerInfo':
$submitControls = array(
$previous,
$next,
$cancel
);
break;
//So on for other subforms
}
$this->setSubmitButtonGroup($submitControls);
}
In my parent class, Form_Multipage, I have
public function setSubmitButtonGroup(array $elements)
{
$this->_submitButton = $elements;
}
And
public function addSubmitButtonGroupToSubForm(Zend_Form_SubForm $subForm)
{
$subForm->addElements($this->_submitButton);
return $subForm;
}
Which is called when I render the "page" of the form with this function
public function prepareSubForm($spec)
{
if (is_string($spec)) {
$subForm = $this->{$spec};
} elseif ($spec instanceof Zend_Form_SubForm) {
$subForm = $spec;
} else {
throw new Exception('Invalid argument passed to ' .
__FUNCTION__ . '()');
}
$subform = $this->setSubFormDecorators($subForm);
$subform = $this->addSubmitButtonGroupToSubForm($subForm);
$subform = $this->addSubFormActions($subForm);
$subform->setMethod($this->getMethod());
return $subForm;
}
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.