Page 1 of 2

Dynamic/Chained Selects using Ajax Prototype/JQuery

Posted: Wed Dec 06, 2006 3:13 am
by CoderGoblin
As it comes up a lot I thought I'd put together this quick little helper for those who want to use something similar. To keep this simple I do not get information from a database although this is easily changed. I used the javascript prototype library although there are many "AJAX" libraries out there. You may want to do some of your own research or check out Forum Poll:Which AJAX toolkit do you use?.

The technique used could equally create tables or any other HTML on the fly, set session information, cart items whatever.

You basically require 3 files (in this case all in the same directory but you could always add paths to the code)...
index.php (name not important but your "caller" php).
sublist.php (again name is up to you but need to change the code to reflect any change).
prototype.js (loaded from here

The file contents (Will not go into prototype javascript library).

index.php

Code: Select all

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <script src="prototype.js" type="text/javascript"></script>
 <script type="text/javascript">
  function getSecond(value) {
    var url = 'sublist.php';
    var myAjax = new Ajax.Request
      (
        url,
        {
          method: "post",
          parameters : "first="+value,            
          onSuccess: function transResult (response) {
            document.getElementById('sublist').innerHTML=response.responseText;                        
          },
          onFailure: function transResult (response) {
            alert ('Failure'+response.responseText); 
          }
        }
      );
      return false;
  }
  </script>

</head>
<body>
  <span id="firstlist">
    <select name="first" onchange="getSecond(this.value);">
      <option value="1">Names starting with A</option>
      <option value="2">Names starting with B</option>
      <option value="3">Names starting with C</option>
      <option value="4">Names starting with D</option>
      <option value="5">Names starting with E</option>      
    </select>
  </span>
  <span id="sublist"><?php include 'sublist.php'; ?></span>
</body>
</html>
sublist.php

Code: Select all

<?php
// Define the result list possibilities. This could easily be retrieved from a database
$sublist=array(1=>array('Anna','Andrew','Andrea','Annakin'),
               2=>array('Bill','Bob','Bernie'),
               3=>array('Charles','Camila','Connie','Constance'),
               4=>array('Daniel','Darla','Danny'),
               5=>array('Edmund','Edgar','Elisabeth','Eugene','Emma','Emily'));

// Process
$value=1;
if (!empty($_POST['first'])) {
    if (isset($sublist[$_POST['first']])) $value=$_POST['first'];
}
$name_list='';
foreach ($sublist[$value] as $name) {
    $name_list.="<option value=\"{$name}\">{$name}</option>\n";
}
echo '<select name="second">'.$name_list.'</select>';
?>
Thats all for now, hope it helps someone... Additional prototype information can be found Here

Posted: Thu Dec 07, 2006 2:38 pm
by ok
This code helped me understanding how Prototype works. Thanks!

Posted: Wed Dec 13, 2006 3:32 am
by CoderGoblin
Thanks, always nice to know wasn't a wasted effort. I've now included a poll. If you post additional wishlist requirements I will see what I can do, although I'm not promising anything :wink:

Posted: Wed Dec 13, 2006 5:37 am
by matthijs
That's pretty cool. Thanks for the example.

What I would be interested in is how you would have to change the code to work without javascript. Ok, sounds silly when we are dealing with prototype. But the point is that if I would like to use something like this I would build it in a way that it works without js to start with (user reloads page in a normal way to get the wanted effect). Then on top of that, IF a user has javascript enabled, he gets the "enhanced" version (without the need to reload the page).

Thinking about it, it would probably need some rethinking about how the design should look/work so it's probably too complicated for a basic example. But I guess you get the idea. Maybe doing something like I would like to see is an idea for another example (of course if I have the time I will give it a shot)

Posted: Wed Dec 13, 2006 5:59 am
by CoderGoblin
Thanks for your comment matthijs. The following changes should do the trick.

index,php

Code: Select all

<?php
  // $value will be available during the include of sublist.php
 $value=0;
 if (!empty($_GET['sublist'])) {
    $value=floor($_GET['first']);
 }
?> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <script src="prototype.js" type="text/javascript"></script>
 <script type="text/javascript">
  function getSecond(value) {
    var url = 'sublist.php';
    var myAjax = new Ajax.Request
      (
        url,
        {
          method: "post",
          parameters : "first="+value,           
          onSuccess: function transResult (response) {
            document.getElementById('sublist').innerHTML=response.responseText;                       
          },
          onFailure: function transResult (response) {
            alert ('Failure'+response.responseText);
          }
        }
      );
      return false;
  }
  </script>
</head>
<body>
  <form>
    <span id="firstlist">
      <select name="first" onchange="getSecond(this.value);">
        <option value="1" <?php if ($value==1) echo " selected"; ?> >Names starting with A</option>
        <option value="2" <?php if ($value==2) echo " selected"; ?> >Names starting with B</option>
        <option value="3" <?php if ($value==3) echo " selected"; ?> >Names starting with C</option>
        <option value="4" <?php if ($value==4) echo " selected"; ?> >Names starting with D</option>
        <option value="5" <?php if ($value==5) echo " selected"; ?> >Names starting with E</option>     
      </select>
    </span>
    
    <!-- This is for those with no javascript -->
    <noscript><input type="submit" name="sublist" value="Find..." /></noscript> 
  
    <span id="sublist"><?php include 'sublist.php'; ?></span>
  </form>
  <noscript><br />This page is enhanced by javascript which is currently not available</noscript>
</body>
</html>
sublist.php

Code: Select all

<?php
// Define the result list possibilities. This could easily be retrieved from a database
$sublist=array(1=>array('Anna','Andrew','Andrea','Annakin'),
               2=>array('Bill','Bob','Bernie'),
               3=>array('Charles','Camila','Connie','Constance'),
               4=>array('Daniel','Darla','Danny'),
               5=>array('Edmund','Edgar','Elisabeth','Eugene','Emma','Emily'));

// Process
if (empty($value)) $value=1;
if (!empty($_POST['first'])) {
    if (isset($sublist[$_POST['first']])) $value=$_POST['first'];
}
$name_list='';
foreach ($sublist[$value] as $name) {
    $name_list.="<option value=\"{$name}\">{$name}</option>\n";
}
echo '<select name="second">'.$name_list.'</select>';
?>
Obviously this is only meant as an example. In a real life example you would also need to process other form data but this keeps things simple.

Posted: Wed Dec 13, 2006 10:05 am
by matthijs
That's very cool CoderGoblin. thanks for taking the time to put together the second example.

Posted: Fri Dec 22, 2006 12:42 am
by gonds
thanks for the example... it's very helpfull

by the way... is there no manual or something about prototype?
if no, why we don't start to write that thing... :idea:

but first "CoderGoblin" must teach us first... :roll:

Posted: Sat Jan 06, 2007 9:57 am
by potato
Ok, looks good. But how i can give a different value and label with the select options?
Like Anna would have '1' as value, Andrew has '2', etc...

Code: Select all

<?php
// Define the result list possibilities. This could easily be retrieved from a database
$sublist=array(1=>array('Anna','Andrew','Andrea','Annakin'),
               2=>array('Bill','Bob','Bernie'),
               3=>array('Charles','Camila','Connie','Constance'),
               4=>array('Daniel','Darla','Danny'),
               5=>array('Edmund','Edgar','Elisabeth','Eugene','Emma','Emily'));

// Process
if (empty($value)) $value=1;
if (!empty($_POST['first'])) {
    if (isset($sublist[$_POST['first']])) $value=$_POST['first'];
}
$name_list='';
foreach ($sublist[$value] as $name) {
    $name_list.="<option value=\"{$name}\">{$name}</option>\n";
}
echo '<select name="second">'.$name_list.'</select>';
?>

Posted: Sat Jan 06, 2007 1:55 pm
by potato
sorry, stupid question :oops: :)

