16 Feb 2010

Easy chained select using jQuery

jQuery 2 Comments

Whilst working on Project Trackr, I had the need to be able to easily add chained selects within a form, more specifically, I wanted selecting an account or a client from a drop down list to load another drop down list beneath it with users in that account. An easy way to do this would be to register a listener on a given ID, when something in that dropdown is selected, fire an AJAX event and then load the response contents into a pre specified div. Easy enough and it works, but I wasn’t satisfied.

I wanted a solution which was a simple as adding a class to the first drop down, that’s it. A couple of minutes later, I had a working solution, and I find it to be very useful, so thought I would share it with you.

The solution is made up of 3 parts, the HTML, JavaScript and the HTML, which I will now go through one at a time.

The HTML

There is absolutely nothing fancy here, at all, so much so, that I’ve decided to not bother with the rest of the form/HTML page and just show the relevant field.

<p>
	<label for="form_client">Client</label>
	<?php echo form_error('client_id')?>
	<?php echo HTML::client_dropdown($clients, false, array('class'=>'chain_user'))?>
</p>

A couple of quick points here, I wrap each form field, label and error (not shown here) in a set of paragraph tags, which allows me to style them using CSS. The client_dropdown function returns a select element containing the clients passed in. This results in something like

<select class="chain_user" id="form_client">
	<option></option>
	<option value="1">Account 1</option>
	<option value="2">Account 2</option>
	<option value="3">Account 3</option>
	<option value="4">Account 4</option>
	<option value="5">Account 5</option>
	<option value="6">Account 6</option>
</select>

The Javascript

I’m a huge fan of jQuery, so obviously that’s what I’m using here. The code itself is well commented, so it should be pretty self explanatory

var SITE_URL	= 'http://localhost/project/';
var LOADING		= '<img src="'+SITE_URL+'images/loading.gif" class="loading" />';

$(document).ready(function() {
	/**
	 * Listen out for chain events
	 * In this case, any select which has a class "chain_user"
	 * It will load a dropdown with all the users in that account beneath it
	 */
	$('select.chain_user').change(function() {
		// First, we need the account ID
		// Leave now if this is not set (They selected the empty item at the top)
		var account_id = $(this).val();
		if(account_id == '') return false;

		// Initialise any objects we need now or later
		// Create these now as $(this) will not be the real this in the response
		var parent = $(this).parent();
		var next = parent.next();

		// We need to put a loading gif in place of where the users drop down will be
		// We need to make sure we don't already have an AJAX loaded dropdown
		// otherwise we could end up with several - Disaster!
		if(next.hasClass('loaded-via-ajax')) {
			next.html(LOADING+' Loading users');
		} else {
			var p = new jQuery('<p class="loaded-via-ajax">'+LOADING+' Loading users</p>');
			parent.after(p);
			// We need to update what "next" is so that it works within the AJAX response
			next = parent.next();
		}

		// Now use AJAX to fetch a dropdown with all the users
		$.ajax({
			type: 'GET',
			url: SITE_URL+'account/ajax/get_users_dropdown/'+account_id,
			success: function(response) {
				next.html(response);
			}
		});
	});
});

The PHP

I’ve used PHP here, but you could use any server side language you want here. The code which gets called by the AJAX function is

$account = new Account_model($account_id);
$users = $account->get_users();

// We also need spit out a label
echo '<label for="form_user">User</label>';
echo HTML::user_dropdown($users);

The user_dropdown function is similar to the client_dropdown function I used to generate the first dropdown and is shown below

public static function user_dropdown($users, $selected = false, $attributes = array()) {
	// Merge the incoming attributes with the defaults
	$defaults = array(
		'name'=>'user_id',
		'title'=>'Select the user from this list',
		'id'=>'form_user'
	);
	$attributes = array_merge($defaults, $attributes);

	// Take care of any attributes we have
	$tmp = '';
	foreach($attributes as $key=>$value) {
		$tmp.= ' '.$key.'="'.$value.'"';
	}

	// Build up the select item
	$str = '<select'.$tmp.'>';
	foreach($users as $user) {
		$str.= '<option value="'.$user->id.'"'.($selected == $user->id ? ' selected="selected"' : '').'>'.$user->first_name.' '.$user->last_name.'</option>';
	}
	$str.= '</select>';
	return $str;
}

And that’s all there is to it! I hope you find this useful

2 Responses to “Easy chained select using jQuery”

  1. Paul Norman says:

    Nice article, well written and okay for admin systems (perhaps), but what about users without JavaScript enabled, or people using text based browsers / screen readers etc. When writing software for professional use this is crucial. There should always be a non-JS driven option (perhaps a form button from next to the drop down – which would do the same action upon submit – which could be hidden using JS and an extra get variable to your AJAX page checking if it is used by AJAX or statically called)…

    Aside, you might want to check http://www.roomguide.co.uk/ with JS off and try to run a search!

    Best of luck and keep up the dev / articles though! (the CI autocomplete one is great – though you might want to remind people not to use this in their production code, no sense in loading all those classes each time online!)

  2. Damian says:

    Hi Paul, thanks for the comment
    You make a very valid point about needing a non JS option to all back on. I should have made it clear that in the case I am using it, it is a “closed” environment where I made a concious decision to only support modern browsers and JS – for the time being at least.

    I agree that if thi was using in a “real” website then there would need to be an alternative, then the method you suggest is a good option. If you ever get round to extending thi with your suggestion then please do share.

    I need to rewrite parts of the CI autocomplete article based on some of the comments to make it a bit clearer. it doesn’t actually load all those classes as you do it in the base file which won’t get executed (definately need to rewrite that part as you’re not the onlt one to think that would happen)

Leave a Reply