Greg's Blog

helping me remember what I figure out

Pages Viewed

| Comments

Just the other day I had to write a bit of functionality for a web site whereby a user would be able to hover over a link and display a dHTML pop up list of the last 5 pages he had browsed on the site. In the following I’ll talk you through how I built up this little feature. So let’s get cracking

Since we are going to be tracking the users motion through the web site, we’ll need to somehow keep track of the pages he has viewed. For this purpose I decided to use a session variable. So you’ll need to enable session management, i.e. create an application.cfm (for a brief introduction have a look at: How to use cfapplication ) like such:

<!— -Enable session management —>
<cfapplication name=”Pages_viewed” sessionmanagement=”Yes” sessiontimeout=”#CreateTimeSpan(0,1,0,0)#”>
<cfset application.http=”http://127.0.0.1/pages_viewed/”>

I also took the opportunity at this stage to specify an application variable application.http to store the full url of where the application is located (I use this in some of the scripts to specify the full url to images). OK with session management enabled, let’s have a look at capturing the pages viewed by the user that will ultimately build up our pop up menu.

All the information we need I decided to capture after the page has been processed, so I could have either created a template that I include at the bottom of the page or use OnRequestEnd.cfm, which works in the same manner that application.cfm does, with the difference that it is included at the end of every page. However it has to reside in the same directory as your application.cfm. Before delving into any more detail here is the code for that page:

<!— -Pages viewed —>
<!— -First hit of the site, set session.current to home so that at next page display it has a name to display —>
<cfif NOT isDefined(“HTTP_REFERER”)>
  <cflock scope=”SESSION” timeout=”10” type=”EXCLUSIVE”>
    <cfset session.current = local.location>
  </cflock>
</cfif>
<!— -OK now we are in the site- —>
<cfif isDefined(“HTTP_REFERER”)>
  <!— -If accessed from a different section of the site, insert the new http_referer and section name in front of the existing information- —>
  <cfif isDefined(“session.pagesViewed”)>
    <cflock scope=”SESSION” timeout=”10” type=”EXCLUSIVE”>
      <cfset session.pagesViewed = session.current&”-“&HTTP_REFERER&”,”&session.pagesViewed>
    </cflock>
  <cfelse><!— -If accessing site by hitting the home page for the first time —>
    <cflock scope=”SESSION” timeout=”10” type=”EXCLUSIVE”>
      <cfset session.pagesViewed = session.current&”-“&#HTTP_REFERER#>
    </cflock>
  </cfif>
  <!— -Now let’s remember the current location for the next page hit.- —>
  <cflock scope=”SESSION” timeout=”10” type=”EXCLUSIVE”>
    <cfset session.current = local.location>
  </cflock>
</cfif>

<!— -Include hiermenu loader- —>
<cfinclude template=”js/HM_loader.cfm”>

Still with me? In order to track the pages that the user has viewed we will be making use of a variable provided by the cold fusion server: HTTP_REFERER, which is the page the user has just come from to get to this point. Furthermore for each page I set a local variable that captures the page name for display purposes (named local.location). This last variable you will need to declare at the beginning each and every page, and assign it a value (such as <cfset local.location = “your page title”>). In order for us to use that defined page title from page to page I store it’s value in a session variable, session.current, which is then accessible to the next page. Why does it need to be accessible to next page? The url value, i.e. HTTP_REFERER doesn’t get set until the following page and we want to associate that url value with a logical name, i.e. the page title, of the previous page then and hence it needs to be passed from page to page so that the values can be associated with one another. Please note that I overwrite the value of this variable with the new page value once the page has been processed. But before I do this I store this variable and the HTTP_REFERER in another session variable called session.pagesViewed, which is a list of all the pages viewed and their associated title. Well that’s the logic, now for the explanation of the code listed above.

The first time a user hits the site, Cold Fusion won’t define or assign a value to the HTTP_REFERER variable, however I still need to capture the page title information all I really need to set session.current to is the title of the page and I can leave the HTTP_REFERER variable blank (since it wil be set on the next page hit and it is the url of the page that I have just come from that I am after). At this stage no dHTML menu is being built yet, that comes after we build the list of pagess visited, which is what comes next.

