Server Time:
Sunday May 11 2008 08:13 PM  
Your Time:
  
HostMySite.Com is sponsoring this tutorial, please visit their site today!
This tutorial is sponsored by HostMySite.Com - ColdFusion Hosting

Save your visitor's clickstreams
by: Charlie Griefer (CJ)
Email this tutorial to a friend Display Printer Friendly Format
[Download in PDF Format] [Download in FlashPaper Format]

Capturing a Visitor’s Clickstream

A nice feature that I’ve seen implemented on a few Web sites is the ability for the visitor to view their clickstream (essentially, the path they’ve taken through your site). This allows the user to jump back to a particular page they’ve visited at any time.

For a (very ugly) sample, see http://charlie.griefer.com/code/clickstream/

By providing this list to your users, you allow them to go, “hey...where was I a couple of pages back when I saw that one picture...?”...and then peruse the list of recently viewed pages.

I’ve chosen to build this as a custom tag. For those unfamiliar with custom tags, it’s simply a ColdFusion template that acts as a <cfinclude>, but can accept parameters. The reason for doing it as a custom tag is because it’s essentially the same code repeated over and over on each page. While this would ordinarily make it a candidate to simply be <cfinclude>’d, there is one difference from page to page...each page must identify itself in order to be added to the clickstream list. I will be passing that page name to the custom tag as an attribute.

The custom tag is called breadcrumb.cfm. A custom tag is called by adding ‘cf_’ to the name of the template, and dropping the extension.

So to include the custom tag in a particular template, the code would be:

<cf_breadcrumb>

The way I’ve put this together, breadcrumb.cfm accepts two attributes. One is the title of the current page (to be added to the list of visited pages). The other is the number of pages to ‘remember’ (by default, it will display the last 10 pages visited). So to call the tag with the attributes, the code would look like:

<cf_breadcrumb 
      pageTitle=
”Home Page” 
      trail_length=
”10”>

This code would be on the home page, of course. To call the tag from the ‘About Us’ page, it would be:

<cf_breadcrumb 
      pageTitle=
”About Us” 
      trail_length=
”10”>

...and so on.

Because the clickstream persists from page to page, and is unique for each user, it will be stored in a session variable. The name of the session variable is session.clickstream (original, yes?). 

Because we’re using session variables, there needs to be an Application.cfm. This Application.cfm is very simple. It creates a name for the application, using the <cfapplication> tag...which also specifies the timeout value of the session variables. It then checks to see if the session variable exists yet. If not, it creates it.

Here’s the complete code for Application.cfm:

<cfapplication 
        name=
"clickstream" 
        sessionmanagement=
"yes" 
        sessiontimeout=
"#createTimeSpan(0,0,20,0)#">

<cfscript>
    if (NOT structKeyExists(session, 'clickstream')) {
        session.clickstream = arrayNew(1);
    }
</cfscript>


The <cfapplication> tag is self-explanatory. The <cfscript> block is simply checking for the existence of the variable ‘clickstream’ within the session scope. It might look a bit unfamiliar, but it’s essentially the same thing as <cfparam name=”session.clickstream” default=”#arrayNew(1)#”>.

As you can see, session.clickstream is an array. This makes sense, as we’re going to be storing multiple values in one location.

Now to dissect the custom tag itself. The code for breadcrumb.cfm:

1. <cfparam name="attributes.trail_length" default="10">
2. <cfif NOT structKeyExists(attributes, 'pageTitle')>
3.    <cfexit>
4. </cfif>

5. <cflock name="addNewPage" type="exclusive" timeout="10">
6. <cfscript>
7.    if ((arrayIsEmpty(session.clickstream)) OR (compare(attributes.pageTitle, session.clickstream[arrayLen(session.clickstream)].title))) {
8.    if (arrayLen(session.clickstream) EQ attributes.trail_length) {
9.    temp = arrayDeleteAt(session.clickstream, 1);
10. }
11. temp = arrayAppend(session.clickstream, structNew());

12. session.clickstream[arrayLen(session.clickstream)].title = attributes.pageTitle;
13. session.clickstream[arrayLen(session.clickstream)].path = listLast(cgi.script_name, '/');
14. }
15. </cfscript>
16. </cflock>

