Category Archives: ColdFusion

jQuery UI Autocomplete Widget with ColdFusion

You may also be interested in the jQuery UI Autocomplete Widget with PHP and MySQL or the jQuery UI Autocomplete Widget with ASP.NET VB post. Also, check out the Using jQuery Autocomplete to Populate Another Autocomplete post.

The jQuery UI folks have released an autocomplete widget that is pretty slick. Using it with ColdFusion is a snap. This example uses the serializeJSON function in ColdFusion 8. If you have an earlier version of CF, you will have to find a cfc that can build a JSON string for you. There are several freely available cfc’s that do it for you.
autocomplete
This example will use US states and territories to populate the autocomplete. It will also demostrate how to fill other fields with data returned from the database. This data can be used to fill a visible text box or a hidden form field. It also demonstrates the basic autocomplete functionality which may be fine for some applications.

Of course, you will need the jQuery core file, the jQuery UI core file, and the jQuery UI style sheet of choice. The style sheet comes from the themes available in the jQuery UI website and can be downloaded with the core file or you can link to the latest versions of both the core files and the css:

<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1/themes/redmond/jquery-ui.css">

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>

The HTML is straight forward and stripped down for the example:

<form action="index.cfm"  method="post">
<fieldset>
<legend>jQuery UI Autocomplete Example - ColdFusion Backend</legend>
<p>Start typing the name of a state or territory of the United States</p>
<p class="ui-widget"><label for="state">State (abbreviation in separate field): </label>
	<input type="text" id="state"  name="state" /> <input readonly="readonly" type="text" id="abbrev" name="abbrev" maxlength="2" size="2"/></p>
    <input type="hidden" id="state_id" name="state_id" />
<p class="ui-widget"><label for="state_abbrev">State (replaced with abbreviation): </label>
<input type="text" id="state_abbrev" name="state_abbrev" /></p>
<p><input type="submit" name="submit" value="Submit" /></p>
</fieldset>
</form>

As a bonus, we dump out the form values to see what we have right underneath the form itself:

<cfdump var="#form#" label="Form Fields" />

And the jQuery on the page is equally brief:

$(function() {

            $('#abbrev').val("");

            $("#state").autocomplete({
                source: "states.cfm",
                minLength: 2,
                select: function(event, ui) {
                    $('#state_id').val(ui.item.id);
                    $('#abbrev').val(ui.item.abbrev);
                }
            });

            $("#state_abbrev").autocomplete({
                source: "states_abbrev.cfm",
                minLength: 2
            });
        });

Notice that there are two autocomplete functions on the page, one for each example in the demo. Each function calls a different ColdFusion file which return slightly different result sets.

Also, the minLength for autocomplete to return results is set to 2 to prevent too many rows from being returned.

The jquery autocomplete will append the text typed into the autocomplete field as the URL parameter ‘term.’ This URL parameter is used to query the database.

From the jquery documentation:

The request parameter “term” gets added to that URL.

Both ColdFusion pages return the data after a few steps:

  1. It queries the database
  2. Loops the query, adding each row to a structure that is appended to an array
  3. Outputs the array as JSON data

The states.cfm file returns the id field, the state field as ‘label’, and the abbrev field. These values are placed in the appropriate text boxes by the autocomplete jQuery function.

<cfset returnArray = ArrayNew(1) />

<cfquery name="qryStates" dataSource="autocomplete">
    Select * from states where state like '%<cfqueryparam value="#URL.term#" cfsqltype="cf_sql_varchar">%'
</cfquery>

<cfloop query="qryStates">
    <cfset statesStruct = StructNew() />
    <cfset statesStruct["id"] = id />
    <cfset statesStruct["label"] = state />
    <cfset statesStruct["abbrev"] = abbrev />

    <cfset ArrayAppend(returnArray,statesStruct) />
</cfloop>

<cfoutput>
#serializeJSON(returnArray)#
</cfoutput>

Unfortunately, the ColdFusion function serializeJSON does not put query results in the format that javascript likes. That’s why we have to do the hokey-pokey with the structure and array.

Very important information below. Please read and understand before expecting the autocomplete to work properly.

The states_abbrev.cfm shows the basic functionality of the autocomplete function by just assigning results of the query to the ‘label’ and ‘value’ fields. Explanation on the ‘label’ and ‘value’ fields from the jQuery UI site:

