Using Zend Studio with Zend Framework = super fast / easy development.

Modifying Solar_View_Helper_Form

May 23rd, 2009 by Ray in Cookbook, PHP/MySQL, Solar | Tags: , | No Comments

Solar comes packed with many different view helpers that will make your life easier, such as inserting images, creating links, and generating forms. However, sometimes a generic helper doesn’t cut it for custom behavior in a project. For example, I wanted to change the way form field hints (field descriptions) and error messages were displayed for the registration form at http://fahwebmon.white-box.us. Note that I am using Solar_View_Helper_Form::auto() to generate my form automagically. The only downside to this is that you do not have control over where errors are displayed on a page.

The default behavior of Solar_View_Helper_Form will output error messages like this:

default_behavior

However, I had two things I needed to accomplish, 1) create form field hints that are displayed only when a text field is clicked, and 2) move all error messages to the top of the page.

Displaying the field hints was the easy part. The form helper automatically adds in a class named ‘descr’ to each field. I decided to use this class name with a little jQuery (focus and blur) to accomplish my form hint behavior.

The issue I ran into was when the form had errors and a field containing a hint was displayed.

default_behavior_hint

You can see that the hint should be aligned with the first text field, Username. A little bit of Javascript and CSS manipulation could solve this problem but I wanted to consolidate the error messages to an eye catching location anyway, which in turn would solve my alignment issue.

Before I provide you with my solution, let me show you what the finished product looks like:

modified_behavior_hint

You can see I have an eye catching error div at the top followed by the hint box being properly aligned. I still need to implement either a) a red asterisk next to each invalid field or b) change the label colors to red. I also need to update my error messages so they read proper. Both of these minor changes are not solve in this blog post. I’ll save that for a rainy day ;-)

netbeans_screenshotSo, how did I do this? I simply copied down Solar_View_Helper_Form from the Solar project to my working directory (Fahwebmon/View/Helper/Form.php) and renamed the class to Fahwebmon_View_Helper_Form. Note that you don’t have to copy down the whole file. You can create your own class and extend Solar_View_Helper_Form since we are only modifying the fetch() method on this class.

Here is the modified code. Note that I have only tested this with text fields.

public function fetch($with_form_tag = true)
{
// stack of output elements
$form = array();
$feedback = array();
// the form tag itself?
if ($with_form_tag) {
$form[] = '<form' . $this->_view->attribs($this->_attribs) . '>';
}
// what status class should we use?
if ($this->_status === true) {
$class = $this->_css_class['success'];
$feedback = $this->_feedback;
} elseif ($this->_status === false) {
$class = $this->_css_class['failure'];
} else {
$feedback = null;
$class = null;
}
// the hidden elements
if ($this->_hidden) {
// wrap in a hidden fieldset for XHTML-Strict compliance
$form[] = '    <fieldset style="display: none;">';
foreach ($this->_hidden as $info) {
$form[] = '        ' . $this->_view->formHidden($info);
}
$form[] = '    </fieldset>';
$form[] = '    ';
}
// loop through the stack
$in_dl       = false;
$in_fieldset = false;
$in_group    = false;
foreach ($this->_stack as $key => $val) {
$type = $val[0];
$info = $val[1];
if ($type == 'element') {
// be sure we're in a <dl> block
if (! $in_dl) {
$form[] = '        <dl>';
$in_dl = true;
}
// setup
$label    = $this->_view->getText($info['label']);
$id       = $this->_view->escape($info['attribs']['id']);
$method   = 'form' . ucfirst($info['type']);
try {
// look for the requested element helper
$helper = $this->_view->getHelper($method);
} catch (Solar_Class_Stack_Exception_ClassNotFound $e) {
// use 'text' helper as a fallback
$method = 'formText';
$helper = $this->_view->getHelper($method);
}
// SPECIAL CASE:
// checkboxes that are not in groups don't get an "extra" label.
if (strtolower($info['type']) == 'checkbox' && ! $in_group) {
$info['label'] = null;
}
// get the element output
$element = $helper->$method($info);
// get the element description
$dt_descr = '';
$dd_descr = '';
// only build a description if it's non-empty, and isn't a
// DESCR_* "empty" locale value.
if ($info['descr'] && substr($info['descr'], 0, 6) != 'DESCR_') {
// build the base description.
// open the tag ...
$descr = "<" . $this->_view->escape($this->_descr_tag);
// ... add a CSS class ...
if ($this->_descr_class) {
$descr .= ' class="'
. $this->_view->escape($this->_descr_class)
. '"';
}
// ... add the raw descr XHTML, and close the tag.
$descr .= '>' . $info['descr']
. '</' . $this->_view->escape($this->_descr_tag) . '>';
// build both the <dt> and <dd> forms so we can use
// them in the right place.
if ($this->_descr_elem == 'dt') {
// using <dt>
$dt_descr = $descr;
} else {
// using <dd>
$dd_descr = $descr;
}
}
// add the element;
// handle differently if we're in a group.
if ($in_group) {
// add the element itself
$form[] = "                $element";
} else {
$require = '';
// is the element required?
if ($info['require']) {
$require = ' class="' . $this->_css_class['require'] . '"';
}
// add the form element with all parts in place
$form[] = "            <dt$require><label$require for=\"$id\">$label</label>$dt_descr</dt>";
$form[] = "            <dd$require>$element$dd_descr</dd>";
$form[] = '';
}
// handle element feedback
if (!empty($info['invalid'])) {
foreach ($info['invalid'] as $invalid) {
$feedback[] = $label . ' ' . strtolower(substr($invalid, 0, 1) . substr($invalid, 1));
}
}
} elseif ($type == 'group') {
// be sure we're in a <dl> block
if (! $in_dl) {
$form[] = '        <dl>';
$in_dl = true;
}
$flag = $info[0];
$label = $info[1];
if ($flag) {
$in_group = true;
$form[] = "            <dt><label>$label</label></dt>";
$form[] = "            <dd>";
} else {
$in_group = false;
$form[] = "            </dd>";
$form[] = '';
}
} elseif ($type == 'fieldset') {
$flag = $info[0];
$legend = $this->_view->getText($info[1]);
if ($flag) {
$form[] = "    <fieldset><legend>$legend</legend>";
$form[] = "        <dl>";
$in_fieldset = true;
$in_dl = true;
} else {
$form[] = "        </dl>";
$form[] = "    </fieldset>";
$form[] = '';
$in_dl = false;
$in_fieldset = false;
}
}
}
if ($in_group) {
$form[] = '            </dd>';
}
if ($in_dl) {
$form[] = '        </dl>';
}
if ($in_fieldset) {
$form[] = '    </fieldset>';
}
// add a closing form tag? This also determins where feedback should be.
if ($with_form_tag) {
// temporarily grab the open form tag and remove it from the array
$openFormTag = array_shift($form);
// add the form open tag and feedback
array_unshift($form, $openFormTag, $this->listFeedback($feedback, $class));
// finally add the closing form tag
$form[] = '</form>';
} else {
// inject feedback as first element
array_unshift($form, $this->listFeedback($feedback, $class));
}
// reset for the next pass
$this->reset();
// done, return the output!
return implode("\n", $form);
}

Leave a Reply