 |
Multiple Related Selects with ColdFusion and JavaScript
By Steve Durette
Coworkers come to me for ideas on how to do different things in SQL or ColdFusion all of the time. I think that it has something to do with the fact that I love the work I do and therefore I am always reading about it.
This time when one of them came to me, his problem was, “I have some select boxes that are related and I need to know the best way to do it”. My first thought was the two selects related custom tag, and then he hit me with the twists.
First, there were three select boxes that were related and second, there could be multiple sets of these selects. I started thinking that I had heard of a three selects related, but at the same time I thought that in the last few months many people had come to me with related select questions that I redirected them to the custom tags. So then I thought that maybe I should investigate how this could be done.
My first stop was to easycfm.com of course! I found three articles relating to this subject. They were:
· The easiest method for multiple dynamic and dependant drop down lists, period by Ryan M. BeMiller
· Multiple dynamic drop-down selection
boxes by Mike Corbridge
· In search of Dynamic Dependant Lists
by Megan Garrison
These were all great articles on the subject, but they didn’t quite work for what I needed. Ryan’s solution (one that I use quite often) didn’t work because it keeps resubmitting the page to itself. This is great in most instances and was tried by my coworker but didn’t fit our needs, as you will read below. Mike’s and Megan’s solutions helped get me started but they still weren’t quite dynamic enough, using them we would have had to write multiple JavaScript functions to deal with all of the related select boxes.
So I decided to create my own solution for a multi-related select situation that could be repeated multiple times on a page without duplication of the JavaScript that creates it. In this example we will deal with three related select boxes for state, city and address. Some basic ColdFusion and JavaScript knowledge is needed for this article, but I’m hoping that it is written so that most can use it.
The original solution that my co-worker had come up with was to cause the select box to submit the page back to itself every time a change was made (The same as Ryan’s solution). This can be a great tool, but with multiple selects and multiple instances of these selects on the page, the queries and the amount of data that had to keep being passed to the page caused it to slow down.
I knew that we would have a query to get the information; we would need the data from the query moved into JavaScript variables and the JavaScript to change the selects.
To make things easier, there should only be one query that gets us all of the information we need. We could probably use multiple queries, but I wanted to keep this simple. For our test we were using a MSSQL 2000 table with the information that could have multiple records with the same city, state and address (it was tied to an employee table). The query we came up with was:
<cfquery name=”qryLocationInfo” datasource=”#ourDS#”>
select distinct city, state, address
from employee_table
group by state, city, address order by state, city, address
</cfquery>
The distinct along with the group by is probably a little bit of overkill, but I needed to make sure that the returned table was only as big as it absolutely needed to be.
Ok, so we now have the data, it’s time to get it into JavaScript so that we can use it to populate the selects. For what we were about to do I decided to use an array of arrays.
<script language=”JavaScript”>
<!--
//create the initial array to hold the different records.
var aryLocation = new Array();
//Now to bring in the data. This is a combination of
//ColdFusion and JavaScript
<!--- We set a variable for our loop to insert the data. The loop starts at zero because JavaScript arrays start at zero. --->
<cfset Variables.JSLoop =
0>
<cfoutput>
//The next lines create an array of 3 items in the
//outer array. It could hold more than three items
//if we needed more than 3 selects related.
<cfloop query=”qryLocationInfo”>
aryLocation[#Variables.JSLoop#] = new Array(“#qryLocationInfo.state#”, “#qryLocationInfo.city#”,
“#qryLocationInfo.address#”);
<cfset Variables.JSLoop = Variables.JSLoop +
1>
</cfloop>
</cfoutput>
The array is declared and populated outside of any functions. This will make it available for any other functions that we may need to create in the future, plus we will only take the time to set it when the page is initially loaded. Not every time the function is called.
Next step is to create the function that will update the selects. As you may have noticed in the previous code block, the script tag was never closed. That is because the function will be created in the same JavaScript code block.
function tsrUpdSelects(frstSel, scndSel, thrdSel, thisSel) {
//This function takes four arguments.
/*
They will be the frstSel (the state select box), the scndSel (the city select box),
the thrdSel (the address select box) and thisSel (the select box that
actually called for the change to occur).
*/
//local variables
var i; var chkCty =
“”;
//see if we just changed the first select box (state).
if(thisSelect.name == frstSelect.name) {
//set the length of the other selects to zero to empty them
scndSel.options.length = 0;
thrdSel.options.length = 0;
//set the first option for each (being messages to do a select).
scndSel.options[scndSel.length] = new Option(“Choose
City”, “”);
thrdSel.options[thrdSel.length] = new Option(“Choose
Address”, “”);
//Now loop through and set the second option list (city).
//If a state was chosen. There is also code to prevent duplicate
//cities. This could happen if a city had multiple addresses.
if(thisSel.options[thisSel.selectedIndex].value != “”) {
for (i = 0; i < aryLocation.length; i++) {
if(aryLocation[I][0] == thisSel.options[thisSel.selectedIndex].value &&
chkCty.lastIndexOf(aryLocation[i][1] == -1) {
scndSel.options[scndSel.length] = new
Option(aryLocation[i][1],
aryLocation[i][1]); chkCty = chkCty + “,” +
aryLocation[i][1];
}
}
}
}
//see if we just changed the second select box(city).
if(thisSel.name == scndSel.name) {
//set the length of the third select to zero.
thrdSel.options.length = 0;
//set the first option.
thrdSel.options[thrdSel.length] = new Option(“Choose
Address”, “”);
//set the rest of the values if a city was chosen.
for(i=0; i < aryLocation.length; i++) {
if(aryLocation[i][0] ==
frstSel.options[frstSel.selectedIndex].value &&
aryLocation[i][1] ==
scndSel.options[scndSel.selectedIndex].value) {
thrdSel.options[thrdSel.length] = new
Option(aryLocation[i][2],
aryLocation[i][2])
}
}
}
}
//-->
</script>
The function looks kind of large, but if you remove some of the comments and don’t split the lines like I did for this document, then it actually turns out to be quite small. Even though I say that it is quite small, it is still doing a lot of work. It starts with the fact that I built it to receive the three selects as attributes. This ensures that we don’t need to rewrite the function for every set of related selects we want to do. Next, it determines which select did the call to the function. If it was the state, it clears the city and address selects and then populates the city. If it was the city then it clears and populates the address select. If it is the address, we don’t call the function at all. You will also notice that if it is a city it also checks to see if we have already added the current city (in the loop) to the array by using the chkCity variable, this prevents duplicates of the cities.
Now that the JavaScript is complete we can start into the html. We will do 5 sets of selects for testing.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01
Transitional//EN">
<html>
<head>
<title>Multiple three selects related</title>
<!-- Place the JavaScript code here. -->
</head>
<body>
<cfoutput>
<form method=”post”
name=”testForm”>
<table>
<tr>
<th>State</th>
<th>City</th>
<th>Address</th>
</tr>
<cfloop from=”1”
to=”5” index=”Variables.myLoop”>
<tr>
<td>
<cfset stateList = “”>
<select name=”myState#myLoop#”
onChange=”tsrUpdSelects(this, testForm.myCity#myLoop#, testForm.myAddr#myLoop#,
this);”>
<option value=””
selected>Choose State</option>
<cfloop query=”qryLocationInfo”>
<cfif not listFindNoCase(stateList, qryLocationInfo.state,
“,”)>
<option
value=”#qryLocationInfo.state#”> #qryLocationInfo.state#
</option>
<cfset stateList = listAppend(stateList, qryLocationInfo.state,
“,”)>
</cfif>
</cfloop>
</select>
</td>
<td>
<select name=”myCity#myLoop#” onChange=”tsrUpdSelect(testForm.myState#myLoop#, this, testForm.myAddr#myLoop#,
this);”>
<option value=””
selected>Choose City</option>
</select>
</td>
<td>
<select
name=”myAddr#myLoop#”>
<option
value=””
selected>Choose Address</option>
</select>
</td>
</tr>
</cfloop>
</table>
</form>
</cfoutput>
</body>
</html>
That completes all of the code. All you have to do is save it to a file (I used tsrtest.cfm). Make sure that you change the query to suit your needs.
All that you have to do to make it do more selects is add extra ifs to the JavaScript and add extra arguments.
Hopefully this document was of some help to you.
Date added: Fri. November 28, 2003
Posted by: Steven Durette | Views: 18885 | Tested Platforms: CF4,CF5,CFMX | Difficulty: Intermediate
Forms
Every Week has a Wednesday
This tutorial serves two purposes. First it demonstrates how to create a function that is usable in CF5, CFMX and CFMX6.1. It also demonstrates a math and logical way to determine the number of weeks in a month without looping. - Date added: Wed. December 24, 2003
Using ColdFusion Functions to clean up code
In another tutorial inspired by a co-worker, I look at the ability to clean up code using CF Functions in CF5 and CFMX. - Date added: Fri. November 28, 2003
Using ColdFusion for things other than serving web pages
Sometimes, ColdFusion can be used to do tasks that you wouldn't have normally thought of. This tutorial examines one such incident and the ColdFusion solution. - Date added: Fri. November 28, 2003
|
Errors
This code appears to have several errors: 1) The functions is defined as 'tsrUpdSelects' but is called as 'tsrUpdSelect' 2) There is a case error with the variable 'i' it is called as [I] which will error on most Javascript interpretters. 3) if(thisSelect.name == frstSelect.name) { should be: if(thisSel.name == frstSel.name) {
Neat code otherwise.
Posted by: Lee
Posted on: 04/30/2004 04:49 PM
|
Object expected
After I run the code, I got the error message: Line 2713 Error: Object expected. Please help.
Posted by: Lei Huang
Posted on: 05/19/2004 05:53 PM
|
re: Object Expected
See above comment or try posting in a forum:
http://tutorial196.easycfm.com/forums/
Posted by: Lee
Posted on: 06/03/2004 06:20 PM
|
Spaces from db
Look out for spaces coming from database. Use trim() for lines:
aryLocation[#Variables.JSLoop#] = new Array("#trim(qryLocationInfo.state)#","#trim(qryLocationInfo.city)#","#trim(qryLocationInfo.address)#");
and
<option value=”#trim(qryLocationInfo.state)#”> #trim(qryLocationInfo.state)# </option>
Posted by: declan
Posted on: 11/15/2004 07:24 AM
|
It's all well and good, but...
how about when you are trying to use this method to post to another form - for instance, a search results page? The action attribute has the form posting to itself. I've tried adding an OnClick to the submit button and passing the variables that way but run into a problem in that it will only pass 1 variable. Any suggestions? Thanks.
Posted by: bamafan
Posted on: 11/23/2004 02:20 PM
|
Problem with duplicate cities
Your code works great and has saved me a lot of time! Thanks! One problem, however, is that duplicate cities are not dropped as your comments suggest. Any suggestions?
Posted by: Pam
Posted on: 02/09/2005 11:19 AM
|
Need help
Hi if i habe input textand in this input i enter anumber as 5 for example i will get five input texts How i can do it? I would be gratfull if u help me? Thanks
Posted by: maiada
Posted on: 04/05/2005 02:35 AM
|
Made a CFC out of this
Hey everyone. This is awesome stuff. I turned this into a CFC that you can easily use. Essentially all you have to do is do a query similar to Steven's, and you call an initial function to set everything up. And then call the First and Second drop down functions wherever you want them to appear.
http://www.dopejam.com/CustomTags/DualSelect.zip
Posted by: Tariq Ahmed
Posted on: 04/07/2005 06:34 PM
|
Errors
Thought this script might be the answer to my prayers but all I get are errors,
'Missing ')' after condition'
'tsrUpdSelects is not defined'
I checked all the items mentioned here and still cannot get this script to work.
Posted by: Macca
Posted on: 08/01/2005 06:03 AM
|
Second Select Box not populating
Hi, I am pretty sure I have followed your code. The 1:st dropdown is populated OK but it doesn't seem to populated the 2:nd after I do a selection in the 1:st. Any ideas ?? Thanks
Jonas
Posted by: Jonas
Posted on: 03/25/2006 05:56 AM
|
help using 3 tables
Is there any way you can show an example of this using three seperate tables with 3 seperate queries?
Posted by: Nicole Rutter
Posted on: 05/15/2006 04:43 PM
|
What to use in place of lastIndexOf
Hi
This is a great tutorial, but I use IE6 and can not use lastIndexOf. "This is a JavaScript1.6 feature, supported in Firefox1.5+ but NOT IE7 or below." I found this statement on this website: http://www.javascriptkit.com/jsref/arrays.shtml. Can you tell us IE6 and IE7 users what to use in place of lastIndexOf?
----------- //Now loop through and set the second option list (city). //If a state was chosen. There is also code to prevent duplicate //cities. This could happen if a city had multiple addresses. if(thisSel.options[thisSel.selectedIndex].value != “”) { for (i = 0; i < aryLocation.length; i++) { if(aryLocation[I][0] == thisSel.options[thisSel.selectedIndex].value && chkCty.lastIndexOf(aryLocation[i][1] == -1) { scndSel.options[scndSel.length] = new Option(aryLocation[i][1], aryLocation[i][1]); chkCty = chkCty + “,” + aryLocation[i][1]; } } } } -------------
Thanks a bunch.
Regina
Posted by: R. Stevens
Posted on: 05/04/2007 09:44 PM
|
It Really Works
Good Day,
The Multiple Related Selects with ColdFusion and JavaScript Really Works!!!!! There were some syntax errors that had to be correct, but the structure is correct.
Errors 1) The functions is defined as 'tsrUpdSelects' but is called as 'tsrUpdSelect' 2) There is a case error with the variable 'i' it is called as [I] which will error on most Javascript interpretters. 3) if(thisSelect.name == frstSelect.name) { should be: if(thisSel.name == frstSel.name) { 4.) Add the ending ) to && chkTable.lastIndexOf(aryLocation[i][1] == -1)) 5.) Remove the form name testForm from the onChange Events. (ex. (bad) testForm.myCity#myLoop# (good) myCity#myLoop#.
- I actually added more array indexes. I took it to 6 indexes. thrdSel.options[thrdSel.length] = new Option(aryLocation[i][6], aryLocation[i][6]);
- I removed the cfloop tag. Didn't need just need one row. <cfloop from=”1” to=”5” index=”Variables.myLoop”>
It took a minute. I debugged in Firefox Firebug. Its an excellent browser for debugging javascript.
Hope these notes help someone.
Deetra
Posted by: DAW
Posted on: 11/06/2007 02:52 PM
|
Correction To Error Fix #4 Listed Above
The fix for error for #4 above is incorrect. It will cause duplicates to be listed in the City drop-down.
The closing tag was in the wrong location.
CORRECTION: 4.) Add the ending ) to && chkTable.lastIndexOf(aryLocation[i][1]) == -1)
Posted by: Ben
Posted on: 11/27/2007 02:16 PM
|
Not clear to me
Can someone explain what they mean by this ????
4.) Add the ending ) to && chkTable.lastIndexOf(aryLocation[i][1]) == -1)
Posted by: Charles
Posted on: 12/29/2007 10:08 AM
|
Some more errors
I do like the script, but I came across a few errors. As soon as I select the second box the first box reverts back to default with no selection. I try to use this with more than 3 boxes (6), but the third box does nothing. Not sure what I am doing wrong
Any ideas?
Posted by: John
Posted on: 03/18/2008 08:59 AM
|
|