Sunday, October 6, 2024

Capturing a Visitors 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.

15.

16.

17.

18. Now we’re ready to display the clickstream to the user.

19.

20. Start an HTML table…

21. Open a <cfoutput> tag…

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

23. For each iteration of the loop, we want new table row (<tr>)

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

Article first appeared at EasyCFM.com.

Charlie lives in Phoenix, AZ with his lovely wife and daughter, two dogs,
and a pig. He has been using ColdFusion since version 1.5, and likes to
think that one day he’ll be good at it. A frequent contributor to Easycfm, Charlie enjoys writing ColdFusion tutorials, and
helping others in the forums. He invites you to view his own site at
http://charlie.griefer.com or drop him an email at charlie@griefer.com.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles