Ask Ben: Dynamic Form Field Population With jQuery

Posted June 10, 2009 at 2:49 PM

Tags: ColdFusion, Javascript / DHTML, Ask Ben

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:

 Launch code in new window » Download code as text file »

  • <!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:

 Launch code in new window » Download code as text file »

  • <!--- 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.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Other Searches  |  Print Page




Reader Comments

Jun 10, 2009 at 3:08 PM // reply »
2 Comments

I may be jumping the gun here but, where's the code???


Jun 10, 2009 at 4:23 PM // reply »
31 Comments

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>


Jun 10, 2009 at 4:24 PM // reply »
31 Comments

OK, looks like I can't use the <code> tag. Hm. Any advice on posting code in comments?


Jun 10, 2009 at 4:43 PM // reply »
7,486 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.


Jun 10, 2009 at 4:57 PM // reply »
31 Comments

Hey, I'm just going by the Adobe ColdFusion documentation...


Jun 11, 2009 at 8:18 AM // reply »
7,486 Comments

@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?


Jun 11, 2009 at 10:47 AM // reply »
123 Comments

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);


Jun 11, 2009 at 11:17 AM // reply »
31 Comments

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>


Jun 11, 2009 at 11:25 AM // reply »
7,486 Comments

@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 :)


Jun 11, 2009 at 11:31 AM // reply »
31 Comments

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


Post Comment  |  Ask Ben

Recent Blog Comments
Mar 12, 2010 at 1:38 AM
Using jQuery's SlideUp() and SlideDown() Methods With Bottom-Positioned Elements
Very nice and useful tutorials for web designers, Thanks for posting. ... read »
Mar 11, 2010 at 11:14 PM
Using The Apple iPod Shuffle Without iTunes
Whoever coded this deserves a pat on the back. I really hated iTunes and this has worked great for me. Thanks! Oh, and thanks for answering the question about file renaming. I was just afraid I woul ... read »
Mar 11, 2010 at 9:29 PM
Tim Cracked The GMail - CFMailPart Puzzle!
I've been wrestling with the CFMAIL tag and CFMAILPARTS for several days now and have found issues with the CF implementation even in CF9! What I have learned so far is: 1. Using only one CFMAILPART ... read »
Mar 11, 2010 at 6:09 PM
Ask Ben: Building An AJAX, jQuery, And ColdFusion Powered Application
@Eric, Neat trick, I was able to get rid of most of the lines of whitespace following your advice. Some whitespace still remains. With a bit of playing around, I found that the remaining whitespa ... read »
Mar 11, 2010 at 4:56 PM
Ask Ben: Building An AJAX, jQuery, And ColdFusion Powered Application
I've struggled with returning JSON from ColdFusion CFCs for a while because I (mysteriously) get lots of white space/new lines that appear before the actual JSON result (check the response in Firebug ... read »
Mar 11, 2010 at 3:24 PM
Ask Ben: Using jQuery To Act On A Click Event Based On The Target Element
@TripeL, Awesome :) Glad it was helpful. ... read »
Mar 11, 2010 at 3:23 PM
Ask Ben: Using jQuery To Act On A Click Event Based On The Target Element
WOW...that's what I'm looking for. The code examples are very helpful. Thanks ... read »
Mar 11, 2010 at 1:20 PM
What Is The Best Time Of Day To Workout?
Well I am glad I stick to mid afternoon / evening work outs. Interesting find! ... read »