I've spent hours trying to figure out all the pieces to the puzzle of
dependent selects.  Here's my solution, in part gathered form messages on
the zend framework mailing list.

Typical problem:  A form with country and city selects.  Selecting a country
should automatically populate the city select.  Pre-existing values for
country and city should automatically be shown in the form.


Step 1.  Define a country data source. 

Country select will be based on data from a Zend_Db_Table_Abstract.  I've
defined a generic "lookup table" resource which defines a getLookup method
for returning name/value pairs:

  public function getLookup($filterName = null, $filterValue = null)
  {   
    $select = $this->select();
    if (null !== $filterName && null !== $filterValue)
    {
      $select->where($filterName.' = '.$filterValue);
    }
    $select->order($this->_order);
    return $this->fetchAll($select);
  }
  
So when you add your options in the form:

    $country = new Community_Resource_Country();
    $countrySelect = new
Zend_Dojo_Form_Element_FilteringSelect('countryId');        
    foreach ($country->getLookup() as $c) {
      $countrySelect->addMultiOption($c->countryId, $c->name);
    }


Step 2.  Define a city data source which can be filtered by country ID.
  
You need a way to query and return JSON-formatted data sets.  Create a
specialized controller or add an action to a controller that handles this:

  public function lookupAction()
  {
  $filterName = $this->_request->getParam('filterName');
  $filterValue = $this->_request->getParam('filterValue');

  // Again we're using a generic lookup table class
  $_model = new Community_Resource_City();
  $data = $_model->getLookup($filterName, $filterValue);
  
  if (null !== $data) {
    $items = array();
    foreach ($data as $row) {
    $items[] = array('label' => $row->name, 'name' => $row->name, 'key' =>
$row->cityId);
  }
  
  $final = array(
    'identifier' => 'key',
    'items' => $items,
  );
  echo Zend_Json_Encoder::encode($final);
  }

Because this controller or controller action doesn't need to return any
fancy stuff (layout, css, etc.), we can disable the layout and view
renderer:

  public function preDispatch()
  {    
    // Disable for entire controller.  Otherwise, put these calls into your
action function
    $this->_helper->layout()->disableLayout();
    $this->_helper->viewRenderer->setNoRender(true);
  }  


Step 3.  Ensure your form is dojo-enabled.

class Myproject_Form_Member_Base extends SF_Form_Abstract
{
  public function init()
  {
    // Dojo-enable the form:
    Zend_Dojo::enableForm($this);


Step 4.  Configure your form elements to use the data sources:

    // Country select using a lookup resource
    $country = new Community_Resource_Country();
    $countrySelect = new
Zend_Dojo_Form_Element_FilteringSelect('countryId');    
    $countrySelect->setLabel('Location')
         ->setRequired(true);
    $countrySelect->addMultiOption('', '');
    foreach ($country->getLookup() as $c) {
      $countrySelect->addMultiOption($c->countryId, $c->name);
    }
    // IMPORTANT:  Whenever the country is changed, the data store 
    // for the dependent city select should also change
    $countrySelect->setAttrib('onchange', 'dijit.byId("cityId").store = new
dojo.data.ItemFileReadStore({ url:
"/data/lookup/table/city/filterName/countryId/filterValue/" +
dijit.byId("countryId").value });');
    $this->addElement($countrySelect);
    

    $city = new Community_Resource_City();
    $citySelect = new Zend_Dojo_Form_Element_FilteringSelect('cityId');
    $citySelect->setAutocomplete(true);
    $citySelect->setLabel('City');
    $citySelect->setRequired(true);
    $citySelect->setStoreId('city');
    $citySelect->setStoreType('dojo.data.ItemFileReadStore');  // important!
    $this->addElement($citySelect);    

    
Step 5.  Pre-populate the city select with data.

At this point the city select won't be populated until the country select is
changed.  But what if we already know the initial country ID?  Zend_Form
won't automatically populate the city select for us!

Therefore, in the controller of our action, after we create the form we need
to programmatically populate the city select:

    // set the dojo city lookup based on country id
       $countryId = $this->_getParam('countryId',false); 
       // Note that for my project, id's can never be 0, so I can use the
empty function
       if (!empty($countryId)) {
      // memberForm is a reference to the form we will display
     
$this->view->memberForm->getElement('cityId')->setStoreParams(array('url' =>
'/data/lookup/table/city/filterName/countryId/filterValue/'.$memberValues['countryId']));
      $this->view->defaultCityId = $countryId;
    }
    

Step 6.  Set the value of the city select.  What if we already have a value
for city?  (For instance, a user is editing his profile, which already has a
city value.)

Unfortunately, simply calling $citySelect->setValue($cityId) won't work! 
(This is currently a bug listed in Zend.)  This may be due to the fact that
the city FilteringSelect has not yet been populated with data.

So, we need some way to set the value of the select AFTER it has been
populated.

In our controller, set a variable in the view with the value of our country:

        $this->view->defaultCityId = $this->_getParam('cityId',false);

In our view script that shows the form:

<?=$this->memberForm ?>

<? if (!empty($this->defaultCityId)) :  ?>
<script type="text/javascript">
dojo.addOnLoad(function() {
  dijit.byId('cityId').attr('value','<?=$this->defaultCityId; ?>');
});
</script>
<? endif; ?>  

Note that I put this script AFTER I output the form.  Why?  Zend will
automatically output a dojo.addOnLoad which loads your city select
ItemFileReadStore.  You cannot set the value of your select BEFORE the
ItemFileReadStore is loaded, so we need to set the city value later.

Check the source of your page in the browser.  Make sure you set the value
of your city select AFTER the dojo.addOnLoad which loads your
ItemFileReadStore.


Now your form should not only have dependent selects, but also pre-populate
the city select correctly.

Derek


-- 
View this message in context: 
http://www.nabble.com/How-to%3A--Zend-Dojo-Form-Dependent-Selects-%28e.g.-Country-City%29-tp25269984p25269984.html
Sent from the Zend Framework mailing list archive at Nabble.com.

Reply via email to