The local data can be a simple Array of Strings, or it contains Objects for each item in the array, with either a label or value property or both. The label property is displayed in the suggestion menu. The value will be inserted into the input element after the user selected something from the menu. If just one property is specified, it will be used for both, eg. if you provide only value-properties, the value will also be used as the label.”

<cfset returnArray = ArrayNew(1) />

<cfquery name="qryStates" dataSource="autocomplete">
    Select * from state where state like '%#URL.term#%'
</cfquery>

<cfloop query="qryStates">
    <cfset statesStruct = StructNew() />
    <cfset statesStruct["label"] = state />
    <cfset statesStruct["value"] = abbrev />

    <cfset ArrayAppend(returnArray,statesStruct) />
</cfloop>

<cfoutput>
#serializeJSON(returnArray)#
</cfoutput>

Usual recommended jQuery and CF reading:

Passed the ColdFusion 8 Certification Exam

Passed the ColdFusion 8 Certification Exam today. Scored a 95 and could not have done it without CF8 Exam Buster.

ColdFusion 8 Adobe Certified Expert - Advanced

I have heard all the debate on whether or not to take the CF exam and I think most arguments against taking it are rubbish.

The real benefit of taking any exam is the fact that you get exposed to parts of the language that you never knew existed. Just studying for the exam helped me find tags, attributes, and functions I wouldn’t even have thought to look for. And, if you are a freelancer, it can only help to have this credential.

So, if you can pony up the $150 for the test and the $40 for Exam Buster, there is no reason why you shouldn’t take the exam. Just go through the Exam Buster tests until you consistently score in the 90′s and you are a lock to pass.

ColdFusion and Google Analytics: Getting Out What You Put In

The Hooking into Google Analytics post details how to connect to the Data Export API of Google Analytics (GA) and get a simple visitors count in return. Here we will expand greatly on that and create a cfc that will process just about any result from the API.

This code requires at least ColdFusion 8.0.1. Check your version before installing.

An auth token is needed for requests to the API and two ways to do that are the AuthSub and ClentLogin methods. The method used is up to the developer as once the auth token is retrieved and set into session, data calls to the API can be repeatedly made.

<cffunction name="googleLogin" access="public" hint="GA account authorization">
        <cfargument name="email" type="string" required="yes" default="">
        <cfargument name="password" type="string"required="yes" default="">
        <cfargument name="gaLoginUrl" type="string" required="no" default="https://www.google.com/accounts/ClientLogin">

        <cfset var loginAuth = "" />

        <cfhttp url="#arguments.gaLoginUrl#" method="post">
            <cfhttpparam name="accountType" type="url" value="GOOGLE">
            <cfhttpparam name="Email" type="url" value="#arguments.email#">
            <cfhttpparam name="Passwd" type="url" value="#arguments.password#">
            <cfhttpparam name="service" type="url" value="analytics">
            <cfhttpparam name="source" type="url" value="my-analytics">
        </cfhttp>

        <cfif NOT FindNoCase("Auth=",cfhttp.filecontent)>
            <cfset loginAuth = "Authorization Failed" />
        <cfelse>
            <cfset loginAuth = Mid(cfhttp.filecontent, FindNoCase("Auth=",cfhttp.filecontent) + (Len("Auth=")), Len(cfhttp.filecontent)) />
        </cfif>

         <cflock scope="session" type="exclusive" timeout="5">
                <cfset session.ga_loginAuth = loginAuth />
         </cflock>

    </cffunction>

To retrieve the profile or data feed, a call is made to the API via a function.

   <cffunction name="callApi" access="public" returntype="array" hint="GA data as array of structures">
        <cfargument name="gaUrl" type="string" required="yes">
        <cfargument name="authToken" type="string" required="no" default="#session.ga_loginAuth#" />

        <cfset var authTokenHeader = 'GoogleLogin auth=' & arguments.authToken />

        <cfset var responseOutput = "" />

        <cfhttp url="#arguments.gaUrl#" method="get">
            <cfhttpparam name="Authorization" type="header" value="#authTokenHeader#">
        </cfhttp>

        <cfset responseOutput = cfhttp.filecontent />
        <!---remove dxp: prefix from nodes that have it and strip xmlns from feed element--->
         <cfset responseOutput = responseOutput.ReplaceAll("(</?)(\w+:)","$1") />
         <cfset responseOutput = REReplaceNoCase(responseOutput,"<feed[^>]*>","<feed>") />
         <!---entry nodes hold the data--->
         <cfset entryNodes = XmlSearch(responseOutput, '//entry/') />

         <cfreturn entryNodes />
    </cffunction>