17. <br /><br /><br /><br />

18. <cfoutput>Your Click Path:</cfoutput>
19. <br />
20. <table style="border:solid #000000 1px; width:150px;" cellpadding="1" cellspacing="0">
21.     <cfoutput>
22.     <cfloop from="#arrayLen(session.clickstream)#" to=”1" index="i" step="-1">
23.     <tr>
24.         <td><a href="#session.clickstream[i].path#">#session.clickstream[i].title#</a></td>
25.     </tr>
26.     </cfloop>
27.     </cfoutput>
28. </table>


Since breadcrumb.cfm is a bit involved, let’s go over it line by line.

  1. Breadcrumb.cfm is expecting two attributes to be passed to it. One is trail_length. If this attribute is not passed, we set it to a value of 10 by default.
  2. The other attribute that the tag expects is “pageTitle”. This is important, as it is the name that will be appended to the clickstream. If it is not passed, we really don’t know what value to assign it by default. For that reason, we’re not even going to try. If it’s not provided, we use <cfexit> to gracefully exit the custom tag.
  3. The cfexit
  4. </cfif>
  5. Because we’re dealing with session variables, it is considered a ‘best practice’ to use <cflock>. This helps to ensure the integrity of the shared scope variables. 
  6. Open a <cfscript> block
  7. Line 7 can get a bit tricky. On my first draft of this code, I could hit ‘Home Page’ 10 times in a row, and my clickstream would be ‘Home Page’ repeated 10 times. Likewise, if I was on a page and I hit refresh, that page was added twice in succession to the clickstream. I didn’t want this behavior. No one page should show up twice (or more) in succession. So I realized I needed to check the title of the last element in the array with the current title. If they are the same, I don’t want to do anything. The compare() function returns 0 if the two arguments are equal. 

    The one problem with that is that if the array was empty (if the current page was the first page in my clickstream), the application errors out on the compare() function (because there *is* no last element to compare (there can’t be a last element if there are no elements). So the one condition under which I don’t want the compare() function to run is if the array is currently empty. Hence the if arrayIsEmpty(session.clickstream) which is added to condition.
  8. Line 8 checks the length of the array to see if it has met the maximum length as specified by the attributes.trail_length value. There’s no use in continuing to store values if the user is only going to see the first 10 (or whatever value is specified). So in order to avoid exceeding the value of attributes.trail_length, we remove the first element from the array once we hit the specified length.
  9. The actual deletion of the first element in the array (remember, CF arrays are dynamic in nature, and when the element at position 1 is deleted, the elements at positions 2 and higher will shift down one position to fill the void).
  10. Closing curly brace for the condition in #8.
  11. Now we’re ready to append the current template into the clickstream. Now you’ll see that each array position in session.clickstream is actually a structure (because we want to store both the name of the page and the URL). You’ll understand that a bit more on lines 12 and 13. For now, suffice it to say that we need to create a new array position, which will be a structure :)
  12. One of the keys of the structure is ‘title’. This is referenced as session.clickstream[n].title (where n is a position in the array between 1 and the array length). The value of the key is the value passed in attributes.pageTitle. This is the value that is actually displayed to the user in the clickstream.
  13. The other key in the structure is ‘path’. This value was not passed to the custom tag, but can be ascertained using the cgi environment variable #cgi.script_name# and the listLast() function. This value will allow us to make the individual items in the clickstream into links.
  14. Now we’re ready to display the clickstream to the user.
  15. Start an HTML table...
  16. Open a <cfoutput> tag...
  17. Now we’re going to loop over the session.clickstream array. So we loop from the length of the array to 1, with a step of –1 (we want to loop backwards, so the most recently viewed page is at the top of the clickstream list).
  18. For each iteration of the loop, we want new table row (<tr>)
  19. Inside of a <td>, output the value of session.clickstream[i].title, inside of <a href> tags that contain the value of session.clickstream[i].path. The user now clearly sees the clickstream, with a link to any page contained therein.

    The rest of the code simply closes <tr>s and <table>s.

