Ask Ben: Dynamic Form Field Population With jQuery
Hi Ben, Sorry for not clear enough with my question. Here is my scenerio. I have three input fields: 1) Empno as a combo box; 2) Ename as a text box, and 3) sal as text box.. Now i have all the empno's from the table in the combo box. AS the user selects one of employee number.. related data (ename and sal) should appear in the text boxes.. Can you please help me in this. Thanks.
What we have here is essentially a set of related form fields in which the value of two of them are directly dependent on the value of a third one. To get those first two fields to populate, we have to bind some sort of event listener to the master form field such that when it changes, it has the other two fields update as well. When figuring out where that dependent data comes from, we have two option: local data and remote data. Local data is going to be the fastest option, but depending on the volume of information, this might not be feasible. Remote data, retrieved with an AJAX call, will take a bit longer, but will ultimately make our form page much more light weight and flexible.
Because I am not sure where your data is coming from, I have taken the opportunity to write up a jQuery powered demo that features both data retrieval methods:
The code for this is going to be more complicated than it needs to be because it dynamically handles two different modes of data storage. But, even so, I think you'll see that I am breaking it down in ways that are very easy to understand:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Dynamic Form Population With jQuery</title>
<script type="text/javascript" src="jquery-1.3.2.js"></script>
<script type="text/javascript">
// Define a method that handles nothing but the actual
// form population. This helps us decouple the data
// insertion from the data retrieval.
function PopulateFields( strValue1, strValue2 ){
$( "#field2" ).val( strValue1 );
$( "#field3" ).val( strValue2 );
}
// I take the given option selection and return the
// associated data using a remote method call.
function GetAJAXValues( strOption, fnCallback ){
// Check to see if there is currently an AJAX
// request on this method.
if (GetAJAXValues.Xhr){
// Abort the current request.
GetAJAXValues.Xhr.abort();
}
// Get data via AJAX. Store the XHR (AJAX request
// object in the method in case we need to abort
// it on subsequent requests.
GetAJAXValues.Xhr = $.ajax({
type: "post",
url: "./dynamic_form_population_data.cfm",
data: {
option: strOption
},
dataType: "json",
// Our success handler.
success: function( objData ){
// At this point, we have data coming back
// from the server. However, since ColdFusion
// upper-cases the struct-keys, let's
// translate these back to the expected case.
fnCallback({
Value1: objData.VALUE1,
Value2: objData.VALUE2
});
},
// An error handler for the request.
error: function(){
alert( "An error occurred" );
},
// I get called no matter what.
complete: function(){
// Remove completed request object.
GetAJAXValues.Xhr = null;
}
});
}
// I take the given option selection and return the
// associated data.
function GetStaticValues( strOption ){
if (strOption == "opt1"){
return({
Value1: "Value 1 - Field 1",
Value2: "Value 1 - Field 2"
});
} else if (strOption == "opt2"){
return({
Value1: "Value 2 - Field 1",
Value2: "Value 2 - Field 2"
});
} else {
// No matches, so return default value.
return({
Value1: "",
Value2: ""
});
}
}
// I handle the updating of the form fields based on the
// selected option of the combo box.
function UpdateFormFields(){
var jSelect = $( "#field1" );
var jAJAX = $( "#ajax" );
var objData = null;
// Check to see if we are using AJAX or static data
// to re-populate the form.
if (jAJAX.is( ":checked" )){
// Make a remote call to get the remote data.
// Because we have to do this asynchronously,
// we have to provide a callback method that
// will hook the results up to the populate
// fields method.
GetAJAXValues(
jSelect.val(),
// Callback method for results.
function( objRemoteData ){
PopulateFields(
objRemoteData.Value1,
objRemoteData.Value2
);
}
);
} else {
// Make a local call to get the static data.
objData = GetStaticValues( jSelect.val() );
// Populate form fields.
PopulateFields( objData.Value1, objData.Value2 );
}
}
// When the DOM is ready to be interacted with, init.
$(function(){
var jSelect = $( "#field1" );
// Bind the change event to the select box. We're
// just going to hand that control off to the
// handler method.
jSelect.change( UpdateFormFields );
});
</script>
</head>
<body>
<h1>
Dynamic Form Population With jQuery
</h1>
<form>
<p>
<select id="field1">
<option value="">- - Select - -</option>
<option value="opt1">Option 1</option>
<option value="opt2">Option 2</option>
</select>
</p>
<p>
<input type="text" id="field2" size="50" />
</p>
<p>
<input type="text" id="field3" size="50" />
</p>
<p>
<label>
<input type="checkbox" id="ajax" />
Use Ajax To Gather Data
</label>
</p>
</form>
</body>
</html>
When our document object model (DOM) is ready to be interacted with, I get a reference to the master select box and bind a "change" event handler to it. I am purposely defining the method of the change-handler outside of our bind so that we can leverage it in more than just one way. For example, by separating the event binding from the event handling, it allows us to execute the event handling manually and independently of the select box (NOTE: you can also manually trigger the change() event, but I'm just trying to show different ways of breaking the code up).
The UpdateFormFields() method, our event handler, checks to see if the AJAX checkbox was checked. If it was not checked, our method gets the field data from one method that delivers static data; if it was checked, our method gets the data from a different method that delivers AJAX-based data. You'll notice that both of these methods simply return data - they don't populate the form fields. The actual population of the form fields is left to another method altogether - PopulateFields().
By breaking all the pieces up, it gives us several advantages:
We never repeat ourselves because the individual pieces of functionality have been factored out.
Our code is easier to digest mentally because the parts are all bite-size.
Our code is easier to augment because there is low coupling between the parts.
In our GetAJAXValues() method, you'll notice that I'm doing something interesting - I'm storing the XHR (AJAX request) object as a property of the GetAJAXValues() function. In Javascript, functions are first-class citizens which means that they can be treated as real objects. And, objects can have properties. By storing the XHR object as a property of the method itself, it allows me to cohesively keep track of the current AJAX request without muddying up the global namespace.
The code that returns the data in the AJAX request is simply a ColdFusion version of the static data method:
<!--- Param the FORM variable. --->
<cfparam name="FORM.option" type="string" />
<!--- Check to see which return value we are using. --->
<cfswitch expression="#FORM.option#">
<cfcase value="opt1">
<cfset objReturn = {
Value1 = "AJAX Value 1 - Field 1",
Value2 = "AJAX Value 1 - Field 2"
} />
</cfcase>
<cfcase value="opt2">
<cfset objReturn = {
Value1 = "AJAX Value 2 - Field 1",
Value2 = "AJAX Value 2 - Field 2"
} />
</cfcase>
<cfdefaultcase>
<cfset objReturn = {
Value1 = "AJAX Default",
Value2 = "AJAX Default"
} />
</cfdefaultcase>
</cfswitch>
<!--- Return the serialized AJAX response. --->
<cfcontent
type="application/x-json"
variable="#ToBinary( ToBase64( SerializeJSON( objReturn ) ) )#"
/>
There's no one "right" way to execute this kind of dynamic form population. I'm using jQuery here because jQuery is the most amazing Javascript library ever. I'm also breaking up the code in a more distributed way for demonstration and explanation purposes; the same thing could have been easily accomplished in a more concise way. Even so, I hope that this demonstration has helped in some way.
Want to use code from this post? Check out the license.
Reader Comments
I may be jumping the gun here but, where's the code???
Great stuff.
I'm not sure why you're do this though:
<code>variable="#ToBinary( ToBase64( SerializeJSON( objReturn ) ) )#"</code>
What's with the ToBinary and ToBase64?
In an AJAX situation I wrote this morning, here's
what I'm doing:
<code>
<!--- Define return value structure --->
<cfset retVal = StructNew()>
<cfset retVal["Errors"] = StructNew()>
<cfset retVal["Status"] = "">
<cfset retVal["IDN"] = 0>
<!--- Check for Errors and add to retVal.Errors if found --->
<cfif FORM.Field1 EQ "">
<cfset retVal["Errors"]["Field1"] = "Field1 is required." />
</cfif>
<cfif structIsEmpty(retVal["Errors"])>
<!--- Save FORM data to database - code omitted --->
<cfset retVal["Status"] = 'Updated'>
<cfset retVal["IDN"] = IDN><!--- from database --->
</cfif>
<!--- Return JSON data --->
<cfcontent type="application/json" variable="#serializeJSON(retVal)#">
</code>
This code returns the JSON I'm expecting.
And to avoid doing this:
<code>
Value1: objData.VALUE1
</code>
I have found that I can use this notation in ColdFusion:
<code>
<cfset retVal["Status"] = 'Updated'>
</code>
Instead of:
<code>
<cfset retVal.Status = 'Updated'>
</code>
OK, looks like I can't use the <code> tag. Hm. Any advice on posting code in comments?
@Chris,
Sorry about that, the code didn't post the first time, but I quickly updated it.
@Brian,
I am shocked that your code works! Perhaps the VARIABLE attribute has changed in ColdFusion 8? Very interesting.
Hey, I'm just going by the Adobe ColdFusion documentation...
@Brian,
I just tried your code example and I get what I expected to get from ColdFusion:
Attribute validation error for tag cfcontent. java.lang.String is not a supported variable type. The variable is expected to contain binary data.
If you look at the ColdFusion documentation:
http://livedocs.adobe.com/coldfusion/8/htmldocs/Tags_c_11.html#2850760
... you see that the Variable attribute is defined as:
Name of a ColdFusion binary variable whose contents can be displayed by the browser, such as the contents of a chart generated by the cfchart tag or a PDF or Excel file retrieved by a cffile action="readBinary" tag. When you use this attribute, any other output on the current CFML page is ignored; only the contents of the file are sent to the client.
This is the reason why I convert it to Binary before passing it to Variable.
That said, I am fascinated that it works for you and I want to know more. What version of ColdFusion are you running? Are you on one of the other engines?
Also, you might want to take a look at my Fields plug-in:
http://www.pengoworks.com/workshop/jquery/field/field.plugin.htm
It has helper functions like formHash() which will either return a form as a hash of values, or populate a form based on hash values. So, you could just pass back a JSON record and use formHash() to update the form for you:
// a sample JSON string (a simple hash; aka "structure")
var JSON = {field1: "opt1", field2: "Dan", field3: "Switzer"};
// update the form
$("form").formHash(JSON);
My apologies. I thought I had been using the VARIABLE attribute, but it turns out that I had run in to that error and did this instead:
<pre>
<cfsetting enablecfoutputonly="true" /><!--- prevent unnecessary output --->
<snip - code removed>
<cfcontent type="application/json">
<cfoutput>#serializeJSON(retVal)#</cfoutput>
</pre>
@Dan,
That looks pretty slick. Seems very robust in your demo.
@Brian,
Ah gotcha. Yeah, I thought I was losing my mind for a second :)
And that counts as me learning something for the day. Now I can relax for the rest of the day ;) And it's only 8:30 AM here....
Seems nice. :) However, for the second part - I barely know what ColdFusion is. Can you post a sample output of your cold fusion script, so that I can see what kind of reply the AJAX function expects? That would be great.
@Tom,
The AJAX function is just expecting a JSON object. Something like:
{"value1":"some value", "value2":"some value"}
Hi,
This website is great and I have got many of my problems solved. Recently, I am getting this error when I invoke the method from the page.
Variable SERIALIZEJSON is undefined.
Whereas, i successfully used the same method on my other server. can any one tell me why is this so??
Thanks!
Hey, Ben. When I asked you about this in an email this morning, I had not come across this yet. I think this was exactly what I was looking for. (I guess I just have to find the right "google" words). anyway, you can just disregard my email from this morning, although I still may not have "gotten" it by the time you email me back even, if you still do. Regardless, your help to the community is SO MUCH appreciated here. :-)
I just wanted to write an update. First of all, I'm sorry for bothering you about this when you already had a solution out there...when I did, I had not come across this post yet. Secondly, thank you so much! I have figured it out now, completely. You are so much help. I'm really glad I found this, and I will search harder the next time I come across a problem before asking for help. :-) Thanks.