Easy chained select using jQuery

logo-jquery

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.

This was a quick and dirty solution to my specific problem. In my case, I don’t need to worry about this not working when JavaScript is disabled. If you need to worry about such things, Paul Norman suggested how this might work in the comments

The solution is made up of 3 parts, the HTML, JavaScript and the PHP, 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

 
Damian Gostomski

My name's Damian and I'm a web developer working for MC2 but am also available for freelance. I've been working on the web since I can remember and now work with (X)HTML, CSS, PHP, JavaScript and .NET. I specialise in WordPress development, including custom theming and plug-ins. You can follow me on Twitter here

 

Comments on Easy chained select using jQuery

  1. Paul Norman February 26, 2010 at 12:23 pm

    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!)

    Reply
  2. Damian February 26, 2010 at 4:01 pm

    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)

    Reply
  3. Curious Onlooker December 4, 2010 at 2:54 pm

    Thanks for this.

    As for Paul N., if you’re going to start picking apart what someone posts, when it’s an obvious starter’s guide, why not publish your own version, rather than trying to show how smart you are.
    What a jerk.

    Thanks again Damian

    Reply
  4. Paul Norman December 4, 2010 at 3:17 pm

    @Curious Onlooker Wow, what an unpleasant and unconstructive comment! Pretty sure I’m not the one who comes off as the ‘jerk’ though…

    I don’t actually remember writing this comment it’s that long ago, but I stand by what I wrote. I don’t see any ‘this is a starter post’ or ‘do not use this in production’ statements in the post and I firmly believe that all code should strive to be usable by everyone in any environment (i.e. without relying on anything client side). That’s all I was getting at. I even offered thoughts on how it could potentially work with the JS as ‘icing on the cake.’

    I don’t believe that Damian was offended by my thoughts, but if you were, in any way, I apologise!

    Reply
  5. Damian December 5, 2010 at 1:04 am

    @Paul Norman & Curious Onlooker

    FIrstly, Paul is correct – I wasn’t offended his comment. I’m sure if he were to try and offend me, he wouldn’t have suggested a possible work around and complimented one of my other posts (which I still need to rewrite!)

    Secondly, Paul is again correct, as this isn’t really a “beginners post” either. Although there is nothing in the code which to me seems “advanced”, the style of the post, and depth of the explanations assumes a knowledge of the used technologies.

    As I’m sure that anyone who posts any sort of code online can testify, there is always the “risk” of being “criticized”. In many cases, these “criticisms” are what push us to become better at what we do, as they help to either fill holes in our own knowledge, or push us to find better solutions, where better can mean more accessible, usable, faster, cleaner code etc.

    When developing for the web, there is always a tradeoff between these factors and time/cost. Especially with the huge range of browsers, users and devices, it’s almost impossible to have a single, one size fits all solution without the huge investment in time/money.

    I’ve updated the post to include a little disclaimer to avoid any further confusion (clearly mentioning it only took me a few minutes to write this code wasn’t enough of a disclaimer!)

    Reply
  6. Dom mody December 19, 2010 at 10:03 pm

    This is amazing stuff and I’m not playing Bazinga! on you here now ;) .

    Reply

Leave a Reply

Required
Required