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.
Related
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 building an application using Zend_Framework and I would like to ask you for an advice regarding Zend_Form. All forms on the site should be decorated with tables, instead of definition lists. My idea is that I should create a common form (with no elements) and then just instantiate it across the site and add elements as needed. This common form would need to modify the standard decorators, so that tables are used instead of definition lists. How do you suggest to do this? Override Zend_Form's addElement() method so that it alters decorators of new elements?
But there's another caveat. There should be an option of using a different set of decorators for a particular element, if needed. So I'm a bit puzzled how to do this. Do you have any advice?
There is no simple way to override the default decorators. The solution I use is to override all the elements and redefine the loadDefaultDecorators method.
The problem is that each element have a specific set of decorator. For example, the hidden element needs only the ViewHelper decorator while file element needs File, Errors, Description, HtmlTag (td), Label (th), HtmlTag (tr).
You can also use Zend_Form::setElementDecorators at the end of your init method (after calls to addElement). But you need to customize it for each form...
Use an intermediate class for your project wide configuration. You will then extend this class instead of Zend_Form
file My/Form.php
<?php
abstract class My_Form extends Zend_Form {
public function __construct ( $options = null ) {
parent::__construct($options);
$this->setElementDecorators(array(
// the base <input, <select, <textarea markup
'ViewHelper',
// wrap that into a <div class="input-wrap" />
array (
'HtmlTag',
array (
'tag' => 'div',
'class' => 'input-wrap',
)
),
// append errors in <ul/li>
'Errors',
// then prepend <label markup
'Label',
));
}
}
then in file My/Form/Demo.php
<?php
class My_Form_Demo extends My_Form {
public function init () {
// Your elements here
}
}
You can do this for specific element as well
file My/Form/Element/Group.php
<?php
class My_Form_Element_Group extends Zend_Form_Element_Select {
public function init () {
// Specific options
$this->addMultiOptions(array(
'A' => 'group A',
'B' => 'group B',
));
// This element doesn't need the div.input-wrap
$this->removeDecorator('HtmlTag');
}
}
Is it possible to alter an html attribute of a Zend_Form_Element in a Decorator previously added ?
Lets say I have a decorator named RichTextArea. When I add it to a Zend_Form_Element_Textarea, I want the decorator to add the class "rich" to the textarea.
The final output should look like this :
<textarea name="content" id="content" class="rich" />
It is possible, but the syntax depends a little on how you are building the form. Easiest way is to do it on the element itself as you add it:
$element = new Zend_Form_Element_Text('something');
$element->class = 'rich';
$form->addElement($element);
or if you mass-assigned the decorators, e.g.:
$element = new Zend_Form_Element_Text('something');
$element->setDecorators(array(
'Errors',
'Label',
array(array('row' => 'HtmlTag'), array('tag' => 'div'))
));
[...]
$decorator = $element->getDecorator('row');
$decorator->setOption('class', 'rich');
If you are using a rich text editor like TinyMCE or similar, another option might be to create a custom form element that extends Zend_Form_Element_Textarea and always add your class to it.
It's possible to add any HTML-Attribute with
// #var Zend_Form_Element $element
$element->setAttribute($key, $value);
But you also can access the Attributes as a property like
$element->key = $value;
For more Information read this Section in Zend-Documentation: http://framework.zend.com/manual/1.12/en/zend.form.elements.html#zend.form.elements.metadata
I am using two decorator
- To get tabular form alignment
- To get date picker (ZendX_JQuery_Form_Element_DatePicker)
both are working individually, but not at a same time
Error:
Warning: Exception caught by form: Cannot render jQuery form element without at least one decorator implementing the 'ZendX_JQuery_Form_Decorator_UiWidgetElementMarker' interface. Default decorator for this marker interface is the 'ZendX_JQuery_Form_Decorator_UiWidgetElement'. Hint: The ViewHelper decorator does not render jQuery elements correctly.
My Get Form Function:
$form = new Form_Job();
$form->setDecorators(Decorator::$formDecorators);
$form->setElementDecorators(Decorator::$elementDecorators);
$form->getElement('submit')->setDecorators(Decorator::$buttonDecorators);
Form class Form_Job()
class Form_Job extends ZendX_JQuery_Form {
public function init() {
$element = new ZendX_JQuery_Form_Element_DatePicker('date_from');
$element->setLabel('Campaign Period From :');
$element->setRequired(true);
$element->setAttrib('size', '10');
$element->setJQueryParam('dateFormat', 'yy-mm-dd');
$this->addElement($element);
}
}
I got this help from http://framework.zend.com/manual/en/zend.form.decorators.html
jQuery Decorators: Beware the Marker
Interface for UiWidgetElements
By default all the jQuery Form
elements use the
ZendX_JQuery_Form_Decorator_UiWidgetElement decorator for rendering the jQuery
element with its specific view helper.
This decorator is inheritly different
from the ViewHelper decorator that is
used for most of the default form
elements in Zend_Form. To ensure that
rendering works correctly for jQuery
form elements at least one decorator
has to implement the
ZendX_JQuery_Form_Decorator_UiWidgetElementMarker
interface, which the default decorator
does. If no marker interface is found
an exception is thrown. Use the marker
interface if you want to implement
your own decorator for the jQuery form
element specific rendering.
But i need code to implement this, please suggest
Got my answer:-
I used
public static $formJQueryElements = array(
array('UiWidgetElement', array('tag' => '')), // it necessary to include for jquery elements
array('Errors'),
array('Description', array('tag' => 'span')),
array('HtmlTag', array('tag' => 'td')),
array('Label', array('tag' => 'td', 'class' =>'element')),
array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
);
$form->getElement('jq_date')->setDecorators(Decorator::$formJQueryElements);
this works well for tabular alignment, for jquery elements !!!!!