I have a Zend_Form whose sub-form is only required in certain circumstances. The parent form and the sub-form both have required fields. The sub-form will not always be filled but when any of its elements are filled, they should all be filled.
<?php
class Cred extends Zend_Form
{
public function init()
{
$title = new Zend_Form_Element_Text('Title');
$title->setLabel('Title')
->setRequired(TRUE);
$this->addElement($title);
$award = new Zend_Form_Element_Text('Awarded');
$award->setLabel('Awarded On')
->setRequired(TRUE)
->addValidator('date');
$this->addElement($award);
$subform = new Zend_Form_SubForm();
$proof = new Zend_Form_Element_File('Documentation');
$proof->setLabel('Documentation')
->setRequired(TRUE)
->addValidator('Size', false, 409600) // limit to 400K
->addValidator('Extension', false, 'pdf');
$subform->addElement($proof);
$lang = new Zend_Form_Element_Select('Language');
$lang->setLabel('Language')->setRequired(TRUE);
$subform->addElement($lang);
$this->addSubForm($subform,'importForm');
$submit = new Zend_Form_Element_Submit('submitForm');
$submit->setLabel('Save');
$this->addElement($submit);
$this->setAction('/cred/save')
->setMethod('post')
->setEnctype(Zend_Form::ENCTYPE_MULTIPART);
}
}
When I call $form->isValid($_POST), it validates both the parent form and the sub-form and returns errors when the subform's required elements are empty even when the sub-form itself is not required.
Other than overloading the isValid() function, is there any way to validate only the parent form?
If you look into the source code the isValid() method of Zend_Form you see that there is no explicit mechanism that prevents the execution of the validators on the subforms (line 2273 ff).
Anyway, If I understand your requirement "The sub-form will not always be filled but when any of its elements are filled, they should all be filled." correctly then I think your problem does not necessarily have something to do with subforms per se but rather with conditional validation. This can be solved pretty easy with a custom validator: How to validate either Zend_Form_Element_File or Zend_Form_Element_Text are not empty.
Just keep in mind that the elements in $context contain only the subform elements.
Related
1st way to creating form elements like below -
*In Controller
$email = new Zend_Form_Element_Text('email');
$email->setLabel('Email address')
->addFilter('StringToLower')
->setRequired(true)
->addValidator('NotEmpty', true)
->addValidator('EmailAddress');
2nd way to creating form elements like below -
*In View
echo $this->formText('email',$this->data['email'],array('size'=>'20', 'class'=>'input_min_max'));
Is there possible to create element in 2nd method with same as in 1st method, Like adding addFilter,addValidator etc. . setLabel is not required becoz we have added the label just before the element ?
Yes it is possible, you can execute this code in the view :
$email = new Zend_Form_Element_Text('email');
$email->setLabel('Email address')
->addFilter('StringToLower')
->setRequired(true)
->addValidator('NotEmpty', true)
->addValidator('EmailAddress');
But you shouldn't because it's violating the MVC pattern.
Or maybe you could explain better what you want to do... maybe I didn't understand...
I' pretty sure you cannot create an element with your second method, but there is another way to create it by factoring, in your case you can do :
$formText = createElement('email',$this->data['email'],array('size'=>'20', 'class'=>'input_min_max)->setRequired(true)->addValidator('whatever');
Hope this will answer your question.
Not sure why you would want to insert the form element in the view. If this is for styling, you can use a view script as the decorator.
The form :
$email = new Zend_Form_Element_Text('email');
$email->setLabel('Email address')
->addFilter('StringToLower')
->setRequired(true)
->addValidator('NotEmpty', true)
-> setDecorators(array(array('ViewScript', array('viewScript' => 'index/myform.phtml'))))
->addValidator('EmailAddress');
This will call the view script "myform.phtml" for decorating the element, inside which you can add the rest of the markup for decorating the element.
'm trying to use setBelongsTo in form, but Zend does not validate the values and not setDefaults that field. Have searched on google and found nothing. Any idea?
Form:
$this->setName('form_tecnicos')
->setAttrib('onSubmit',"return validarForm()");
$elementos['nome'] = new Zend_Form_Element_Text('nome');
$elementos['nome']->setLabel('Nome')
->setRequired(true)
->setAttrib('maxlength','150');
$elementos['telefone0'] = new Zend_Form_Element_Text('0');
$elementos['telefone0']->setLabel('Telefones')
->setRequired(true)
->setAttribs(array('mascara'=>'tel','maxlength'=>14))
->setBelongsTo('telefones');
$elementos['telefone1'] = new Zend_Form_Element_Text('1');
$elementos['telefone1']->setLabel('Telefones')
->setRequired(true)
->setAttribs(array('mascara'=>'tel','maxlength'=>14))
->setBelongsTo('telefones');
$elementos['submit'] = new Zend_Form_Element_Submit('Entrar');
$elementos['submit']->setLabel('Entrar');
$this->addElements($elementos);
$filters = array('StringTrim');
$this->setElementFilters($filters);
$this->setElementDecorators(array(
'viewHelper',
'Errors',
array(array('data'=>'HtmlTag'), array('tag' => 'dd')),
array('Label',array('tag'=>'dt')),
array(array('row'=>'HtmlTag'),array('tag'=>'div','class'=>'grid_2'))
));
$remover_label = array('submit');
foreach($remover_label as $elementos)
$this->$elementos->removeDecorator('label');
Controller
$form = new Application_Form_Tecnicos();
$this->view->form = $form;
if($this->getRequest()->isPost()):
$dados = $this->getRequest()->getPost();
if($form->isValid($dados)):
//
else:
$form->setDefaults($dados);
endif;
endif;
Print_r($dados)
Array ( [nome] => [telefones] => Array ( [0] => (11) 1111-111 ) [Entrar] => Entrar )
I'm stuck on it
You have two issues which are actually unrelated to setBelongsTo. If you just want the short answer, change the relevant part of your form code to this which works:
$elementos['nome'] = new Zend_Form_Element_Text('nome');
$elementos['nome']->setLabel('Nome')
->setRequired(true)
->setAttrib('maxlength','150');
$elementos['telefone0'] = new Zend_Form_Element_Text('telefone0');
$elementos['telefone0']->setLabel('Telefones')
->setRequired(true)
->setAttribs(array('mascara'=>'tel','maxlength'=>14))
->setBelongsTo('telefones');
$elementos['telefone1'] = new Zend_Form_Element_Text('telefone1');
$elementos['telefone1']->setLabel('Telefones')
->setRequired(true)
->setAttribs(array('mascara'=>'tel','maxlength'=>14))
->setBelongsTo('telefones');
Here's the longer explanation:
There are two ways you can add form elements to a Zend Form object. You can either instantiate the relevant form element object and add that to the form:
$form->addElement(new Zend_Form_Element_Text('name'));
or you can call addElement with the relevant params and get it to create the object for you:
$form->addElement('text', 'name');
If you use the first approach, it gets the element name from the object. If you use the second, you are passing in the name as the second parameter. The name is what Zend_Form uses internally to store the element, and it's also how it knows which submitted data relates to which element.
When you add elements all at once with addElements(), it will use the array key (if non-numeric) as the name. So your first issue is that this:
$elementos['telefone0'] = new Zend_Form_Element_Text('0');
stores the name as telefone0, but the actual form element uses the name 0. So when the form is submitted it always fails validation because Zend_Form is expecting the data to be in $_POST['telefone0'].
Your second issue is much simpler. Form element names need to work as PHP variables, so you can't use a numeric name (such as the 0 and 1 you had for the telephone fields).
In the working code above all that was required was changing the names of the two telephone elements to match the key in $elemntos, which fixes both problems.
I am trying to validate Zend_Form which has several optional fields and I want at least one of them to be filled in. In my case I have mobile, home and office phone numbers and I want at least one of them to be provided.
I am trying to achieve this though Validation Context (as suggested here) by creating custom validator which extends Zend_Validate_Abstract. The problem is that if all optional fields are empty they are missing from the form $context (passed to the validator class) and this way not validated at all.
So if you fill any or several of the three options (mobile, home, work) they are all going to be validated (which is fine, but for this no custom validator is needed), but if you fill none of them, there is no option to force the customer to fill at least one of the fields (which is my aim).
Here is what I have:
1. my form
<?php
class Application_Form_Application extends Zend_Form {
public function init() {
$this->setName('application');
// attach sub forms to main form
$this->addSubForms(array(
'application' => $this->application(),
...
));
}
private function application() {
$application = new Zend_Form_SubForm();
// custom phone validation
$phone_validation = array('phone_mobile' => 'Mobile', 'phone_home' => 'Home', 'phone_work' => 'Work');
// phone mobile
$app['phone_mobile'] = new Zend_Form_Element_Text('phone_mobile');
$app['phone_mobile']->setLabel('Mobile')
->addFilter('StripTags')
->addFilter('StringTrim')
->addValidator('Regex', false, array('/^[0-9]{8}$/i'))
->addValidator(new Application_Form_PhoneMobileHomeWork($phone_validation), false);
// phone home
$app['phone_home'] = new Zend_Form_Element_Text('phone_home');
$app['phone_home']->setLabel('Home')
->addFilter('StripTags')
->addFilter('StringTrim')
->addValidator('Regex', false, array('/^[0-9]{8}$/i'))
->addValidator(new Application_Form_PhoneMobileHomeWork($phone_validation), false);
// phone work
$app['phone_work'] = new Zend_Form_Element_Text('phone_work');
$app['phone_work']->setLabel('Work')
->addFilter('StripTags')
->addFilter('StringTrim')
->addValidator('Regex', false, array('/^[0-9]{8}$/i'))
->addValidator(new Application_Form_PhoneMobileHomeWork($phone_validation), false);
$application->AddElements($app);
}
}
?>
2. custom validator
<?php
class Application_Form_PhoneMobileHomeWork extends Zend_Validate_Abstract {
const NOT_PRESENT = 'notPresent';
protected $_messageTemplates = array(
self::NOT_PRESENT => 'At least one contact phone shall be provided!'
);
protected $_listOfFields;
public function __construct(array $listOfFields) {
$this->_listOfFields = $listOfFields;
var_dump($listOfFields);exit;
}
public function isValid($value, $context = null) {
var_dump($context);exit;
...
}
?>
The validator always passes though the first dump ($listOfFields), but if I remove it, isValid() is never called unless some data is typed into some of the phone fields (which we want to prevent).
When I checked further I found a solution in extending the Zend_Validate class by passing empty fields to the $context parameter, but I would like to have a better solution if someone knows any.
Concluding it in short - how to validate certain form, forcing the user to fill at least one out of several optional fields?
If I understand you right, you want your form elements to not be required, but prevent them to be empty (except if one of them is not empty) using a custom validator? Then, in order to not skip the validation chain, you need to prevent them to be empty calling the method setAllowEmpty(false) in each of your elements.
Finally, in your custom validator, you will have something like this:
foreach ($this->_listOfFields as $field) {
if (isset($context[$field]) AND $context[$field])
{
return true;
}
}
Also, make sure your elements are not required (setRequired(false)).
The problem is that if any field is filled, the multi_checkbox element doesn't exists in the form, and then it won't be validated.
One solution is the follow:
Use a hidden option always checked and validate that this always is checked this more one of the others.
I have an element. I want to add a custom validator and custom filter to it. The validator makes sure the input is one of several permitted values, then the filter adds some custom values to the input. This means I have to validate the original input first before running the filter. I do it in this order
$element = new Zend_Form_Element_Text('element');
$element->addValidator('PermittedValue', false);
$element->addFilter('TotalHyphen', false);
$this->addElement($element);
but this order isn't being respected. The filter runs first and changes the data, then the validator runs on the filtered data which means it always fails even for valid input. It seems from documentation that this is intentional
Note: Validation Operates On Filtered
Values Zend_Form_Element::isValid()
filters values through the provided
filter chain prior to validation. See
the Filters section for more
information.
How can I specify the order in which validators and filters run?
Sure seems like creating a custom element that supports post-validation filtering would be the way to go. How about this:
/**
* An element that supports post-validation filtering
*/
class My_Form_Element_PostValidateFilterable extends Zend_Form_Element_Text
{
protected $_postValidateFilters = array();
public function setPostValidateFilters(array $filters)
{
$this->_postValidateFilters = $filters;
return $this;
}
public function getPostValidateFilters()
{
return $this->_postValidateFilters;
}
public function isValid($value, $context = null)
{
$isValid = parent::isValid($value, $context);
if ($isValid){
foreach ($this->getPostValidateFilters() as $filter){
$value = $filter->filter($value);
}
$this->setValue($value);
}
return $isValid;
}
}
Usage would be something like this:
$elt = $form->addElement('PostValidateFilterable', 'myElement', array(
'label' => 'MyLabel',
'filters' => array(
'StringTrim',
// etc
),
'validators' => array(
'NotEmpty',
// etc
),
// here comes the good stuff
'postValidateFilters' => array(
new My_Filter_RunAfterValidateOne(),
new My_Filter_RunAfterValidateTwo(),
),
));
This keeps the validation and filtering in the form - keeping the controller thin.
Not tested, just a stab in the dark. And surely you could fatten/modify the API to add/remove filters by key, etc.
Whaddya think?
Maybe don't add the filter at all. Validate the content first in the controller, and then use the filter separately:
$request = $this->getRequest();
if ($request->isPost() && $form->isValid($request->getParams())) {
$filter = new Filter_Whatever();
$val = $filter->filter($request->getParam('element'));
... //call your model or whatever
}
I've never done this, but I suppose this (or something similar) might work.
Good point ! ,
AFAIK filters should or must run before validating the input :
from ZF docs
It's often useful and/or necessary to
perform some normalization on input
prior to validation. For example, you
may want to strip out all HTML, but
run your validations on what remains
to ensure the submission is valid. Or
you may want to trim empty space
surrounding input so that a
StringLength validator will use the
correct length of the input without
counting leading or trailing
whitespace characters.
but if and only if you are in case which can't solve mingos's answer must be the help
What you want to achieve is to change default behavior of how text element is being processed. Thus, I think you could create your own element (e.g. My_Form_Element_Text) that extends Zend_Form_Element_Text and overload its isValid() method.
Specifically you could just change second line in the orginal isValid() method, from $value = $this->getValue(); into $value = $this->getUnfilteredValue();. This way your validation will be performed using unfiltered values.
I am using Zend Framework. For a particular form, there is not enough space to show the errors next to the form elements. Instead, I want to be able to display the errors above the form. I imagine I can accomplish this by passing $form->getErrorMessages() to the view but how do I disable error messages from being shown be each element?
The proposal above does not take into account that the default decorators may change. Instead of clearing the decorators and then reapplying all except the ones you do not need, it would be better to disable the decorators you don't need at form-initialization time, like:
class My_Form_Login extends Zend_Form
{
public function init()
{
$this->setMethod('post');
$username = new Zend_Form_Element_Text('auth_username');
$username->setLabel('Username')
->setRequired(true)
->addValidator('NotEmpty')
->removeDecorator('Errors')
->addErrorMessage("Please submit a username.");
etc.....
You could then, wherever you use the form, decide how to display the messages (if you're planning to display them apart from your form). Of course if they should be part of the form, just create a suitable decorator and add it to the form-element init method above. Here is a nice tutorial on form decorators from ZendCasts.com
Example of displaying messages apart from the form itself.
$elementMessages = $this->view->form->getMessages();
// if there actually are some messages
if (is_array($elementMessages))
{
foreach ($elementMessages as $element)
{
foreach ($element as $message)
{
$this->view->priorityMessenger($message, 'notice');
}
}
}
The priorityMessenger-helper used above can be found here: http://emanaton.com/code/php/zendprioritymessenger
You can add decorators to form elements using setElementDecorators. Zend_Form has a function called just after init entitled loadDefaultDecorators. In your subclass, you can override this like so:
/**
* Load the default decorators for forms.
*/
public function loadDefaultDecorators()
{
// -- wipe all
$this->clearDecorators();
// -- just add form elements
// -- this is the default
$this->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'dl')),
'Form'
));
// -- form element decorators
$this->setElementDecorators(array(
"ViewHelper",
array("Label"),
array("HtmlTag", array(
"tag" => "div",
"class" =>"element",
)),
));
return $this;
}
Assuming you added your elements in init, that applies those decorators to each element in the form. You'll note the absence of the "Errors" decorator in setElementDecorators. You could also try looping through the form elements and using removeDecorator to remove just the Errors decorator.