On fly elements in Zend_Form

How to create and modify elements in Zend_Form on fly?

Lets say we have a form that creates a customer address. The form has the following fields:

  • Country (select element)
  • Street name (text element)
  • Region/State (text element)
  • City (text element)
  • Postal code (text element)

When the user selects a country, we want to check our database to see if we have a list of regions that the user can select from.

If we have the list, then the system will replace the current text element for region, to a select element of the regions.

Also, we want to maintain the field after we submit the form, so we don’t lose the regions select element.

Here is the use case :

Basic Flow

  1. User selects a country: “New Zealand”.
  2. System sends an Ajax request to the server to check if it has a list of regions.
  3. System finds regions. Creates a select element then returns it to the browser.
  4. System replaces the current element in the form with the new element from the server.
  5. User fills out all the fields and submits the form.
  6. System saves the form data into the database.

Alternative Flows

On 3: System could not find regions.

  • System creates a text element and returns it
  • System replaces the current element in the form with the new element from the server.

On 4: System returns that the form input for Postal code is incorrect.

  • System analyses the form submission and renders the form elements correctly. If we have select element for regions, the system will generate it.

How to achieve the above?

Here is the form class

Satrn_Form_Address extends Zend_Form 
{
	public function init()
	{
		// form elements here....
		
		// region element
		$this->addElement('text', 'region', array(
			'label' => 'Region',
			'attribs' => array(
				'maxlength' => 50
			),
			'validators' => array(
				array('StringLength', false, array(1,50)),
				array('String'),
			),
		));		
	}
}

Here is the controller code

class CustomerController extends Zend_Controller_Action
{
   public function newAddressAction()
   {
      $this->_helper->pageTitle('Create New Address');
      $form = new Satrn_Form_Address();

      if ($this->getRequest()->isPost())  {
         if($form->isValid($this->getRequest()->getPost())) {
            // save
            $model = new Satrn_Model_Customer();
            $id = $model->saveAddress($form->getValues());

            $this->_helper
                 ->messenger('success', 'New Address Saved.');
         }
      }
      $this->view->form = $form;
   }
}

The code that needs to be added to the form class to do what we require.

First override the method “isValid” in Zend_Form

public function isValid($values)
{
	$values = $this->_modifyElements($values);	
	return parent::isValid($values);
}

Then create a method, in my example it is “modifyElements”. Add all the needed code to modify the elements in form class. Code commented.

protected function _modifyElements($values)
{ 
        // search for states
        $countryModel = new Satrn_Model_Country();
        $regions = $countryModel->getRegions($values['country_id']);
        // if we don’t have any regions for the selected country id
       // then no modification needed and return the data.
        if($regions ->count() == 0) {
            return $values;
        }
        // create an array from the Zend_Db_Rowset data. 
        // Where the keys are the ids of the regions
        $options = $regions ->toPairs('region_id', 'region_name');
     
        // remove the current region element
        $this->removeElement(‘region');
		
	// create a new element. 
	$this->addElement('select', ' region', array(
		'label' => 'Regions',
		'attribs' => array(
			'class' => 'countryRegionSelect'
		),
		'validators' => array(
			array('inArray', false, array(array_keys($options)))
		),
		'multiOptions' => $options,
		'order' => 2,
		'value' => $values['region'],
	));
	// add element to a group if you want
	$this->getDisplayGroup('addressdetails')
	     ->addElement($this->getElement('region'));
            // if you add an element to a group then unset
            // it from the form elements stack
	unset($this->_order['region']);
	return $values;
}

Why do we add the element to the form, then add it to a group, then reset it from the form?

Because when you add the element to the group directly, it will not be added to the form elements stack $_order. This means if you validate the form elements with the method “isValid”, your new created element will not be validated. Also, if you use the method “getValues” to return all the values of the form elements, the new element will not be included.

The other method that needs to be modified is the “populate” method

public function populate(array $values)
{
	$values = $this->_modifyElements($values);	
	return parent::populate($values);
}

It’s the same as the “isValid” method.

You’re all done. The form works the way you expected :).

Any comments?

Posted in PHP, Zend Framework and tagged , .
Loading Facebook Comments ...

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

One Comment

  1. Thank you so much for this article! I spent quite some hours trying to use an ajax-generated select element in my ZF project, in vain as at form processing time the new element was “not existing” anymore being added in the view with javascript.
    Thank you Mohamed ;>