Next, a list of the profiles (websites) that the user has access to are retrieved by requesting an account feed. If the user has only one profile associated with their credentials, the data is displayed immediately. If they have more than one, they are presented with a select box to select the profile they want data from.

<cfinvoke component="ga" method="parseProfiles" returnvariable="profilesArray"></cfinvoke>
 <cffunction name="parseProfiles" access="public" returntype="array" hint="GA profiles as array of structures">

	<cfset var profileArray = ArrayNew(1) />
        <cfset var entryStruct = StructNew() />

        <cfset entryNodes = callApi("https://www.google.com/analytics/feeds/accounts/default") />

        <cfloop array="#entryNodes#" index="entry">
            <cfset entryStruct = StructNew() />

            <cfset entryStruct.title = entry.title.XmlText />
            <cfset entryStruct.tableId = entry.tableId.XmlText />

            <cfset arrayAppend(profileArray,duplicate(entryStruct)) />
        </cfloop>

        <cfreturn profileArray />
    </cffunction>

A default date range of one year is set initially. A dialog box that allows the user to change the date range is presented as an option. The date range is limited to a max date of yesterday because GA does not have full data for the current day. This causes the averages that are calculated on the return data to be skewed.

A data feed request url is sent to GA along with the auth token a via cfhttp tag.

<cfset reqUrl = "https://www.google.com/analytics/feeds/data?ids=" & session.tableId & "&metrics=ga:newVisits,ga:pageviews,ga:visits,ga:visitors,ga:timeOnSite&start-date=" & session.startdate & "&end-date=" & session.enddate />

The XML response is then parsed out and returned as an array of structures.

<cffunction name="parseData" access="public" hint="GA data as array of structures set in session">
        <cfargument name="gaUrl" type="string" required="yes" />
        <cfargument name="arrayName" type="string"required="yes" />

        <cfset var dataArray = ArrayNew(1) />
        <cfset var entryStruct = StructNew() />

         <cfset entryNodes = callApi(arguments.gaUrl) />

        <!---CF8 loop through the entries and put each data point in structure--->
        <cfloop from="1" to="#ArrayLen(entryNodes)#" index="num">
		<!---rest of the stats data from GA, first check if dimension exists--->
		<cfif StructKeyExists(entryNodes[num],"dimension")>
		<cfloop from="1" to="#ArrayLen(entryNodes[num].dimension)#" index="i">
		<!---start after ga: to remove it--->
		<cfset "entryStruct.#Mid(entryNodes[num].dimension[i].XmlAttributes["name"],4,
Len(entryNodes[num].dimension[i].XmlAttributes["name"]))#" = entryNodes[num].dimension[i].XmlAttributes["value"] />
		</cfloop>
		</cfif>

		<cfloop from="1" to="#ArrayLen(entryNodes[num].metric)#" index="i">
			<cfset "entryStruct.#Mid(entryNodes[num].metric[i].XmlAttributes["name"],4,
Len(entryNodes[num].metric[i].XmlAttributes["name"]))#" = entryNodes[num].metric[i].XmlAttributes["value"] />
		</cfloop>

		<cfset arrayAppend(dataArray,duplicate(entryStruct)) />

	</cfloop>

         <!---CF9 can use this simplier loop. If you have CF9, uncomment and delete loop above
            <cfloop array="#entryNodes#" index="entry">
		<cfif StructKeyExists(entry,"dimension")>
                	<cfloop array="#entry.dimension#" index="dimension">
                 	<cfset "entryStruct.#Mid(dimension.XmlAttributes["name"],4,
Len(dimension.XmlAttributes["name"]))#" = dimension.XmlAttributes["value"] />
                 	</cfloop>
                 </cfif>

                 <cfloop array="#entry.metric#" index="metric">
                 	<cfset "entryStruct.#Mid(metric.XmlAttributes["name"],4,
Len(metric.XmlAttributes["name"]))#" = metric.XmlAttributes["value"] />
                  </cfloop>

                <cfset arrayAppend(dataArray,duplicate(entryStruct)) />
           </cfloop>--->

        	<cflock scope="session" type="exclusive" timeout="5">
        		<cfset "session.#arrayName#" = dataArray />
        	</cflock>

    </cffunction>