To implement, simply place the call to the custom tag on any pages that you would like included in the clickstream.  As shown earlier, it would look like this:

<cf_breadcrumb
         pageTitle=
"Contact Us"
         trail_length=
"10">

This would, of course, go on a Contact Us page (contactus.cfm(?)), and specifies that the clickstream should 'remember' 10 pages back.

While you'd probably want this code on most of the pages in your site, one caveat would be do NOT place the tag on form action pages.  A form's action page should only be accessible via the form itself.  If you do include the action page it would most likely simply display a "Warning Page Has Expired" message, which is of no use to your visitors.

That’s all there is to it. As always, questions, comments, criticisms, and large inheritances are welcome. Feel free to e-mail me, or post a question on the Easycfm.com forums.


Date added: Mon. June 16, 2003
Posted by: Charlie Griefer (CJ) | Views: 13114 | Tested Platforms: CF5,CFMX | Difficulty: Intermediate
Categories Listed: Other Reusing Code

HostMySite.Com is sponsoring this tutorial, please visit their site today!
This tutorial is sponsored by HostMySite.Com - ColdFusion Hosting

This author's other tutorials:
Helping users obtain their passwords
Your site requires your visitors to log in. of course, some of your visitors are going to forget their passwords (ok, most will forget their passwords). You don't want them to have to send you an e-mail, and then wait for a response. They need immediate access. <br><br> This tutorial shows two methods by which you can accomodate them. - Date added: Thu. August 28, 2003
CF 'Best Practices'
Some tips and techniques that I've picked up over the years. I don't maintain that these are 'official' or 'absolute'...they are simply my preference and things that have worked for me. I would like to share them here, and leave you to make the decision as to whether or not they fit in your 'code arsenal' :) - Date added: Fri. August 15, 2003
JavaScript Form Validation
Yes, I know we're a ColdFusion site...but ColdFusion does not live in a vacuum. We have to know SQL, HTML, CSS...and sometimes...JavaScript! This tutorial focuses on using JavaScript (in lieu of cfform) to create client side form validation (and explains why writing your own is better than using <cfform>). - Date added: Thu. August 14, 2003
arrays and structures - part 3
part three of a three-part tutorial designed to gently introduce you to the world of complex variables. - Date added: Mon. August 11, 2003
arrays and structures - part 2
part two of a three-part tutorial designed to gently introduce you to the world of complex variables. - Date added: Mon. August 11, 2003

Additional Tutorials:
· arrays and structures - part 1

· Grouping Output in CF

· Remote File Management

· Dynamic Column Output (Part Two)

· Dynamic Column Output (Part One)

· to cfqueryparam or not to cfqueryparam

· ColdFusion Mad Libs - Part II

· ColdFusion Mad Libs - Part I

· CFSCRIPT Intro
Please rate this tutorial:
5 Stars 4 Stars 3 Stars 2 Stars 1 Stars
Comments on this tutorial
Read previous comments on this particular tutorial
Or..
..you could <CFINCLUDE> it, and simply pass the page name from a variable set in the page you are including it from, which the included page will read!
Posted by: Matt
Posted on: 11/03/2004 10:22 AM
Set the path to current URL
Charlie:
Great tutorial
Its working great for me as long as all the files are under the same directory.
As I am new to CFM, I tried all night (until 4:00 AM :) trying to set the path to match my root directory and its not working.

I saw that you are setting the path from the array on line 13 of the tutorial.
I would like to change it because I have lots of directories and would like the path to always match the correct directory.
Can you show me how to do that ;)
I appreciate the help