Posted: Sat Jan 06, 2007 1:59 pm
by Christopher
I really like this code (and Protoype), but I would push more of the View responsibilities for HTML generation into the Javascript. That way the PHP script is clearly just the datasource providing the list of options. I use pipe delimited here, but you could use something else like XML.

It also removes any embedded PHP from the script so it has the implementation flexibility to be index.html or index.php.

Here is index.html

Code: Select all

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <script src="prototype.js" type="text/javascript"></script>
 <script type="text/javascript">
   // creates select HTML from a name string and an array of options
   function toSelect(name, options) {
		str = "<select name=\"" + name + "\">\n";
		var n = options.length;
		for (i=0; i<n; i++) {
			str += "<option value=\"" + options[i] + "\">" + options[i] + "</option>\n";
		}
		str += "</select>\n";
		return str;
   }
   
   function getSecond(value) {
    var url = 'sublist.php';
    var myAjax = new Ajax.Request
      (
        url,
        {
          method: "post",
          parameters : "first="+value,           
          onSuccess: function transResult (response) {
 			// split the response text on the delimiter to create an array
 			options = response.responseText.split("|");
 			document.getElementById('sublist').innerHTML = toSelect("second", options);                       
          },
          onFailure: function transResult (response) {
            alert ('Failure'+response.responseText);
          }
        }
      );
      return false;
  }
  </script>

