reptile7's JavaScript blog
Thursday, July 22, 2010
You Can Count Me Out In
Blog Entry #185

For HTML Goodies' JavaScript Script Tip #64, we analyzed a script that uses (well, attempts to use) the document.cookie property to distinguish between first-time and return visitors to a Web page. In today's post, we will discuss HTML Goodies' "So You Want A Cookie Counter, Huh?" tutorial, which presents a script that extends the Script Tip #64 concept by using document.cookie to actually count the number of times a visitor has been to a Web page; the script thus serves as a simple Web counter for that visitor and page (not for all visitors to the page).

In brief, the "So You Want A Cookie Counter, Huh?" script writes to the user's hard disk a cookie whose value attribute value is a number n: n = 1 for a first-time visit, n = 2 for a second visit, etc.; just before the cookie is written, the script displays to the user a "You have been to my site n-1 time(s) before" message.

Joe provides a demo for the script in the tutorial's "The Effect" section. The demo currently does not work because the expires date of the cookie it sets - 4 April 2010 - is in the past; putting the expires date in the future sets things to right.

For a reason that is not clear to me, the tutorial's Cookie Count Script link to the script goes to a 404 page whereas its Cookie Count Script Explanation link to the script annotated with brief commentary works fine - both links should redirect to HTML Goodies' /legacy/beyond/javascript/ directory but only the latter does so in practice - something strange must be going on on the server side. The unannotated script can be accessed at

In the sections below, we'll first run through a quick script deconstruction and then I'll tell you how I would rewrite the script, with an up-to-date demo to follow.

First-time visit

The "So You Want A Cookie Counter, Huh?" script largely consists of two functions:
(1) a doCookie( ) function that writes a Counter_Cookie=n cookie; and
(2) a gettimes( ) function that returns an "n-1 time(s)" string.
Although it appears second in the script's main script element, the gettimes( ) function is called first, before the page has finished loading, by the following code in a separate script element in the document body:

document.write("<b>You have been to my site " + gettimes( ) + " before.</b>");

The gettimes( ) function first checks if there are any cookies associated with the current document; if not, it returns "0 times" to the gettimes( ) function call in the preceding document.write( ) command.
var cookie_name = "Counter_Cookie";
function gettimes( ) {
	if (document.cookie) { ... }
	return "0 times"; }
To be sure, if document.cookie is an empty string, then the user is definitely a first-time visitor. But there is a design problem here in that a non-empty document.cookie does not mean that the user is a return visitor; the user could have already picked up one or more cookies from a domain/path-related page: for example, if we were to surf to "So You Want A Cookie Counter, Huh?" for the very first time from the HTML Goodies home page, then document.cookie would contain cookies that were set at the latter page. As an if condition, document.cookie is thus an inadequate test for determining first-time vs. return visitor status. However, the if (document.cookie) { ... } block's first two lines spell out what we really want:

