Custom Zend Form Slider Element

In one of my web projects, I had a need for a slider UI element. I use Zend framework extensively in all my projects, and my initial approach was to use pure JQuery coupled with $_POST variables to interact with a JQueryUI slider.

The validation criterion was that a slider HAD to be selected: If the user wanted to indicate a zero value, the slider had to be dragged and brought back to zero.

Very soon, I realized that while the JQuery/php code worked well, it required increasing amounts of code in the controller and view to handle form validation. Also, maintaining the state across form submissions became a laborious process. Error highlighting/reporting became difficult, and all these mundane activities took up lots of space and time. Of course, the problems only grew worse with increasing number of sliders on the page.

I recently refactored my code and arrived at a much better solution: Create a custom zend form element named “slider”, and let the Zend framework handle the grunt work of state persistence and error management – things it was designed to do in the first place.

Requirements: The main requirements of the slider element are summarized below:

1. The ability to create and work with sliders in a way similar to other standard form controls (text/radio etc). For example:

$slider1 = $this->createElement('slider', 'clinical_experience');
$slider1->setLabel('How do you rate your clinical experience?');
$slider1->setRequired();
$this->addElement($slider1);

2. The setRequired() validation should be performed by the framework, and slider state should be retained between form postings.

3. It should be possible to customize left and right anchors of the sliders to reflect varying end points(default least-most).

4. It should be possible to customize the min and max values of each slider (default range from 0-100).

Now that our requirements are clearly defined, we move on to coding the form element. In order to work with the slider, we actually create a combination of a slider div AND a hidden form element. The hidden element will help us get the posted value easily and also help maintain state.

We establish a simple naming convention for associated hidden fields – give their name attribute a value of “controlname-text”

For instance the slider above named “clinical_experience” will have a hidden field named “clinical_experience-text” that holds the value of the slider selection (the numerical value of the slider is set via javascript)

The slider class extends Zend_From_Element_Xhtml. This gives our custom slider element access to all of Zend frameworks’ support for form elements. We also create setter methods for the left and right anchor and the min/max slider values. The init() method sets appropriate defaults (should the user choose not to use the setters while creating the form).

Note that the form helper element (bolded in the code below) is set to ‘SliderElement’ – This will be defined by us next. The framework takes care of passing the control name, value, attribs array and options array to the helper.

<?php
class ZF_Slider extends Zend_Form_Element_Xhtml
{
    public $helper = 'sliderElement';
    public $options = array();
    public function init()
    {
        $this->setMinValue(0);
        $this->setMaxValue(100);
        $this->setLeftAnchor('Least');
        $this->setRightAnchor('Most');
    }
    public function setLeftAnchor($left)
    {
        $this->options['left'] = $left;
    }
    public function setRightAnchor($right)
    {
        $this->options['right'] = $right;
    }
    public function setMinValue($min=0)
    {
        $this->options['min']=$min;
    }
    public function setMaxValue($max=100)
    {
        $this->options['max']=$max;
    }
    public function isValid($value, $context = null)
    {
        //We have named our hidden text field name-text.. get its value
        $field = $this->_name."-text";       
        
        $value = $context[$field];
        $this->setValue($value);
        if (($value === null) or ($value==-1))
        {
            $value=null;
        }
        return parent::isValid($value, $context);
    }

}
?>

The control name is available via $this->_name. Therefore the corresponding hidden element can be retrieved using $this->_name.”-text”

The isValid() method is overridden as shown in the code above. It gets passed a value and a context (which comprises all the posted form values). We extract the hidden field value (based on our naming convention) and assign it to the value of our control ($this->setValue()).

If the value is null or -1, we set the value to null. Finally we make a call to the parent’s isValid() method. This takes care of responding appropriately to missing/null values.

The view helper for our newly created form element is displayed below (SliderElement.php). The “Sliderelement” helper takes care of “visually” displaying our custom control. First, a set of div tags is established. The first div tag establishes the left anchor, followed by a placeholder for the slider (rendered by jquery), followed by the div for the right anchor (I use Blueprint css for positioning)

<?php
class Zend_View_Helper_SliderElement extends Zend_View_Helper_FormElement
{
    //all these input parameters are passed by the Zend_Form_Element
    public function SliderElement($name, $value=null, $attribs=null, $options=null)
    {
        $str ='<div class="box">
            <div class="span-3">' . $options['left'] . '</div>
            <div class="span-9" id="' . $name . '"></div>
            <div class="span-3">' . $options['right'] . '</div>
                <input type="hidden" value="' . $value . '" id="' .
                $name . '-text" name="' . $name . '-text"/>
            </div>';
        //$value is injected into the javascript.. passing null will result in js error!
        $value=($value == null)?-1:$value;
        $str.=$this->SliderGen($name, $value,$options['min'],$options['max']);
        return $str;
    }

    //return a javscript string that renders the slider using 
    //jquery ui..
    private function SliderGen($id, $value,$min,$max)
    {
        $str = '<script>
    $(document).ready(function(){';
        $str.="
            \$(\"#$id\").slider({
    animate: true ,
    range: \"min\",
    value: $value,
    min: $min,
    max: $max,
    slide: function(event, ui) {\$(\"#$id-text\").val(ui.value)}
    });";
        $str.= "\$(\"#$id\").css('cursor', 'pointer');";
        $str.='});
        </script>';
        return $str;
    }
}
?>

The internal (private) method SliderGen() emits javascript code to render the slider using JQuery. The “slide” function/callback populates the hidden text box with the slider selection.

Please look up the JQueryUI site if you need more information regarding the syntax for displaying the slider component on a web page.

Finally, we are ready to create our Zend form. The code is as shown below (forms/slider.php):

<?php
class Form_Slider extends Zend_Form
{
    public function init()
    {
        //tell this form where to find our slider element
        $this->addPrefixPath('ZF', 'ZF','element');
        
        $slider1 = $this->createElement('slider', 'clinical_experience');
        $slider1->setLabel('How do you rate your clinical experience?');
        $slider1->setRequired();
        $slider1->setLeftAnchor('Poor');
        $slider1->setRightAnchor('Excellent');
        $slider1->setMinValue(100);
        $slider1->setMaxValue(500);
        $this->addElement($slider1);
        
        $slider2 = $this->createElement('slider', 'teaching_experience');
        $slider2->setRequired();
        $slider2->setLabel('How do you rate your teaching experience?');
        $slider2->setLeftAnchor('Poor');
        $slider2->setRightAnchor('Excellent');
        $this->addElement($slider2);
            
        $submit = $this->createElement('submit', 'next');
        $submit->setIgnore(true);
        $submit->setLabel('Next');
        $this->addElement($submit);
    }
}
?>

The “addPrefixPath()” command is used to instruct set the actual location of our ‘slider’ component (plugin). This class is named “ZF_Slider” and is in the path /library/ZF/slider.php

We create two sliders using the “slider” component that we just created.. Set all the required values and add it to Form_Slider.

Finally, to tie it all together and generate some output, we create an indexController. It simply initiates our form class, passes it on to the view and dumps the response when the form is submitted:

<?php
class IndexController extends Zend_Controller_Action
{
    public function indexAction()
    {
       $form = new Form_Slider();
       $this->view->form = $form;
       if ($this->_request->isPost())
       {
           if ($form->isValid($_POST))
           {
               var_dump($form->getValues());
           }
       }
    }
}
?>

Since all the heavy weight lifting has been delegated to the Zend form, the view (index.phtml) is a simple one-liner:

<?php
echo $this->form;
?>

That’s it! No additional javascript coding and messing with posted values (Note that a reference to both JQuery and JQueryUI is necessary to make this function.. I setup all this in my bootstrap and render it on my layout.phtml)

This is what the rendered form looks like (note that I use blueprint css for positioning):

 

clip_image002

When the form is submitted without interacting with either of the sliders, the standard error messages (setRequired())are displayed underneath the appropriate controls.

clip_image004

When selections are made and the “Next” button is hit, the posted values are displayed as expected:

clip_image006

Note that when we initialized the “clinical_experience” slider, we had min and max set at 100 and 500 respectively. The posted value reflects that. Notice also how the state of the slider is maintained after post back.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s