Darryl Lyons’ Blog

AJAX, ColdFusion and Web technology…

Entries Comments



Category: ColdFusion


I’ve been very busy…

11 January, 2007 (20:31) | ColdFusion, JavaScript | By: Darryl Lyons

At last, the project I have been working on for the last 3 to 4 months has come to a close (just in time for Christmas). I’ve been working on version 2 of our internal AJAX CRM application, and it went into production last month.

I did the release out of hours, so it would disrupt the least amount of people. Around 2 ½ hours later it was on the production servers with minimal fuss. The most rewarding thing for me after so much work is hearing praise from users. A few even emailed the project sponsors stating how much they loved the upgrade. I haven’t heard any negative comments as of yet (you can’t please everyone)…

A lot of my previous posts have been based on lessons learned during v1.0.0 development, and my up and coming posts will no doubt contain similar content.

I’m busily managing the development of the 2.1 release…

IIS+SSL+CFCONTENT problems

24 July, 2006 (22:20) | ColdFusion | By: Darryl Lyons

Today I experienced a very strange problem, that I think a few of you have also experienced. If you are using SSL and IIS together, there is a known bug that prevents attachment downloads from working correctly if you are using cache control headers.

So, the following will not work.

CFM:
  1. <cfheader name="Content-Disposition" value="attachment;filename=thefile.csv">
  2. <cfheader name="pragma" value="no-cache">
  3. <cfcontent type="application/csv" deleteFile="No" file="path\to\file\theFile.csv">

But this will work.

CFM:
  1. <cfheader name="Content-Disposition" value="attachment;filename=thefile.csv">
  2. <cfcontent type="application/csv" deleteFile="No" file="path\to\file\theFile.csv">

AJAX (On-Demand JavaScript) with ToScript()

24 June, 2006 (22:54) | AJAX, ColdFusion, JavaScript | By: Darryl Lyons

I recently discovered the neat little method toScript() had been added to ColdFusion 7.0. I immediately thought that this would have to be quicker at serialising ColdFusion variables into their JavaScript equivilent than any existing JSON serialisers.

It also occurred to me that ToScript() could be used as an alternative data delivery mechanism to XML or JSON for "AJAX-enabled" web applications.

Using ToScript() for AJAX

The easiest way to use ToScript() in an "AJAX" sense is to use the On-Demand JavaScript pattern.

I've posted about the On-Demand JavaScript pattern before, so I thought I would re-use that same code, but improve it a little so that it is cross-browser (tested in Mozilla 1.5.x, IE6) compatible, and a little less verbose.

There are a number of parts to this example:

  1. index.html: Contains an empty table, ready to populate, and the trigger to call the JavaScript.
  2. getData.cfm: Serialises a query as a JavaScript array of objects (structs).
  3. getData.js: Creates a SCRIPT tag on the fly that points to the getData.cfm template as its SRC.

You can also download this example.

index.html

This code simply includes the getData.js JavaScript file, and defines an empty table, awaiting population. I've put a link above the table to trigger the population based upon data returned from ColdFusion.

HTML:
  1. <title>AJAX with ToScript()</title>
  2.  
  3. <script src="getData.js"></script>
  4. *
  5. {
  6.         font-family: sans-serif;
  7.         font-size: 10pt;
  8. }
  9.  
  10. caption
  11. {
  12.         background-color: #333333;
  13.         font-weight: bold;
  14.         text-align: left;
  15.         color: white;
  16.                
  17. }
  18.  
  19. thead tr
  20. {
  21.         background-color: #CCCCCC;
  22.         font-weight: bold;
  23.         text-align: left;
  24.                
  25. }
  26.  
  27. tr
  28. {
  29.         background-color: whitesmoke;
  30. }
  31.  
  32. td, caption
  33. {
  34.         padding: 4px;
  35. }
  36. </style>
  37. </head>
  38.  
  39.  
  40. <a href="javascript:getArtists()">Call getArtists() method</a>
  41.  
  42. <br/><br/>
  43. <table id="artistTable">
  44.         <caption>Artists</caption>
  45.         <thead>
  46.                 <tr>
  47.                         <td>ID</td>
  48.                         <td>Last Name</td>
  49.                         <td>First Name</td>
  50.                         <td>City</td>
  51.                 </tr>
  52.         </thead>
  53.         <tbody>
  54.         </tbody>
  55. </table>
  56.  
  57. </body>
  58.  
  59. </html>

getData.js

The getDataFromServer(id, url, callback) method dynamically creates a SCRIPT tag., where the source is set to a combination of the url and callback arguments. When the SCRIPT tag has loaded, whatever inside it is evaluated, including the specified callback method reference. The callback is placed there by getData.cfm.

The getArtists() method simply calls getDataFromServer with specified parameters. Notice that the callback is set to populateArtists.

The populateArtists(data) method is called when the SCRIPT tag has finished loading. getData.cfm contains the qArtists JavaScript variable, and a call to populateArtists, with qArtists being passed as a parameter. The table on the page is then populated.

JavaScript:
  1. function getDataFromServer(id, url, callback)
  2. {
  3.         var oScript = document.getElementById(id);
  4.         var head = document.getElementsByTagName("head").item(0);
  5.        
  6.         if (oScript)
  7.         {
  8.                 // Destory object
  9.                 head.removeChild(oScript);
  10.         }
  11.        
  12.         // Create object
  13.         oScript = document.createElement("script");
  14.        
  15.         var dtRf = new Date();
  16.  
  17.         oScript.setAttribute("src",url + "?callback=" + callback + "&rf=" +dtRf.getTime());
  18.         oScript.setAttribute("id",id);
  19.  
  20.         head.appendChild(oScript);
  21.  
  22. }
  23.  
  24. function getArtists()
  25. {
  26.         getDataFromServer("artistData","getData.cfm","populateArtists");
  27. }
  28.  
  29. function populateArtists(data)
  30. {
  31.         var oTBody = document.getElementById("artistTable").tBodies[0];
  32.  
  33.         // Loop over the data in the JS array, and add to table
  34.         for (var i=0; i <data.length; i++)
  35.         {
  36.                 // Create a new TR element
  37.                 var oTR = document.createElement("TR");
  38.                 oTBody.appendChild(oTR);
  39.  
  40.                 // Create a call for each element in struct
  41.                 var oTD = document.createElement("TD");
  42.                 oTD.innerHTML = data[i].artistid;
  43.                 oTR.appendChild(oTD);
  44.  
  45.                 var oTD = document.createElement("TD");
  46.                 oTD.innerHTML = data[i].lastname;
  47.                 oTR.appendChild(oTD);
  48.                                
  49.                 var oTD = document.createElement("TD");
  50.                 oTD.innerHTML = data[i].firstname;
  51.                 oTR.appendChild(oTD);
  52.                
  53.                 var oTD = document.createElement("TD");
  54.                 oTD.innerHTML = data[i].city;   
  55.                 oTR.appendChild(oTD);
  56.                
  57.         }
  58. }

getData.cfm

This ColdFusion code queries the art gallery example database to return all of the artists. It then uses the new ColdFusion built-in function ToScript() to serialise the query into a JavaScript array of objects (structs).

You will also notice that I am placing #url.callback(qArtists)# directly after ToScript(). When this JavaScript is loaded (when the SCRIPT tag is added to the DOM), the JavaScript method specified as url.callback will be called. The method will be passed the the parameter qArtists, which is the JavaScript variable just created by ToScript().

CFM:
  1. <cfsetting enablecfoutputonly="true">
  2.  
  3. <cfquery datasource="cfartgallery" name="qData">
  4. SELECT *
  5. FROM artists
  6. ORDER BY lastname, firstname
  7. </cfquery>
  8.  
  9. <cfoutput>var #toScript(qData, "qArtists", false, true)# #url.callback#(qArtists);</cfoutput>

ToScript() vs. CFJSON

I decided to run a few tests comparing ToScript() to CFJSON. Not surprisingly, ToScript() came out on top. But can the output be used in place of JSON? No, it cannot. The key difference is that JSON is represented as a single statement, whereas ToScript() generates multiple statements. Therefore, you cannot simply run eval() on the ToScript() generated string to create a JavaScript object.

I will be really happy when the boys at Adobe create a ToJSON() method native to ColdFusion...

CFjsmin - JavaScript Compressor

8 June, 2006 (21:21) | ColdFusion, JavaScript | By: Darryl Lyons

