Sunday, March 7, 2010

Dependent Selects using Joomla's javascript library – changeDynaList()

Dependent selects can be created using Joomla's built-in javascript library
(see includes > js > joomla.javascript.js )
using the changeDynaList() function.
 
So what I want to achieve here is that by selecting a Department, the Subdepartment dropdown gets dynamically populated.


There are 2 scenarios – a new course being added and an existing course being edited. In a new course, I only have to list the Departments to begin with. The Subdepartment select would be empty until a Department is chosen. In the second scenario, an edit of an existing course, I want the values of the department and subdepartment to display in the selects.
In this example, I'm working in the Admin section of the Model for the component I'm creating. I want to generate JHTMLSelect genericLists which will be returned from the Model to the View for display in the template.

The basic method – in the Model

  1. Use Joomlas generic list function to create the Department list (named 'didlist'),
  2. Same for the Subdepartment (named 'sidlist')
  3. Create a javascript array called subdeptarray in the form [key, text, value] which the javascript function changeDynaList can work with in the template.

The Department List (in the Model)

Ok, lets suppose you've queried the database for all the Departments, something like this:
$query = 'SELECT id AS did, department FROM #__departments';
$this->_db->setQuery( $query );
$departments = $this->_db->loadObjectList();
You now have the departments object, but we need to do a little work before we can stuff that into the JHTMLSelect genericList function:
  1. I want to add an option at the top of the dropdown that says "Choose Department" with an option value of 0.
  2. The Department select must contain a javascript attribute along the lines of:
    onchange=changeDynaList(param1, param2, etc)
1. Add an option - Choose Department
To add the Choose option I'll use the JHTMLSelect option function to insert an extra option:
object option (string $value, [string $text = ''], [string $value_name = 'value'], [string $text_name = 'text'], [ $disable = false])
as follows:
$deps[] = JHTML::_('select.option', '0', JText::_(' - Choose Department - '), 'did', 'department');
Then I'll merge this with the $departments object I got from the DB query. This will work fine because the option only needs 2 values in the order of key – value and $departments provides both those values in the right order.
$deps = array_merge($deps, $departments); //complete list of html options
2. Add the javascript attribute
Sort out the javascript which will trigger the changeDynaList function (this will be stuffed into the JHTMLSelect genericList function also)
The changeDynaList function is in the format:
function changeDynaList( listname, source, key, orig_key, orig_val )
where:
listname – name of list to change 'sidlist'
source – a javascript array of list options in the form [key,value,text] which we will later program in as $subdeptarray
key – the key to display
orig_key – the original key that was selected
orig_val – the original item value that was selected

Since in this example the list will be shown in an admin template I'll use:
$js = "onchange=\"changeDynaList( 'sidlist', subdeptarray, document.adminForm.didlist.value, document.adminForm.didlist.options[document.adminForm.didlist.selectedIndex].value,
document.adminForm.didlist.options[document.adminForm.didlist.selectedIndex].text);\"";
3. Stuff the lot into Joomla's JHTMLSelect genericList
Then finally stuff it all back into the JHTMLSelect genericList function, noting that the js is appended to the attributes param.
string genericlist (array $arr, string $name, [string $attribs = null], [string $key = 'value'], [string $text = 'text'], [mixed $selected = NULL], [ $idtag = false], [ $translate = false])
as follows:
$lists['departments'] = JHTML::_('select.genericlist', $deps, 'didlist', 'class="inputbox" size="1" '.$js, 'did', 'department', intval($selecteddept));
The last value - $selecteddept – you would use if the user was editing an existing course rather than creating a new one where the value would just be 0. Obviously you could harvest this value in a number of ways in your model. If a department's value is greater than 0, then you're dealing with an edit and you'll also need a value from the subdepartments select. In this example those values are referred to as:
$selecteddept; //0 or id value from the database
$selectedsubdept; //0, or id value from the database
So now $lists['departments'] is ready to be returned to the View.

The Subdepartment List (in the Model)