index = document.cookie.indexOf(cookie_name);
if (index != -1) { /* This is the if statement that should have an else clause returning "0 times", but it doesn't. */

The document.cookie.indexOf(cookie_name) command looks for the presence of Counter_Cookie, the name attribute value of the cookie we'll be writing in just a bit, in document.cookie; if it's there (if the index of the starting C is not -1), then we are probably dealing with a return visitor. This test isn't 100% foolproof either in that it's possible that another page could have added a Counter_Cookie cookie to document.cookie*, but it's as good as we're gonna get.

*Indeed, in the tutorial's "More Than One" section, Joe notes that the script could be added to every page of a Web site in order to count the total number of site pages visited by a user.

Anyway, let's assume that the user really is a first-time visitor whose document.cookie return vis-à-vis the current document is an empty string, and that "You have been to my site 0 times before" is printed out for the user as detailed above. Once the page has finished loading, the <body onload="doCookie( );"> element's onload event handler calls the doCookie( ) function in the main script element. Like the gettimes( ) function, the doCookie( ) function first checks if document.cookie does or does not contain any cookies and then determines the index of Counter_Cookie's starting C in document.cookie:
function doCookie( ) {
	if (document.cookie) {
		 index = document.cookie.indexOf(cookie_name); }
	else {
		 index = -1; }
As intimated earlier, an empty document.cookie converts to false as an if condition, and therefore -1 is assigned to index. But because an empty string can serve as the 'base string' for an indexOf( ) comparison, there's actually no need to conditionalize the index assignment: the if container and else clause can be thrown out, and the index = document.cookie.indexOf(cookie_name); statement can stand (and will dutifully return -1) on its own - this is also true for the corresponding code in the gettimes( ) function.

The next doCookie( ) line sets a date/time value for the expires attribute of the Counter_Cookie cookie:

var expires = "Monday, 04-Apr-2010 05:00:00 GMT";

The preceding expires value must of course be reset, but to what? Looking over my Safari cookie list, I see that five of those cookies (all of them from, BTW) are set to expire in 2200 - sounds like a good year, eh? Moreover, Joe's expires format has too many details to keep track of for my tastes; I much prefer the toUTCString( )-based formulation given below:

var expires = new Date("Jan 1, 2200").toUTCString( );

We're finally ready to write the Counter_Cookie cookie:
if (index == -1) {
	document.cookie = cookie_name + "=1; expires=" + expires; }
The cookie's value attribute value is simply 1 - that's it.

Return visit

Now, what happens with a second visit, huh? The action shifts back to the gettimes( ) function and its if (index != -1) { ... } statement, whose condition now returns true.
if (index != -1) {
	countbegin = document.cookie.indexOf("=", index) + 1;
	countend = document.cookie.indexOf(";", index);
	if (countend == -1) {
		countend = document.cookie.length; }
	count = document.cookie.substring(countbegin, countend);
	if (count == 1) {
		return (count + " time"); }
	else {
		return (count + " times"); } }
À la other cookie scripts we've discussed - the Script Tip #64 script, the related Script Tips #60-63 script, and the "So, You Want To Set A Cookie, Huh?" script - the above code uses the indexOf( ) method to locate the Counter_Cookie cookie's value in the document.cookie string and the substring( ) method to extract that value, which is given a count identifier. ('Play-by-play' for the analogous code in the "So, You Want To Set A Cookie, Huh?" script's getName( ) function is given at the end of Blog Entry #144.) For a second visit, count is 1, "1 time" is returned to the gettimes( ) function call, and "You have been to my site 1 time before" is printed out for the user.

The doCookie( ) function is called when the page has loaded; index is no longer -1 and the else clause below is operative:
else {
	countbegin = document.cookie.indexOf("=", index) + 1;
	countend = document.cookie.indexOf(";", index);
	if (countend == -1) {
		countend = document.cookie.length; }
	count = eval(document.cookie.substring(countbegin, countend)) + 1;
	document.cookie = cookie_name + "=" + count + "; expires=" + expires; }
The above code writes a new Counter_Cookie cookie with a value again specifying the number of times the user has visited the page - 2 in this case. The current Counter_Cookie cookie value, 1, is located and extracted as in the gettimes( ) function; because that value has a string data type, it is numberified via the eval( ) function (the parseInt( ) function could also be used for this purpose) and then incremented to give the number 2, which is assigned to count. Lastly, cookie_name, =, count, ; expires=, and expires are concatenated to give
Counter_Cookie=2; expires=Wed, 01 Jan 2200 0x:00:00 GMT (x will vary depending on the user's time zone),
which replaces the corresponding Counter_Cookie=1 cookie when it is added to document.cookie.

I trust you can take it from here for subsequent visits.

Tightening up the code, and a demo

I can understand why Joe set up separate functions for writing the Counter_Cookie cookie and getting the number of user visits - 'division of labor' and all that - but you can see for yourself that this leads to some code redundancy; it would be better to utilize a single doCookie( ) function comprising an if (index == -1) { ... } block for first-time visitors followed by an else clause that takes care of return visitors, and I have accordingly merged the doCookie( ) and gettimes( ) functions for the demo below - check the page source for the details.

I myself am not a fan of Web counters. Counting page/site visits is something that should be done behind the scenes, IMO - you shouldn't bother the user with it. But 'different strokes for different folks', I suppose.

We'll revisit the topic of text scrolling in the following entry when we take up "So, You Want A JavaScript Ticker Tape, Huh?", the next Beyond HTML : JavaScript tutorial.


Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)