Once the data is in a form you can manipulate, graphs, tables, and charts of all kinds can be made.

ClientLogin, AuthSub, and Secure AuthSub authentication examples are included in the demo. ClientLogin recommended only for client apps. Google notice on using ClientLogin:

“Important: Do not use ClientLogin if you are writing an application that runs on
your computer to make requests on behalf of 3rd party end users. Instead, use either
AuthSub or OAuth, which protects end users’ private data. Because ClientLogin stores
the user login data, it should only be used in cases where that data is under the
direct control of the user (e.g. their personal computer).”

Recommended Reading

Download files from github

ColdFusion cfgrid Selected Row Disappearing / Blank in IE

After selecting a row in cfgrid in Internet Explorer, the selected row goes blank or all white. The data is still there and the row is selected, you just can’t tell.

The problem lies in a css file that ColdFusion uses for the cfgrid. According to some other posts on the web, there is some incorrectly commented out css rules. They are commented out with ‘//’ rather than ‘/*…*/’. The file is ext-all.css and on my set up with IIS, I found it at C:\Inetpub\wwwroot\CFIDE\scripts\ajax\resources\ext\css\ext-all.css.

When the comments are correctly marked up, it got better (the text showed up) but the background color did not behave even though I specified the select background color in the cfgrid attributes with selectColor=”#FF3300″. BTW, the selectColor worked for FireFox, IE didn’t seem to care.

My solution (although imperfect) was to edit the ext-all.css by properly commenting out the bad comments and adding my own background color for the selected td. It’s imperfect because it overrides the selectColor in FireFox.

.x-grid-row-selected td, .x-grid-locked .x-grid-row-selected td{
	background-color: #316ac5; /* added */
	/*color: white;*/
}

.x-grid-row-selected span, .x-grid-row-selected b, .x-grid-row-selected div, .x-grid-row-selected strong, .x-grid-row-selected i{
	color:white !important;
}

.x-grid-row-selected .x-grid-cell-text{
	/*color: white;*/
}

.x-grid-cell-selected{
	/*color: white;*/
}

.x-grid-cell-selected span{
	/*color: white !important;*/
}

.x-grid-cell-selected .x-grid-cell-text{
	/*color: white;*/
}

Hope this helps someone else and I hope Adobe fixes it.

jQuery.ajax and jQuery.post Form Submit Examples with ColdFusion

This is the same as the previous jquery form submit post that used PHP just flavored with CF this time.

Two jQuery functions that allow for the submission of form are the jQuery.ajax function and the jQuery.post function (there is also jQuery.get but that is not addressed here).

More functionality, along with more complexity, is offered with the .ajax function while the .post function, with its more simple functionality and implementation, will be all that is needed for simple form posts.

It is highly recommended that you get a tool like Firebug to see the post response coming back from the page. It helps immensely.

Here is an example of both in action doing the same thing: form submit with email validation.

jQuery.ajax

The form:

<form id="JqAjaxForm">
<fieldset>
<legend>jQuery.ajax Form Submit</legend>
<p><label for="name_ajax">Name:</label><br />
<input id="name_ajax" type="text" name="name_ajax" /></p>
<p><label for="email_ajax">E-mail:</label><br />
<input id="email_ajax" type="text" name="email_ajax"  /></p>
<p><input type="submit" value="Submit" /></p>
</fieldset>
</form>
<div id="message_ajax"></div>

Pretty simple, nothing fancy. There is a form for each jQuery function, .ajax and .post. The only difference in the forms are the element names.

jQuery controls the submit for the forms. For the .ajax submission the jQuery is this:

$(function(){
    $("#JqAjaxForm").submit(function(){
        dataString = $("#JqAjaxForm").serialize();

        $.ajax({
        type: "POST",
        url: "process_form.cfm",
        data: dataString,
        dataType: "json",
        success: function(data) {

            if(data.email_check == "invalid"){
                $("#message_ajax").html("<div class='errorMessage'>Sorry " + data.name + ", " + data.email + " is NOT a valid e-mail address. Try again.</div>");
            } else {
                $("#message_ajax").html("<div class='successMessage'>" + data.email + " is a valid e-mail address. Thank you, " + data.name + ".</div>");
            }

        }

        });

        return false;            

    });
});

The .serialize function is used to put the form data in a format that can be processed by a page on the server. The .ajax function options include:

  • type: “get” or “post”
  • url: the page to receive the form data
  • data: the form data itself
  • dataType: the data type the function should expect back from the server
  • success function: runs on a succesful post to the page

More information on the options and further explanations of the options used here can be found on the jQuery.ajax documentation page.

jQuery.post

As in the .ajax example, the form is simple, only the names have been changed:

<form id="JqPostForm">
<fieldset>
<legend>jQuery.post Form Submit</legend>
<p><label for="name_post">Name:</label><br />
<input id="name_post" type="text" name="name_post" /></p>
<p><label for="email_post">E-mail:</label><br />
<input id="email_post" type="text" name="email_post" /></p>
<p><input type="submit" value="Submit" /></p>
</fieldset>
</form>
<div id="message_post"></div>

And again, jQuery controls the submit for the forms. For the .post submission the jQuery is this:

$(function(){
    $("#JqPostForm").submit(function(){
        $.post("process_form.cfm", $("#JqPostForm").serialize(),
        function(data){
            if(data.email_check == 'invalid'){

                    $("#message_post").html("<div class='errorMessage'>Sorry " + data.name + ", " + data.email + " is NOT a valid e-mail address. Try again.</div>");
            } else {
                $("#message_post").html("<div class='successMessage'>" + data.email + " is a valid e-mail address. Thank you, " + data.name + ".</div>");
                }
        }, "json");

        return false;

    });
});

jQuery.post is a shorter, easier way to post the form data. The function arguments are:

  • url of the form processing page
  • the form data
  • the callback function
  • the data type of the return data

More information can be found on the jQuery.post documentation page.

Processing the Form

Both methods are processed by the same page. It processes the form data, process_form.cfm in this example, by checking to see if the e-mail submitted is valid. Much more than that could be done on the page if needed.

<cfset email_check = "" />
<cfset return_json_string = "" />
<cfset return_struct = StructNew() />

<cfif (isDefined("form.email_ajax") AND isValid("email",form.email_ajax)) OR (isDefined("form.email_post") AND isValid("email",form.email_post))>
   <cfset email_check = "valid"/>
<cfelse>
    <cfset email_check = "invalid"/>
</cfif>

<cfset StructInsert(return_struct, "email_check", email_check) />

<cfif isDefined("form.email_ajax")>
	<cfset StructInsert(return_struct, "name", form.name_ajax) />
	<cfset StructInsert(return_struct, "email", form.email_ajax) />

	<!---return_json_string is for pre-CF 8 only, delete if you have cf 8 or later--->
	<cfset return_json_string = '{"email_check":"#email_check#","name":"' & form.name_ajax & '","email":"' & form.email_ajax & '"}' />

<cfelse>
	<cfset StructInsert(return_struct, "name", form.name_post) />
	<cfset StructInsert(return_struct, "email", form.email_post) />

	<!---return_json_string is for pre-CF 8 only, delete if you have cf 8 or later--->
	<cfset return_json_string = '{"email_check":"#email_check#","name":"' & form.name_post & '","email":"' & form.email_post & '"}' />

</cfif>

<!--- serializeJSON is CF 8 and above only, see below for pre-CF 8 --->
<cfoutput>#serializeJSON(return_struct)#</cfoutput>

<!--- Uncomment the cfoutput statement below and remove the cfoutput statement above if you don't have CF 8--->
<!--- <cfoutput>#return_json_string#</cfoutput> --->

This code puts the results of the e-mail validation and the form data in a JSON-formatted string. It then will output the return data string which is picked up by the success function in the .ajax function or the function(data) function in the .post function on the original page.

I prefer working with JSON, but there are other options for the return data. Check the jQuery documentation for the types available to you.

The JSON response will look like this:

{"email_check":"valid","name":"Julia","email":"julia@example.com"}

NOTE: If you have ColdFusion 8 or better use the serializeJSON function. This function can return query results, arrays, dates, strings, and the like to JSON which is easily consumed and digested by javascript. Pre-CF 8 will require building the JSON string manually but with some loops and other creativity, it can be done.

The appropriate message based on the e-mail validation check is then displayed.

Pretty simple, pretty handy couple of jQuery functions. Once you see it in action, you get the idea.

Usual recommended jQuery and CF reading:

Download zip of all files