</head>
<body>
  <span id="firstlist">
    <select name="first" onchange="getSecond(this.value);">
      <option value="1">Names starting with A</option>
      <option value="2">Names starting with B</option>
      <option value="3">Names starting with C</option>
      <option value="4">Names starting with D</option>
      <option value="5">Names starting with E</option>     
    </select>
  </span>
  <span id="sublist"><script type="text/javascript"> 
        // no more PHP in the code
        getSecond(0);
   </script></span>
</body>
</html>
And sublist.php

Code: Select all

<?php
// Define the result list possibilities. This could easily be retrieved from a database
$sublist=array(1=>array('Anna','Andrew','Andrea','Annakin'),
               2=>array('Bill','Bob','Bernie'),
               3=>array('Charles','Camila','Connie','Constance'),
               4=>array('Daniel','Darla','Danny'),
               5=>array('Edmund','Edgar','Elisabeth','Eugene','Emma','Emily'));

// Process
if (isset($_POST['first'])) {
	$value = (int) $_POST['first'];
} else {
	$value = 1;
}
if (! isset($sublist[$value])) {
	$value = 1;
}
echo implode('|', $sublist[$value]);

Posted: Sat Jan 06, 2007 3:51 pm
by matthijs
The only - and for me not so small - drawback to this variant is that without javascript the functionality is gone. With the previous version there was a nice fall-back function.

In my opinion you would have to start with a non js system, and - if js is available - Hijack the normal process and use (h)ajax.

Posted: Sun Jan 07, 2007 6:18 am
by CoderGoblin
I agree, any web site should be tested without javascript and should be able to be used. I have recently been looking into accessibility for the disabled which includes switching off javascript and also styles. Makes life a lot more interesting/frustrating to browse a site, but this is what some people have to use.

Posted: Mon Feb 26, 2007 5:35 am
by Kieran Huggins
At CoderGoblin's request, here's the equivalent script written in jQuery:

index,php

Code: Select all

<?php
  // $value will be available during the include of sublist.php
 $value=0;
 if (!empty($_GET['sublist'])) {
    $value=floor($_GET['first']);
 }
?> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <script src="/js/jQuery.js" type="text/javascript"></script>
 <script type="text/javascript">
	$(function(){	
		$('#firstlist select').change(function(){
			$.post('sublist.php',{first: $(this).val()},function(data){
				$('#sublist').html(data);
			});
		});
	});
  </script>