CFjsmin (beta) is a ColdFusion component that compresses JavaScript files too a fraction of their original size (sometimes more than 50%). It achieves this by removing all non-essential whitespace and comments.

The component is also useful for packaging multiple JavaScript files into one compressed script file. This can lead to further improvements in speed. Internet Explorer only downloads two assets at a time when loading a page, so the less JavaScript files you have got, the quicker the site will load (we have over 200 individual files in our AJAX application at work, so it really helps us).

I am using a Java class called jsmin, by John Reilly, to actually do the grunt work. I modified the source a little so that it would accept file input/output streams, thus enabling the ability to merge multiple source files into the one destination file. Because CFFILE is not being used to achieve this, you will find it is pretty quick even when dealing with lots of files.

I am providing the component as a "beta" release, so if you find any issues or have suggestions for improvements, please let me know.

I hope this helps you!

Download

CFjsmin v1.0.0

Usage

CFM:
  1.  
  2. <cfscript>
  3.         // Usage 1: Simple usage
  4.         // NOTE: Destination file MUST be different! If it is the same as the source filename, then
  5.         // the source file will be overwritten.
  6.         oJSMin.compress("sourceFile.js", "compressedFile.js");
  7.  
  8.         // Usage 2: Explicitly specifying the destination file
  9.         oJSMin.setDestinationFile("compressedFile.js");
  10.         oJSMin.compress("sourceFile.js");
  11.         oJSMin.closeDestinationFile();
  12.        
  13.         // Usage 3: Creating a merged javascript file from multiple source files
  14.         oJSMin.setDestinationFile("compressedFile.js");
  15.  
  16.         oJSMin.compress("sourceFile.js");
  17.         oJSMin.compress("sourceFile2.js");
  18.         oJSMin.compress("sourceFile3.js");
  19.         oJSMin.compress("sourceFile4.js");
  20.        
  21.         oJSMin.closeDestinationFile();
  22. </cfscript>
  23.  

Example output

JavaScript:
  1.  
  2. // Before
  3. function foo()
  4. {
  5.  
  6.         // Some comments
  7.         var a = 2;
  8.        
  9.         // Some more comments
  10. }
  11.  
  12. // After
  13. function foo()
  14. {var a=2;}
  15.  

Using Java BufferedWriter to create CSV files

19 February, 2006 (13:12) | ColdFusion | By: Darryl Lyons

Someone recently asked me to expand upon my previous post about generating CSV files, and in particular, to focus on the Java method. In my previous post, I concluded that using the Java BufferedWriter class is a much more scalable and stable solution than using the typical concatenate-and-write-once-done method.

Step by Step

There really isn't much to using Java to write to a file. Essentially, there are three steps, 1) create the Java class instances, 2) get the data and write to the buffer, and 3) close the output stream.

Firstly, we instantiate the FileWriter class (which writes to the file) and then the BufferedWriter class, passing the FileWriter instance as a parameter.

CFM:
  1. <cfscript>
  2.    outputFile = getDirectoryFromPath(GetCurrentTemplatePath()) & "dump.csv";
  3.    oFileWriter = CreateObject("java","java.io.FileWriter").init(outputFile,JavaCast("boolean","true"));
  4.    oBufferedWriter = CreateObject("java","java.io.BufferedWriter").init(oFileWriter);
  5. </cfscript>

Then, we create the column headers, and loop over the data (query in this case) and write it to the buffer. The Java class manages the size of the buffer internally, so when threshold is reached, it will write to the file.

CFM:
  1. <cfset oBufferedWriter.write("LASTNAME,FIRSTNAME" & chr(13) & chr(10))>
  2. <cfloop query="qData">
  3.    <cfset oBufferedWriter.write(chr(34) & lastname & chr(34) & ",")>
  4.    <cfset oBufferedWriter.write(chr(34) & firstname & chr(34) & chr(13) & chr(10))>
  5. </cfloop>

Lastly, we need to close the output stream, otherwise the file will not be released.

CFM:
  1. <cfset oBufferedWriter.close()>

Full code

CFM:
  1. <cfscript>
  2.    outputFile = getDirectoryFromPath(GetCurrentTemplatePath()) & "dump.csv";
  3.    oFileWriter = CreateObject("java","java.io.FileWriter").init(outputFile,JavaCast("boolean","true"));
  4.    oBufferedWriter = CreateObject("java","java.io.BufferedWriter").init(oFileWriter);
  5. </cfscript>
  6.  
  7. <cfquery datasource="cfartgallery" name="qData">
  8. SELECT *
  9. FROM artists
  10. ORDER BY lastname, firstname
  11. </cfquery>
  12.  
  13. <cftimer type="inline" label="Generate CSV">
  14.    <cfset oBufferedWriter.write("LASTNAME,FIRSTNAME" & chr(13) & chr(10))>
  15.  
  16.    <cfloop query="qData">
  17.  
  18.       <cfset oBufferedWriter.write(chr(34) & lastname & chr(34) & ",")>
  19.       <cfset oBufferedWriter.write(chr(34) & firstname & chr(34) & chr(13) & chr(10))>
  20.  
  21.    </cfloop>
  22.    <cfset oBufferedWriter.close()>
  23. </cftimer>

AJAX Diary: Race conditions and using cflock

18 February, 2006 (17:00) | AJAX, ColdFusion | By: Darryl Lyons

Within traditional web applications, it is highly unlikely that you will come across a scenario where a race condition will ocurr in the current session, unless you are accessing shared scopes. However, within an AJAX application, which is more like a frame-based Web site (remember them?), they can occur only too often.

Background

In our CRM application, there is a method that returns a "client ID". This method checks to see if a record exists in the database, and if not, creates it, and then returns the ID. If it already exists, then it simply returns the record. This record is in a tracking table that we use to reference records in an external system. All tables in our schema join to the tracking table, not the external database. Suffice to say, this method is called in a lot of places where client information is accessed.

<cffunction name="getClientID" returntype="string">
<cfset var local = StructNew()>
<cfset local.qClient = getClientID(arguments.account)>

<cfif NOT local.qClient.recordcount>
// Some logic to create a new tracking ID -- insert into db, etc.
</cfif>

<cfreturn local.qClient.ID>
</cffunction>

Now, you would probably state that there should be a scoped or named lock around this code. However, the likelihood of two sessions trying to create this record at the same time is very remote. Furthermore, most locking in the past has been where you are making changes to shared scopes such as SESSION, APPLICATION or SERVER.

Problem

Our new AJAX interface to our CRM has shown us that we need to be more careful, and use locking more judiciously. The scenario is this. The user clicks on a client record in a search result. This in turn loads the client information card. Three seperate, almost simultaneous, requests are sent to the server to retrieve different pieces of data. Each of the requests just happens to call the aforementioned method at some point. Even though the requests themselves are not occurring at the same time, a race condition is still created...

All three requests are now essentially calling the method, let's call it getClient(), at the same time. The database lookup to determine whether or not the record exists are being performed within a millisecond of each other. All three lookups return no records, so the conditional block of code is executed to insert a new record! So, the end result is three records in the database where there should have only be one!

Solution

Now, we pretty much had two options -- change the calls on the client so that the record check is performed once, or put a named lock around the code, so that we force requests to queue for execution.

We went for the later. Changing all of the calls to the method would require significant refactoring of the model, so that just wasn't an option.

<cffunction name="getClient" returntype="string">
<cfset var local = StructNew()>
<cflock name="getClient.getClientID" timeout="10">
<cfset local.qClient = getClientID(arguments.account)>

<cfif NOT local.qClient.recordcount>

<cflock name="getClient.getClientID.insert" timeout="10">
<cfset local.qClient = getClientID(arguments.account)>
<cfif NOT local.qClient.recordcount>
// Some logic to create a new tracking ID -- insert into db, etc.
</cfif>
</cflock>

</cfif>
</cflock>

<cfreturn local.qClient.ID>
</cffunction>

In the example above, I've put a named lock around the query to the database, and the check of the recordcount. If the recordcount is zero, then we perform another lock, and then do the check all over again. The reason for this is that in the time between acquiring the lock and performing the first check, a database record could have been inserted. The double lock technique just minimises the chance of this happening.

So, why was this only an issue now? Well, the old "traditional" user interface was a HTML page. The processing of the single request was top-down. By that I mean that if various elements of the user interface happened to call the getClient() method, they would be synchronously executed, thereby avoiding the scenario I have described.

Some other resources