Posted by: BroChild
Posted on: 05/07/2005 09:28 AM
fix it
Never mind
I fixed it by placing the customtag under the custom tag directory and removing the '/' from the list from the listlast function
Posted by: BroChild
Posted on: 05/07/2005 10:20 AM
Tough ask.....Storing stream in database - Now that would be great !
Would it be possible to store this click stream in a database by storing all the info initially in a session variable and then writing to the database when the user logs out. Is there any way you could force the session variable contents to be written to the database when the user simply closes the browser. If you include the following code would the session information be preserved ?

<InvalidTag LANGUAGE="JavaScript">
function changeurl() {
window.open("write_session_to_database.cfm");
}
</SCRIPT>

<BODY onUnload="changeurl()" onBeforeUnload="changeurl()">

</body>
Posted by: Paul
Posted on: 06/15/2006 04:33 PM
Modified breadcrumb.cfm output
I liked this but I wanted the display to be horizontal instead of vertical so here is what I did. I also implemented the BroChild fix posted above and this now works with subdirectory/links, too! Thanks BroChild! Oh, and I also added the option to clear the clickstream. We had clients request that they can clear their clickstream and this gave them a piece of mind. We informed them we weren't tracking their clicks but I digress, silly ole bear.

Here is my breadcrumb.cfm file
[code]
<cfparam name="attributes.trail_length" default="4">
<cfif NOT structKeyExists(attributes, 'pageTitle')>
<cfexit>
</cfif>
<cfif structkeyExists(url, 'clear')>
<cfscript>
ArrayClear(session.clickstream);
</cfscript>
</cfif>
<cflock name="addNewPage" type="exclusive" timeout="10">
<cfscript>
if ((arrayIsEmpty(session.clickstream)) OR (compare(attributes.pageTitle, session.clickstream[arrayLen(session.clickstream)].title)))
{
if (arrayLen(session.clickstream) EQ attributes.trail_length)
{
temp = arrayDeleteAt(session.clickstream, 1);
}
temp = arrayAppend(session.clickstream, structNew());
session.clickstream[arrayLen(session.clickstream)].title = attributes.pageTitle;
session.clickstream[arrayLen(session.clickstream)].path = listLast(cgi.script_name);
}
</cfscript>
</cflock>

<br /><br /><br /><br />
<cfoutput><a href="#cgi.script_name#?clear=y">Clear Clickstream</a></cfoutput><br>

<cfoutput>Your Click Path:</cfoutput>
<br />
<!--- <table style="border:solid #000000 1px; width:150px;" cellpadding="1" cellspacing="0">
<cfoutput>
<cfloop from="#arrayLen(session.clickstream)#" to="1" index="i" step="-1">
<tr>
<td><a href="#session.clickstream[i].path#">#session.clickstream[i].title#</a></td>
</tr>
</cfloop>
</cfoutput>
</table> --->

<cfoutput><cfloop from="1" to="#arrayLen(session.clickstream)#" index="i" step="1">
<a href="#session.clickstream[i].path#">#session.clickstream[i].title#</a> <cfif arrayLen(session.clickstream) neq i>></cfif></cfloop></cfoutput>
<br /><br />[/code]
Posted by: Rev2Red
Posted on: 10/02/2007 11:32 AM
Post a new comment on this tutorial
post a new comment on this particular tutorial
Your Name:
Your Email:
Comment Title:
Comments:
Key Phrase:
 
Skyscrapper Banner Advertisement
ProWorkFlow.Com

You are 1 of 700 active sessions! | Privacy | Company
Copyright © 2002 EasyCFM.Com, LLC. (Easy ColdFusion Tutorials) All Rights Reserved
All other trademarks and copyrights are the property of their respective holders.
ColdFusion Hosting ColdFusion Hosting
ADD TO:
Blink
Del.icio.us
Digg
Furl
Google
Simpy
Spurl
Y! MyWeb