</head>
<body>
  <form>
    <span id="firstlist"> <!-- now the onChange event is being applied with javascript -->
      <select name="first">
        <option value="1" <?php if ($value==1) echo " selected"; ?> >Names starting with A</option>
        <option value="2" <?php if ($value==2) echo " selected"; ?> >Names starting with B</option>
        <option value="3" <?php if ($value==3) echo " selected"; ?> >Names starting with C</option>
        <option value="4" <?php if ($value==4) echo " selected"; ?> >Names starting with D</option>
        <option value="5" <?php if ($value==5) echo " selected"; ?> >Names starting with E</option>     
      </select>
    </span>
    
    <!-- This is for those with no javascript -->
    <noscript><input type="submit" name="sublist" value="Find..." /></noscript> 
  
    <span id="sublist"><?php include 'sublist.php'; ?></span>
  </form>
  <noscript><br />This page is enhanced by javascript which is currently not available</noscript>
</body>
</html>
sublist.php

Code: Select all

<?php
// Define the result list possibilities. This could easily be retrieved from a database
$sublist=array(1=>array('Anna','Andrew','Andrea','Annakin'),
               2=>array('Bill','Bob','Bernie'),
               3=>array('Charles','Camila','Connie','Constance'),
               4=>array('Daniel','Darla','Danny'),
               5=>array('Edmund','Edgar','Elisabeth','Eugene','Emma','Emily'));

// Process
if (empty($value)) $value=1;
if (!empty($_POST['first'])) {
    if (isset($sublist[$_POST['first']])) $value=$_POST['first'];
}
$name_list='';
foreach ($sublist[$value] as $name) {
    $name_list.="<option value=\"{$name}\">{$name}</option>\n";
}
echo '<select name="second">'.$name_list.'</select>';
?>
the code with comments:

Code: Select all

	// attach the behaviour at onLoad:
	$(function(){	
		// apply the following onChange event to all elements that match the "#firstlist select" CSS selector
		$('#firstlist select').change(function(){
			// make an ajax POST request to the server with "first" set to the value of the select
			$.post('sublist.php',{first: $(this).val()},function(data){
				// set the innerHTML of the element "#sublist" (css selector again) to the data returned
				$('#sublist').html(data);
			});
		});
	});

If the $_REQUEST or $_GET array was used in sublist.php, I could have shortened the jQuery code to:

Code: Select all

	$(function(){
		$('#firstlist select').change(function(){
			// load the result of the ajax request into "#sublist"
			$('#sublist').load('sublist.php?first='+$(this).val());
		});
	});

Posted: Mon Feb 26, 2007 6:02 am
by onion2k
It's not very useful if you can't make one of the options 'selected'.

On a personal note, and it's no reflection on the code posted, I don't like using innerHTML. It's supported widely but it's not part of any W3C spec. I rewrite option boxes using proper DOM scripting.

Posted: Mon Feb 26, 2007 6:28 am
by CoderGoblin
onion2k wrote:It's not very useful if you can't make one of the options 'selected'.
Which option box would you suggest... Do you mean the first or second one ? On a live site this would depend very much on the source of the lists. I have deliberately kept things as simple as possible (hence no database query) here as it was instigated as a simple demonstration to introduce a solution to the chained selection problem frequently asked for.
onion2k wrote:On a personal note, and it's no reflection on the code posted, I don't like using innerHTML. It's supported widely but it's not part of any W3C spec. I rewrite option boxes using proper DOM scripting.
I have to admit, I agree :oops: . I have to admit though I used innerHTML for two reasons...

1. I am not that good at javascript and wanted to produce the initial thing quickly to answer a question on this forum :wink:
2. Adding extra javascript, whilst it may be good practice, may detract from people getting to grips with what is going on in the code as it stands. I wanted as much as possible as PHP.

I'll take your comments on board and modify the code accordingly when I get time.