Again, we would get the complete list of subdepartments from the database in the Model using a query like:
$query = 'SELECT id AS sid, did, subdepartment FROM #__ subdepartments';
$this->_db->setQuery( $query );
$subdepartments = $this->_db->loadObjectList();
Since a course can be added directly to a department (with or without subdepartment – like Joomla articles can be added directly to Sections without a Category), I need to program in 2 extra options. The first option is the usual 'Choose Subdepartment', value '0', and the second is 'None' value '-1' since courses can be added directly to department, without reference to a subdepartment.
To get your head around it at this stage, what we're programming here is the initial state of the dependent subdepartment select when the form first opens. That's why we can't use array_merge here. What's seen in the Subdepartment select is entirely dependent on the state of the Department select.
If no department has been selected (ie new course), then the value of $selecteddept is also 0 and there won't be any other values in the select for the subdepartment.
On the other hand, if a department has been selected (eg editing an existing course) then we not only have to add 'None' as an extra option, but also list only those subdepartments that come under the department chosen in the department select.
1. Program in the Choose Subdepartment option
$subdeps[] = JHTML::_('select.option', '0', JText::_(' - Choose Subdepartment - '), 'sid', 'subdepartment');
2. Program in the None option
if ((int) $selecteddept != 0) {
$subdeps[] = JHTML::_('select.option', '-1', JText::_('None'), 'sid', 'subdepartment');
}
3. Program in the rest of the options for the subdepartment
foreach ($subdepartments as $subdepartment) {
if ($selecteddept == $subdepartment->did) {
$subdeps[] = JHTML::_('select.option', $subdepartment->sid, $subdepartment->subdepartment, 'sid', 'subdepartment');
}
}
Ok, now we're ready to stuff this into JHTMLSelect genericList as follows:
$lists['subdepartments'] = JHTML::_('select.genericlist', $subdeps, 'sidlist', 'class="inputbox" size="1" ', 'sid', 'subdepartment', intval($selectedsubdept));
And now the subdepartment list is ready to be returned to the View.

The subdeptarray (in the model)

The final requirement is to program an array mapping all the possible department keys to subdepartment text and values. We'll do this in PHP in the model and transfer it to the view for templating into its final form which will be javascript.
In the final form (javascript in the template) it will show something like this:
var subdeptarray = new Array();// [key,value,text]
subdeptarray[0] = new Array( '6','0',' - Choose Subdepartment - ' );
subdeptarray[1] = new Array( '6','-1','None' );
subdeptarray[2] = new Array( '6','1','Ancient History' );
subdeptarray[3] = new Array( '6','5','Medieval History' );
subdeptarray[4] = new Array( '6','4','Modern History' );
etc
Where, say, 6 is the History department id ('did'), the next value is the subdepartment id ('sid') and the final value is the name of the subdepartment).
The changeDynaList javascript function requires that the var subdeptarray is written in the format [key,value,text]
Back to the model, we'll pick up the 2 objects that we got from the original queries which contain the entire department list and the entire subdepartment list – ie $departments and $subdepartments and generate the php version of the subdeptarray to return the view. The first foreach just creates a '0' and '-1' option value for each of the subdepartments, in line with the lists generated to display the subdepartment select. The second foreach generates the list of subdepartments options present.
foreach ($departments as $department) {
$subdeptarray [$department->did][] = JHTML::_('select.option', '0', JText::_(' - Choose Subdepartment - '), 'sid', 'subdepartment');
$subdeptarray [$department->did][] = JHTML::_('select.option', '-1', JText::_('None'), 'sid', 'subdepartment');
}
foreach ($subdepartments as $subdepartment) {
$subdeptarray [$subdepartment->did][] = JHTML::_('select.option', $subdepartment->sid, $subdepartment->subdepartment, 'sid', 'subdepartment');
}
return $subdeptarray;

Displaying $subdeptarray in the Template

Presuming your View has picked up the 2 lists (departments, subdepartments) and $subdeptarray, then all that's left is to display them in the template for the view (eg default.php)
default.php:
<?php
defined('_JEXEC') or die('Restricted access'); ?>
<script language="javascript" type="text/javascript">
<!--
var subdeptarray = new Array(); // array in the format [key,value,text]
<?php
$i = 0;
foreach ($this->subdeptarray as $k=>$items) {
foreach ($items as $v)
echo "subdeptarray[".$i++."] = new Array( '$k','".addslashes( $v->sid )."','".addslashes( $v->subdepartment )."' );\n\t\t";
}
?>
//-->
</script>
<form action="fill-this-in" method="post" name="adminForm" id="adminForm">
<table class="admintable">
<tbody>
<tr>
<td width="100" align="right" class="key">
<label for="department">
<?php echo JText::_( 'Department' ); ?>:
</label>
</td>
<td>
<?php
echo $this->lists['departments'];
?>
</td>
</tr>
<tr>
<td width="100" align="right" class="key">
<label for="subdepartment">
<?php echo JText::_( 'Subdepartment' ); ?>:
</label>
</td>
<td>
<?php
echo $this->lists['subdepartments'];
?>
</td>
</tr>
</tbody>
</table>
</form>
etc
References: Joomla 1.5 API JHTMLSelect class
http://api.joomla.org/Joomla-Framework/HTML/JHTMLSelect.html

1 comment: