Category Archives: ColdFusion

ColdFusion Example: Session Timeout Warning with jQuery/JS

You may also be interested in the post on refreshing session without reloading page with ColdFusion. Updated 11-Jan-2010: Improved ability to adjust timeouts and simplified code a bit.

I wrote a post on session timeout warnings with PHP; here’s a version with ColdFusion. Some of the text is the same because the method and principles are the same for both languages.

Javascript is needed to keep track of the time the user has been sitting on the page. The server does not know how long they have been sitting there. It only knows whether or not a request comes in during a session or after the session has expired and acts accordingly at that time. Too late for a warning.

Session Defined: Start to Finish

A session is defined as when a user begins and ends using or visiting a web site. It can be unlimited in length or strictly defined by a timeout period. If the site requires a log in or accesses sensitive data, it should time out after a period of inactivity. They can end a session by logging out or closing the browser.

Inactivity means the user has done nothing, made no requests of the web server, during a specified time. Ajax requests usually do not count.

Demo

The session time left is determined by the server, and, if you want to poll the server with an Ajax request, go for it. Javascript is used to keep track of the time left in the session.

The demo uses a simple log in with session timing handled by jquery and javascript. When the session expiration approaches, the user is warned and given an opportunity to restart the session. If the session time limit is reached, the user is prompted to log in again. If they ignore that prompt, the page automatically redirects to the log in form. In the demo this sequence of events takes 40 seconds to complete and is broken down as follows:

  1. Session timeout: 30 seconds
  2. Timeout warning: 20 seconds
  3. Session expired warning: 10 seconds
  4. Redirect to log in page: 10 seconds

Interrupting the User

The user’s attention can be diverted away from other open windows to the eminent session expiration by using a javascript alert in place of the jquery dialog box. Personal preference.

Code Breakdown

The application.cfc controls the session by creating non-persistent cookies for CFID and CFTOKEN so the session expires when the user’s browser closes. It also sets the session variable sessionStartTime. The sessionStartTime variable is used to illustrate the fact that the application.cfc function OnSessionStart only fires once. It does not fire every time a session is renewed or restarted.

<cfcomponent
    displayname="Application"
    output="false"
    hint="Handle the application.">

    <!--- Set up the application. --->
    <cfset THIS.Name = "sessiontest" />
    <cfset THIS.ApplicationTimeout = CreateTimeSpan(0,1,0,0) />
    <!--- CreateTimeSpan(days, hours, minutes, seconds) --->
    <cfset THIS.SessionTimeout = CreateTimeSpan(0,0,0,30) />
    <cfset THIS.SessionManagement = true />
    <cfset THIS.SetClientCookies = false />

    <cffunction
        name="OnSessionStart"
        access="public"
        returntype="void"
        output="false"
        hint="Fires ONLY ONCE when session first created and not when session renewed/restarted.">       

        <!---set cfid/cftoken as non-persistent cookies so session ends on browser close --->
        <cfif not IsDefined("Cookie.CFID")>
            <cflock scope="session" type="readonly" timeout="5">
                <cfcookie name="CFID" value="#session.CFID#">
                <cfcookie name="CFTOKEN" value="#session.CFTOKEN#">
                 <cfset session.SessionStartTime = Now() />
            </cflock>
        </cfif>

        <cfreturn />
    </cffunction>

</cfcomponent>

The log in page checks for a query string variable called ‘expired’ and, if present, deletes the session loggedin variable. This is there because the code is going to control the expiration of the session eliminating the need to compensate for browser latency. The actual session start time the time the page loads can differ by several seconds. To avoid having to add time to the session or any other fancy guesswork, when the allotted session time has expired according to the javascript timer on the page, they are done – session over.

If they are logged in, they get bumped to the index page. The rest is the logic that handles the log in form.

Note: I would not recommend handling a log in form this way. This is for demonstration only.

<cfif isDefined("url.expired") AND url.expired>
    <cfset StructDelete(session,"loggedin") />
</cfif>

<cfif isDefined("form.username") AND isDefined("form.pw") AND form.username EQ "session" AND form.pw EQ "test">
    <cfset session.loggedin = true />
</cfif>

<cfif StructKeyExists(session, "loggedin") AND session.loggedin>
    <cflocation url="index.cfm" addToken="no" />
</cfif>

Other than the log in form and a message for the user, that’s all there is to the log in page.

Handling Session Timeout

The index page handles the session timeout code. This could be a separate javascript included in every page. The first block simply determines if they are logged in. If they are not, send them to the login page. If they are, load the index page.

