Dynamic/Chained Selects using Ajax Prototype/JQuery

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Is this useful to you

Yes
7
64%
No
1
9%
Would be if you added... (Please post suggestions)
3
27%
 
Total votes: 11

User avatar
CoderGoblin
DevNet Resident
Posts: 1425
Joined: Tue Mar 16, 2004 10:03 am
Location: Aachen, Germany

Dynamic/Chained Selects using Ajax Prototype/JQuery

Post 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
Last edited by CoderGoblin on Mon Feb 26, 2007 6:02 am, edited 6 times in total.
User avatar
ok
Forum Contributor
Posts: 393
Joined: Wed May 31, 2006 9:20 am
Location: The Holy Land

Post by ok »

This code helped me understanding how Prototype works. Thanks!
User avatar
CoderGoblin
DevNet Resident
Posts: 1425
Joined: Tue Mar 16, 2004 10:03 am
Location: Aachen, Germany

Post 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:
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post 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)
User avatar
CoderGoblin
DevNet Resident
Posts: 1425
Joined: Tue Mar 16, 2004 10:03 am
Location: Aachen, Germany

Post 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.
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post by matthijs »

That's very cool CoderGoblin. thanks for taking the time to put together the second example.
User avatar
gonds
Forum Newbie
Posts: 7
Joined: Wed Dec 20, 2006 3:28 am
Location: Bandung, Indonesia
Contact:

Post 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:
User avatar
potato
Forum Contributor
Posts: 192
Joined: Tue Mar 16, 2004 8:30 am
Location: my lovely trailer, next to the big tree

Post 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>';
?>
User avatar
potato
Forum Contributor
Posts: 192
Joined: Tue Mar 16, 2004 8:30 am
Location: my lovely trailer, next to the big tree

Post by potato »

sorry, stupid question :oops: :)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post 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]);
(#10850)
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post 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.
User avatar
CoderGoblin
DevNet Resident
Posts: 1425
Joined: Tue Mar 16, 2004 10:03 am
Location: Aachen, Germany

Post 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.
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Post 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());
		});
	});
User avatar
onion2k
Jedi Mod
Posts: 5263
Joined: Tue Dec 21, 2004 5:03 pm
Location: usrlab.com

Post 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.
User avatar
CoderGoblin
DevNet Resident
Posts: 1425
Joined: Tue Mar 16, 2004 10:03 am
Location: Aachen, Germany

Post 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.
Post Reply