The conditional statement looks for HTTP_REFERER and since on the second hit, it is defined it moves to the next conditional statement which looks for session.pagesViewed and sees if that has been defined. Since this is our first hit it won’t be and the processing moves to the else statement, which marks the start of the list building process. There we defined session.pagesViewed and assign it a pair value of HTTP_REFERER and session.current (please note that the value pair is delimited by “-“). There you go the first item of your list has been built. Next hit of the site it will recognise that session.pagesViewed has been declared and pre-pend session.pagesViewed with a new pairing and that this new pairing is delimited form the old one by “,”. And so on and so forth. A quick aside, every time you access a session variable you should lock it exclusively, so that the values don’t get corrupted.

Now that all the list building is over for that hit we can move onto building the pop menu (which is the last statement of the code, where we include the HM_Loader.cfm page), which takes us neatly onto the JavaScripts and dHTML. The whole thing is based on the hierarchical menus from webreference (the whole source and relevant explanation can be found here). These scripts work pretty much in all browsers and are under ongoing development. I had to modify them slightly and here is what I did. Once you extract the source you will see that there are a whole heap of javascript files (hierarrays.js, hiermenus.js, HM_Arrays.js, HM_loader.js, HM_ScriptDOM.js, HM_ScriptIE4.js, HM_ScriptNS4.js). The menus are built based on a set of arrays that are stored in external file called: HM_Arrays.js. Now for the purposes of our pages viewed functionality these are no good. This would work great with a set of menus that are built based on database content, but since we are tracking an individual users movement through the site, we would be permanently overwriting other users Array of pages viewed. To solve this problem I opted for the easiest solution and that was to modify the HM_Loader.js file. For starters I turned it into a cfm page, so that I could use the application.http to specify the full path to images and also use the <cfinclude> tag. The main modification can be found at the end of the HM_loader.cfm (lines 86 and 87), where originally you have two document.write Javascript statements.

if(HM_IsMenu) {
  document.write(“<SCR” + “IPT LANGUAGE=’JavaScript1.2’ SRC=’../js/HM_Arrays.js’ TYPE=’text/javascript’><\/SCR” + “IPT>”);
  document.write(“<SCR” + “IPT LANGUAGE=’JavaScript1.2’ SRC=’../js/HM_Script”+ HM_BrowserString +”.js’ TYPE=’text/javascript’><\/SCR” + “IPT>”);
}

Their purpose is to in the first place load the external menu array and secondly load the script that builds the menu system. Now modify the script to include HM_Arrays.cfm, rather than HM_Arrays.js. What we are doing here is making the building of the array dynamic and rather than loading an external file we are embedding the arrays which is populated on the fly into the page and hence overcoming the problem we had with ensuring that each users only sees his list of pages viewed. Here is how the end of the script should look like now:

if(HM_IsMenu) {
  <cfinclude template=”HM_Arrays.cfm”>
  document.write(“<SCR” + “IPT LANGUAGE=’JavaScript1.2’ SRC=’js/HM_Script”+ HM_BrowserString +”.js’ TYPE=’text/javascript’><\/SCR” + “IPT>”);
}

Now moving on swiftly to populating the array that we will use for pop up menu. As I mentioned I renamed the HM_Arrays.js file to a .cfm file so that I could use Cold Fusion to dynamically build the list. SO have a quick look at the code and then I’ll explain what is going on:

