I’m currently teaching (re-teaching) myself JavaScript and jQuery in order to rewrite a legacy application that runs on Lucee.
Part of the rewrite requires several additions to the basic functionality. One specific addition is adding a "Street Name" select field to an existing "County Name" -> "City Name" select fields.
I found this StackOverflow question: Coldfusion conditional select option does not work in IE9, Chrome and Firefox
Using the basics of that question; I wrote the following:
<!--- county.cfm --->
<cfif structKeyExists(url,'work_area_county_id')>
<cfset loadState() />
</cfif>
<cfif structKeyExists(url,'work_area_city_id')>
<cfset loadStreet() />
</cfif>
<cfset loadCounty() />
<cffunction name="loadCounty">
<cfquery name="qCounty">
SELECT work_area_county_id, work_area_county_name FROM work_area_county
</cfquery>
</cffunction>
<cffunction name="loadState">
<cfset variables.work_area_county_id = url.work_area_county_id />
<cfquery name="qCity">
SELECT work_area_city_id, work_area_city_name, work_area_county_id FROM work_area_city WHERE work_area_county_id = <cfqueryparam value="#variables.work_area_county_id#" cfsqltype="cf_sql_int">
</cfquery>
<cfoutput>
<select name="state" class="form-control">
<option value="">Select City</option>
<cfloop query="qCity">
<option value="#work_area_city_id#">#work_area_city_name#</option>
</cfloop>
</select>
</cfoutput>
</cffunction>
<cffunction name="loadStreet">
<cfset variables.work_area_city_id = url.work_area_city_id />
<cfquery name="qStreet">
SELECT work_area_street_id, work_area_street_name, work_area_city_id FROM work_area_street WHERE work_area_city_id = <cfqueryparam value="#variables.work_area_city_id#" cfsqltype="cf_sql_int">
</cfquery>
<cfoutput>
<select name="state" class="form-control">
<option value="">Select Street</option>
<cfloop query="qStreet">
<option value="#work_area_street_id#">#work_area_street_name#</option>
</cfloop>
</select>
</cfoutput>
</cffunction>
<!--- display.cfm --->
<cfinclude template="includes/county.cfm">
<cfoutput>
<form name="state_populate">
<select name="county" id="county">
<option value="0">Select</option>
<cfloop query="qCounty">
<option value="#work_area_county_id#">
#work_area_county_name#
</option>
</cfloop>
</select>
<div id="city">
<select name="city" class="form-control">
<option value="">Select</option>
</select>
</div>
<div id="street">
<select name="street" class="form-control">
<option value="">Select</option>
</select>
</div>
</form>
</cfoutput>
// The JS
$('#county').change(function() {
var value = $('#county').val();
$.ajax({
type: "get",
url:'includes/county.cfm?work_area_county_id='+value,
success: function(response) {
$('#city').html(response);
},
error: function(jqXHR, status, error) {
console.log(status + ": " + error);
}
});
});
The code above works well when selecting "County Name". It displays the "City Name" in the second select field properly. I then tried to add in the third select field "Street Name" and added what I "think" should work for the CFML and HTML pieces. When I got to the JS part of the code I hit a brick wall head on. I can’t seem to find, perhaps for lack of the right search terms, how to add an additional AJAX call.
I found this question: Parallel asynchronous Ajax requests using jQuery
There were multiple answers but the closest one I "think" that’s relevant to my question is this:
$.whenAll({
val1: $.getJSON('/values/1'),
val2: $.getJSON('/values/2')
})
.done(function (results) {
var sum = results.val1.value + results.val2.value;
$('#mynode').html(sum);
});
I believe they are called "promise"?
The code needs to keep the second select field "City Name" from changing and to properly use the AJAX "url" parameter with multiple values. I know I probably need something "similar" to this for the URL part in reference to the above code:
url:'includes/county.cfm?work_area_county_id='+ value + '&work_area_city_id=' + value
For consideration:
- I know my code is not great and I know it could be written better. I’m working hard to improve.
- I’m not a full time coder but I have been coding off and on for many years. I still consider myself a newbie with CFML/JavaScript/jQuery so a lot of the methodology goes over my head.
- The code will be written in cfscript and converted to a CFC in the future. For now I made it simple and used tags.
- I removed the irrelevant HTML code from the display.cfm.
Any input or guidance on the above would be greatly, greatly appreciated! 😀
3
Answers
For future viewers of this; I wanted to post my own answer to my question. While waiting for an answer to be posted, I kept trying to get the code to work. Many days went by and what I pasted below is the result.
Since both answers from AndreasRu and SOS are better, I ended up not using what I wrote. Perhaps someone can use my code as an example of what not to do. :D
My CFC:
My JavaScript/jQuery:
I’m going to show you an example of how I’d deal with such a task. My answer is probably not the best or cleanest solution: I had terrible coding practices in the past (I still tend to write spaghetti and I constantly struggle against doing it). But, I’m in the process of changing that bad habbits and that is fun.
My solution is written with an OOP approach. I’d really recommend everyone trying to go that path, because that quickly begins to feel way more natural: It just feels right, especially when you need to fix or extend your code.
I’d also try using cfscript instead of tags then, because writing OOP with components and functions is simplier, especially if you have some experience with JavaScript (which is somehow similar). However, I’m providing the tag approach, because I think that is what you’d like.
This solution basically consists of 4 files:
The main problem is that you need to retrieve more JavaScript promises, for each ajax request one. In jQuery that happens with defered object.
Here are the files:
1. display.cfm:
2. components/CountiesDAO.cfc
3. countyForm.js
4. ajaxAPI.cfm
The above solution is far from finished, but it should just give you an option to start playing around and have some fun.
(I started to write this up before, but got pulled away ….)
Tbh the
county.cfm
script strikes me as trying too hard to be "everything to everyone" :). It’s acting as a cfinclude and endpoint for ajax, both retrieving data and generating html. Resulting in less readable and more complex code than is necessary IMO.A cleaner approach would be to separate the data retrieval and html/dom manipulation. Let the CF server handle data retrieval and let the client side manipulate the DOM. That way only two (2) scripts are required.
Create a cfc that only returns data. Then inside the form, call the cfc functions via ajax and use the response to populate the select lists with javascript.
YourComponent.CFC
Start by creating a component with three remote functions:
getCounties()
,getCities(selected_county_id)
,getStreets(selected_city_id)
. Each function simply runs a query and returns the results as an array of structures, formatted as a json string.Display.cfm
Inside the form, add in ajax calls to populate the lists. Populate the "county" list when the document loads, and the "city/street" lists on the appropriate change event. There’s room for improvement, but here’s a small example