<!---if not logged in, send them to login page, else load the index page--->
<cfif NOT StructKeyExists(session, "loggedin") OR NOT session.loggedin>
    <cflocation url="login.cfm" addToken="no" />
<cfelse>
 <!---Load the page --->
</cfif>

Now the time variables are set and the a javascript timer is set to check the session every 10 seconds.
Javascript uses milliseconds so for clarity the time intervals multiply the number of seconds by 1,000. You could put 10000 in for 10 seconds but I think 10*1000 helps me determine that it is 10 seconds quite a bit faster. Do what is comfortable for you.

Also, a flag is set to determine if the warning dialog box has been opened and the countdown has begun.

//event to check session time variable declaration
var checkSessionTimeEvent = 0;

$(document).ready(function() {
	//event to check session time left (times 1000 to convert seconds to milliseconds)
    checkSessionTimeEvent = setInterval("checkSessionTime()",10*1000);
});

//Your timing variables in number of seconds

//total length of session in seconds
var sessionLength = 30;
//time warning shown (10 = warning box shown 10 seconds before session starts)
var warning = 10;
//time redirect forced (10 = redirect forced 10 seconds after session ends)
var forceRedirect = 10; 

//time session started
var requestTime = new Date();

//session timeout length
var timeoutLength = sessionLength*1000;

//set time for first warning, ten seconds before session expires
var warningTime = timeoutLength - (warning*1000);

//force redirect to log in page length (session timeout plus 10 seconds)
var forceRedirectLength = timeoutLength + (forceRedirect*1000);

//set number of seconds to count down from for countdown ticker
var countdownTime = warning;

//warning dialog open; countdown underway
var warningStarted = false;

The checkSessionTime function is what gets fired off every 10 seconds by the timer. It does a time comparison and opens the dialog boxes that warn the user.

function checkSessionTime()
{
	//get time now
	var timeNow = new Date(); 

	//event create countdown ticker variable declaration
	var countdownTickerEvent; 	

	//difference between time now and time session started variable declartion
	var timeDifference = 0;

	timeDifference = timeNow - requestTime;

    if (timeDifference > warningTime && warningStarted === false)
        {
            //call now for initial dialog box text (time left until session timeout)
            countdownTicker(); 

            //set as interval event to countdown seconds to session timeout
            countdownTickerEvent = setInterval("countdownTicker()", 1000);

            $('#dialogWarning').dialog('open');
            warningStarted = true;
        }
    else if (timeDifference > timeoutLength)
    	{
    		//close warning dialog box
            if ($('#dialogWarning').dialog('isOpen')) $('#dialogWarning').dialog('close');

            $('#dialogExpired').dialog('open');

            //clear (stop) countdown ticker
            clearInterval(countdownTickerEvent);
        }

     if (timeDifference > forceRedirectLength)
     	{
        	//clear (stop) checksession event
            clearInterval(checkSessionTimeEvent);

            //force relocation
            window.location="login.cfm?expired=true";
        }
}

The countdownTicker function provides a countdown inside the warning dialog box to prompt the user to act now. It uses a timer that fires every second for a 5,4,3,2,1 effect inside the dialog box.

function countdownTicker()
{
	//put countdown time left in dialog box
	$("span#dialogText-warning").html(countdownTime);

	//decrement countdownTime
	countdownTime--;
}

And, the dialog boxes either allow the user to reload the page or, if they did nothing when the warning popped up, it logs them out by redirecting to the log in page with the expired variable in the query string. Also, thanks to scube’s debugging, it now redirects to the log in if they hit the close button on the dialog box rather than the Login button.

$(function(){
                // jQuery UI Dialog
                $('#dialogWarning').dialog({
                    autoOpen: false,
                    width: 400,
                    modal: true,
                    resizable: false,
		    close: function() {
                            window.location="login.php?expired=true";
                     },
                    buttons: {
                        "Restart Session": function() {
                            location.reload();
                        }
                    }
                });

                $('#dialogExpired').dialog({
                    autoOpen: false,
                    width: 400,
                    modal: true,
                    resizable: false,
                    buttons: {
                        "Login": function() {
                            window.location="login.cfm?expired=true";
                        }
                    }
                });
});

The dialog box contents are at the bottom of the page but they could be just about anywhere in the body.

<!--Dialog box contents-->
<div id="dialogExpired" title="Session (Page) Expired!"><p><span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 0 0;"></span> Your session has expired!<p id="dialogText-expired"></p></div>

<div id="dialogWarning" title="Session (Page) Expiring!"><p><span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 0 0;"></span> Your session will expire in <span id="dialogText-warning"></span> seconds!</div>

Remember, this is just one example. There are other ways to do this. Use this, improve this, or roll your own.

Usual recommended jQuery and CF reading:

Download

ColdFusion Dropping, Losing, or Resetting Session Variables and CFID/CFTOKEN

Actually, this is not ColdFusion’s fault; it is a browser issue. Here’s what happens:

A user logins into your application, navigates through it, and then, inexplicably, gets booted out or not recognized. Sometimes it is just one page that seems to be the problem, sometimes several. Sometimes it is one browser, say IE, and not another, say FF.

What is crazy about this is that the application has, for all intents and purposes, worked correctly up to this point. The problem seemed to occur out of the blue. (Personally, I think it happened at the same time said user switched to IE7, but that is just my opinion.) And, what is also wonderful about this is that oftentimes you cannot recreate it. The user just screams and screams while you pull your hair out.

If and when you can recreate it, when you output the CFID and CFTOKEN tags you notice that they are different for the same user. They should not be. They never left the application.

For me the solution was simple. My application was using ‘www’ in the domain name on some pages and not on others as in www.mysite.com vs. mysite.com. The browser interpreted this as two different sites and made no association with the CFID/CFTOKEN cookies set by the application.cfm. So, ColdFusion reset them and bye-bye went my session variables.

Now, if this happens between http and https sites I do not know since this was not using a certificate. So, for that, you will have to figure it out.

This really frustrated me and I hope it helps someone else.

Google Visualization API and ColdFusion: Create a Data Table

The Google Visualization API is fun to play around with and it’s quite powerful. There are a number of charts and graphs to choose from and filling them up with data from ColdFusion is a snap. At least it was for this table example here. Most of this code comes straight out of Google’s documentation which was pretty good and easy to follow.

First things first…get some data. For simplicity sake, a query object is created and populated within a cfscript tag. In the real world you would pull this from a database.


<cfscript>
bookQuery = QueryNew("LastName,FirstName,Title,ISBN,PurchaseLink");
newRow = QueryAddRow(bookQuery);
QuerySetCell(bookQuery, "LastName", "Clifton" );
QuerySetCell(bookQuery, "FirstName","Brian" );
QuerySetCell(bookQuery, "Title","Advanced Web Metrics with Google Analytics" );
QuerySetCell(bookQuery, "ISBN","0470253126" );
QuerySetCell(bookQuery, "PurchaseLink","<a href='http://www.amazon.com/dp/0470253126?tag=jensbits-20&camp=14573&creative=327641&linkCode=as1&creativeASIN=0470253126&adid=0B10Q2FF6KHJB6ATNA5C&'>Amazon.com</a>" );
newRow = QueryAddRow(bookQuery);
QuerySetCell(bookQuery, "LastName", "Gilmore" );
QuerySetCell(bookQuery, "FirstName","W. Jason" );
QuerySetCell(bookQuery, "Title","Beginning PHP and MySQL: From Novice to Professional, Third Edition" );
QuerySetCell(bookQuery, "ISBN","1590598628" );
QuerySetCell(bookQuery, "PurchaseLink","<a href='http://www.amazon.com/gp/product/1590598628?ie=UTF8&tag=jensbits-20&link_code=as3&camp=211189&creative=373489&creativeASIN=1590598628'>Amazon.com</a>" );
newRow = QueryAddRow(bookQuery);
QuerySetCell(bookQuery, "LastName", "Forta" );
QuerySetCell(bookQuery, "FirstName","Ben" );
QuerySetCell(bookQuery, "Title","Adobe ColdFusion 8 Web Application Construction Kit, Volume 1: Getting Started " );
QuerySetCell(bookQuery, "ISBN","032151548X" );
QuerySetCell(bookQuery, "PurchaseLink","<a href='http://www.amazon.com/gp/product/032151548X?ie=UTF8&tag=jensbits-20&link_code=as3&camp=211189&creative=373489&creativeASIN=032151548X'>Amazon.com</a>" );
</cfscript>

Then a little query of a query so we have data to work with.

<cfquery dbtype="query" name="qryBooks">
SELECT * FROM bookQuery ORDER BY LastName
</cfquery>

Now for the magic. Load up Google’s API script.

 <!--Load the AJAX API-->
    <script type="text/javascript" src="http://www.google.com/jsapi"></script>

And, load the visualization package for what you are creating.

// Load the Visualization API and the table package.
  google.load('visualization', '1', {'packages':['table']});

Set the callback and load the callback function with data. In the drawVisualization function ColdFusion sets the number of rows with the query recordCount and loops through the query records and fills each row with data. The setCell method uses row and column coordinates to set the data. The rows start at 0 (zero) and since ColdFusion likes to start at 1 (one), we have to subtract 1 from the currentRow to get the row correct for the setCell method.

All single quotes need to be escaped. That can be done easily with the ColdFusion Replace function.

Create the table visualization object and call the draw method on it. The allowHtml option is set to true so the links work in the generated table.

  // Set a callback to run when the API is loaded.
  google.setOnLoadCallback(drawVisualization);

  // Callback that creates and populates a data table
  function drawVisualization() {
  	  var dataTable = new google.visualization.DataTable();
  	  dataTable.addColumn('string', 'Last Name');
  	  dataTable.addColumn('string', 'First Name');
  	  dataTable.addColumn('string', 'Title');
  	  dataTable.addColumn('string', 'ISBN');
  	  dataTable.addColumn('string', 'Buy Book');
  	  dataTable.addRows(#qryBooks.recordCount#);

  <cfloop query="qryBooks">
      dataTable.setCell(#currentRow#-1, 0, '#LastName#');
      dataTable.setCell(#currentRow#-1, 1, '#FirstName#');
      dataTable.setCell(#currentRow#-1, 2,'book-title', '#Title#', {style: 'font-weight:bold;'});
      dataTable.setCell(#currentRow#-1, 3, '#ISBN#');
      dataTable.setCell(#currentRow#-1, 4, '#Replace(PurchaseLink,"'","\'","all")#');
  </cfloop>

  //Create the table visualization object and call the draw method on it.
  var table = new google.visualization.Table(document.getElementById('table'));
  table.draw(dataTable, {allowHtml: true});

}

Stick a div in the body as the target for the table.

 <body>

	<!---   Div to hold the table   --->
    <div id="table"></div>

  </body>

You’ll end up with a table with click to sort columns that looks like this:
Google Table

This example just uses one short page of code so give the Google Visualization API a try. It’s an easy way to show data and trends.

Download code

Using ColdFusion to Generate Multiple Word Documents (Batch Creation)

UPDATE: Now includes line returns. The example’s form has been modified with additional fields so adding line returns to the Word doc could be shown. The zip has all the new files.

As a continuation (and request from Simon) to the Using ColdFusion to Generate a Custom Word Document post, here is an example of creating multiple Word documents in a batch-like manner.

Create the Word template as an RTF (.rtf) document with placeholders in the same manner as the previous post. Collect the data or have it spit out from a database.

Here’s where the code changes for multiple file generation. The number of files to be created is looped over, a dynamic name is generated for the new file, the output of the Replace functions is written to the new file, and the file is renamed with a .doc extension.

Also, a cfloop was added for the studentname fields so a line return (\par) could be appended on.

<cfset pathToRTF =  GetDirectoryFromPath(GetCurrentTemplatePath()) & "homeworkpass.rtf" />

<cfloop from="1" to="10" index="i">

    		<cfset studentnames = "" />

                <cfloop from="1" to="3" index="num">
                	<cfset studentnames = studentnames & " " & form["studentname" & num] & " \par " />
                </cfloop>

            <cflock name="homeworkpass" type="exclusive" timeout="30">

                <cfif form.cfversion EQ "pre8">
                <!--- CFMX7 or earlier --->
                    <cffile action="read" file="#pathToRTF#" variable="rtf">
                 <cfelse>
                <!--- CF8 or later --->
                    <cfset rtf = FileRead(pathToRTF) />
                </cfif>

                <cfset rtf = Replace(rtf,"%expirydate%",form.expirydate) />
                <cfset rtf = Replace(rtf,"%points%",form.points) />
                <cfset rtf = Replace(rtf,"%studentname%",studentnames) />
                <cfset rtf = Replace(rtf,"%subject%",form.subject) />
                <cfset rtf = Replace(rtf,"%datereceived%",form.datereceived) />

            </cflock>

            <cfset rtfFile = GetDirectoryFromPath(GetCurrentTemplatePath()) & "classPasses/" & "homeworkpass" & i & ".rtf" />
            <cfset docFile = GetDirectoryFromPath(GetCurrentTemplatePath()) & "classPasses/" & "homeworkpass" & i & ".doc" />

            <cfif form.cfversion EQ "pre8">
                <!--- CFMX7 or earlier --->
                    <cffile action="write" file="#rtfFile#" output="#rtf#" />
            <cfelse>
                <!--- CF8 or later --->
                    <cfset FileWrite(rtfFile,rtf) />
             </cfif>

             <cffile action="rename" source="#rtfFile#" destination="#docFile#" />

         </cfloop>
         <p>Passes Generated</p>
         <cfabort>

This should get you pointed in the right direction for creating multiple Word documents. And, again, there is a choice of ColdFusion 8 or an earlier version just to show the new, simpler functions available in CF8.

Download Files (zip)

Using ColdFusion to Generate a Custom Word Document

An oldie but a goodie, ColdFusion can be used to generate Word documents. Here is a quick example of writing to and opening a Word document.

Create Word Template

Create a Word template by adding “placeholders” in the document. This is done by wrapping a keyword with % (percent) signs as such: %firstname%. Put in as many as you want and add any necessary formatting in Word. Your replacement text will pick up the formatting. Save the file as an RTF (.rtf). For simplicity sake, put it in the same directory as the CF file used to generate it. Here an RTF file called homeworkpass.rtf is used.

Collect the Data

This example uses form fields to fill the document. Data from a database could be used just as easily. It’s a pretty standard form, nothing special.

<form action="index.cfm" method="post">
    <fieldset>
    <legend>Homework Pass Info</legend>
    <p><label for="expirydate">Expiration Date:</label><br />
    <input type="text" id="expirydate" name="expirydate" value="#DateFormat(DateAdd('d',10,Now()),'mm/dd/yy')#" /></p>

    <p><label for="points">Points:</label><br />
    <input type="text" id="points" name="points" value="100" /></p>

    <p><label for="studentname">Student Name:</label><br />
    <input type="text" id="studentname" name="studentname" /></p>

    <p><label for="subject">Subject:</label><br />
    <input type="text" id="subject" name="subject" /></p>

    <p><label for="datereceived">Date Received:</label><br />
    <input type="text" id="datereceived" name="datereceived" value="#DateFormat(Now(),'mm/dd/yy')#" /></p>
    </fieldset>

    <fieldset>
    <legend>Version of ColdFusion:</legend>
    <p><label><input type="radio" id="cfversion" name="cfversion" value="pre8" />CFMX or earlier</label><br />
    <label><input type="radio" id="cfversion" name="cfversion" value="post8" />CF8 or later</label></p>
    </fieldset>

    <p><input type="submit" name="submit" value="Generate Word" /></p>
    </form>

Replace Text with ColdFusion

After a little error checking, the file is opened and the placeholders are swapped out with the form field values using the Replace() function in the homeworkpass.rtf.

<cfset error = "" />

<cfif isDefined("form.submit")>

	<cfloop collection="#form#" item="i">
		<cfif NOT Len(form[i]) OR  (NOT isDefined("form.cfversion"))>
			<cfset error = "All fields must have a value" />
		</cfif>
	</cfloop>

	<cfif NOT Len(error)>

    	<cfset pathToRTF =  GetDirectoryFromPath(GetCurrentTemplatePath()) & "homeworkpass.rtf" />        

        <cflock name="homeworkpass" type="exclusive" timeout="30">

			<cfif form.cfversion EQ "pre8">
            <!--- CFMX7 or earlier --->
                <cffile action="read" file="#pathToRTF#" variable="rtf">
             <cfelse>
            <!--- CF8 or later --->
                <cfset rtf = FileRead(pathToRTF) />
            </cfif>

            <cfset rtf = Replace(rtf,"%expirydate%",form.expirydate) />
            <cfset rtf = Replace(rtf,"%points%",form.points) />
            <cfset rtf = Replace(rtf,"%studentname%",form.studentname) />
            <cfset rtf = Replace(rtf,"%subject%",form.subject) />
            <cfset rtf = Replace(rtf,"%datereceived%",form.datereceived) />

        </cflock>

        <cfheader name="content-disposition" value="filename=HomeworkPass.doc" />

    	<cfcontent type="application/msword"><cfoutput>#rtf#</cfoutput>
    	<cfabort>

	</cfif>

</cfif>

There is a section here to allow for ColdFusion version specification. For ColdFusion 8 the FileRead() function can be used. All other versions of CF will need to use cffile.

<cfif form.cfversion EQ "pre8">
    	<!--- CFMX7 or earlier --->
    		<cffile action="read" file="#pathToRTF#" variable="rtf">
   		 <cfelse>
    	<!--- CF8 or later --->
    		<cfset rtf = FileRead(pathToRTF) />
    	</cfif>

Offering a choice for ColdFusion will not be necessary in production; it’s here for illustrative purposes only. At this time all versions of Word can open .doc files.

Output the Word File

To output the Word file use cfcontent and stop processing the rest of the page.

<cfcontent type="application/msword"><cfoutput>#rtf#</cfoutput>
    	<cfabort>

Download Files (zip)

To create multiple Word documents in a batch-like process see the Using ColdFusion to Generate Multiple Word Documents (Batch Creation) post.