<!— -Display options for the dHTML pop up menu, such as positioning and colours- —>
HM_Array1 = [
[160, // menu width
130, // left_position
55, // top_position
“#787e98”, // font_color
“#d2404a”, // mouseover_font_color
“white”, // background_color
“white”, // mouseover_background_color
“#787e98”, // border_color
“#787e98”, // separator_color
0, // top_is_permanent
0, // top_is_horizontal
0, // tree_is_horizontal
1, // position_under
1, // top_more_images_visible
1, // tree_more_images_visible
“null”, // evaluate_upon_tree_show
“null”, // evaluate_upon_tree_hide
, // right_to_left
], // display_on_click

<!— -Supress any error messages session.pages viewed is not defined —>
<cfif isDefined(“session.pagesViewed”)>
  <!— -Counter used to limit the total output (set to 5)- —>
  <cfset local.counter = 1>
  <!— -Now loop over the page urls and titles stored- —>
  <cfloop list=”#session.pagesViewed#” index=”i”>
  <!— -Check to limit output to 5, if counter less than five then process this- —>
    <cfif local.counter lte 5>
    <!— -Use another counter to first grab the title (1) then the url (2)- —>
      <cfset local.loop_counter = 1>
      <!— -Loop over our new list, with a specific delimiter “-“- —>
      <cfloop list=”#i#” delimiters=”-” index=”j”>
        <!— -If 1 then set title value- —>
        <cfif local.loop_counter eq 1>
          <cfset local.title=j>
        <!— -if anything else, i.e. 2 then grab the url- —>
        <cfelse>
          <cfset local.url=j>
        </cfif>
        <!— -Increment loop_counter for this loop and continue looping —>
        <cfset local.loop_counter = local.loop_counter +1>
      </cfloop>
      <!— -Now that we have both values we can build up the Javascript array- —>
      <cfoutput>[“#local.title#”,”#local.url#”,1,0,0]<cfif local.counter neq 8>,</cfif></cfoutput>
      <!— -If local counter greater than 5 break out of the loop- —>
    <cfelse>
      <cfbreak>
    </cfif>
    <!— -Incrememnt counter and return to beginning of loop- —>
    <cfset local.counter = local.counter + 1>
  </cfloop>
</cfif>
]

The opening lines of the code show here are just display options, i.e. setting the positioning, the colouring, etc, so go wild tinkering with the settings. Where the Cold Fusion code begins is where we start building the arrays. If you remember we set a list called session.pagesViewed, which in the first instance was a list made up of a combination of page title and url. The page title and url combination was delimited by a “,”, whereas the page title and url were separated by “-“. So what we have in essence is a list within a list. So we can loop over both these lists to build up our array, which is in the format: [“page title”, “url”,1,0,0]. We are only interested in the values between quotation marks as this will be a simple menu with any child menus or the likes, so we can set the three remaining values to 1,0,0 (for full details on what these values do, please consult this web reference column.

So we start looping over the first list (the combined page title and url list, delimited by a “,”). Since I only want the first 5 values I first declared a counter before beginning the loop process (<cfset local.loop_counter = 1>) and inserted a conditional check to make sure the counter was less than or equal to five. If you wish to display more than the last 5 pages viewed modify that conditional statement (<cfif local.counter lte 5>). If the value for the counter is greater, then I break out of the loop, else I use the index value ”i” and loop a second time over that list using the delimiter “-” to break apart the page title and the url. Since I knew that I had stored the title in the first instance and the url next I set a internal counter for that loop (local.loop_counter) and a conditional statement. If the local.loop_counter value is one set a local variable, local.title to the index value of j and if the value of local.loop_counter is anything else (i.e. 2) then set local.url to j. Now that have both values we require the internal loop terminates and we can write the array element to the page. The external loop starts over and grabs the next combination of values and so on and so forth until you have reached the desired number of pages viewed to output.

Right we are nearly there, now all you need to do is include the initial javascript code for the dHTML pop up menus in your page header section (which I stored in a template called _pages_viewed.cfm and included in every page that I made use of the pages viewed functionality) and a function call to will actually display the pop up menu. Which you do like such:

<a href=”” onMouseOut=”popDown(‘HM_Menu1’);” onMouseOver=”popUp(‘HM_Menu1’,event);”>Pages viewed</a>

Well that’s it. It’s a lot to digest and I hope it makes sense. If anything is unclear I have included a functional set of pages that should demonstrate the workings of this feature here and if things still aren’t clear well then don’t hesitate to drop me a line. Until next time.