reptile7's JavaScript blog
Friday, May 11, 2012
 
Take Me to the value
Blog Entry #250

In today's post we'll tuck into HTML Goodies' "JavaScript Class: How Can I Set A Cookie Based On A User's Selection On A Form?" tutorial, which was written by Scott Clark. The "Cookie Based On A User's Selection" tutorial is based on a JavaScript Source "Cookie Redirect" script authored by Ronnie Moore in 2000.

The tutorial and the code that it offers flesh out the following scenario:
(1) A first-time visitor to a Web page is presented with a menu of choices that is relevant to the visitor in some way, e.g., a menu of pets the visitor might own, a menu of hobbies the visitor might engage in, etc.
(2) The visitor selects an item from the menu: that selection sets an HTTP cookie whose value attribute value (hereafter value) matches the item's label.
(3) Upon a return visit, the cookie value is extracted and used to redirect the visitor to a value-related page; for example, if the user indicated on the menu that she owns a dog, then she might be sent to a dog.html page about dogs.

The tutorial illustrates the cookie redirect script with a "Please choose your mobile device" example; a demo and a downloadable cookies/ package containing the necessary files for the example are helpfully provided.

The cookie redirect script contains some unremarked-on bloat; more seriously, it suffers from basic design flaws that stem from its use of checkboxes for the menu interface. Our analysis will begin with a look at the script's operation per the author's intention; subsequently we'll address the script's problems and discuss how we might code it differently.

Pre-selection

When the cookies/ directory's cookie.html page loads, a first-time visitor sees the following form:

Please choose your mobile device:
iPhone
iPad
Android
Palm

The cookie.html document incorporates the cookies/ directory's cookieRedirect.js script before the form is rendered.

<script type="text/javascript" src="cookieRedirect.js"></script>

The cookieRedirect.js script first creates an exp Date object and then sets the exp Date 30 days (2,592,000,000 milliseconds) into the future via the Date object's getTime( ) and setTime( ) methods; a stringified exp will later serve as the redirection cookie's expires attribute value (expiration date).

var expDays = 30;
var exp = new Date( );
exp.setTime(exp.getTime( ) + (expDays * 24 * 60 * 60 * 1000));


Next, the cookieRedirect.js GetCookie( ) function is called

var favorite = GetCookie("device");

and passed the string device, which will later serve as the redirection cookie's name attribute value. The GetCookie( ) function, which we'll go through in the I'm back! section below, trawls through cookie.html's document.cookie string, which is not necessarily empty*, in search of a device=someValue cookie; if the search is unsuccessful, as it should be for a first-time visitor, then null is returned to the GetCookie( ) function call and assigned to a favorite variable. If favorite is null, then the script's redirection code, which is wrapped in an if (favorite != null) { ... } 'gatekeeper', is not executed.

*At other pages of the same site/domain, the visitor's browser could have previously picked up one or more cookies that pass domain matching and path matching at the cookie.html document, which is fine as long as none of those cookies is named device.

Selection

Suppose our first-time visitor checks the iPhone checkbox.

<input type="checkbox" name="iPhone" onclick="SetCookie('device', this.name, exp);">iPhone

Clicking the iPhone checkbox calls the cookieRedirect.js SetCookie( ) function and passes thereto device, this.name (iPhone), and exp. Here is the SetCookie( ) function in all its glory:

function SetCookie(name, value) {
    var argv = SetCookie.arguments;
    var argc = SetCookie.arguments.length;
    var expires = (argc > 2) ? argv[2] : null;
    var path = (argc > 3) ? argv[3] : null;
    var domain = (argc > 4) ? argv[4] : null;
    var secure = (argc > 5) ? argv[5] : false;
    document.cookie = name + "=" + escape(value) +
    ((expires == null) ? "" : ("; expires=" + expires.toGMTString( ))) +
    ((path == null) ? "" : ("; path=" + path)) +
    ((domain == null) ? "" : ("; domain=" + domain)) +
    ((secure == true) ? "; secure" : ""); }


The SetCookie( ) function's final statement, a five-line-spanning assignment to document.cookie, sets the following cookie:

device=iPhone; expires=exp.toGMTString( )

The device argument, renamed name, is assigned to the cookie's name. The this.name/iPhone argument, renamed value, is escape( )d and assigned to the cookie's value.

The var expires = (argc > 2) ? argv[2] : null; line gives the exp argument (the third-in-source-order argument of the SetCookie( ) arguments[ ] collection) an expires identifier; the cookie's expiration date is set to the expires toGMTString( ) return by the ((expires == null) ? "" : ("; expires=" + expires.toGMTString( ))) part of the document.cookie assignment.

If we were supplying additional arguments for the cookie's path, domain, and secure attributes, then the SetCookie( ) function would set those attributes for the cookie, but we're not doing that.

I'm back!

Suppose our first-time visitor gets from a referring page a chips=ahoy cookie that applies to cookie.html so that cookie.html's document.cookie return is
chips=ahoy; device=iPhone
after the iPhone selection.

The visitor leaves the cookie.html page and returns the next day. As before, the cookieRedirect.js script is brought into the cookie.html document, a 30-days-into-the-future exp Date object is created, and the GetCookie( ) function is called and passed device.

function GetCookie(name) {
    var arg = name + "=";
    var alen = arg.length;
    var clen = document.cookie.length;
    var i = 0;
    while (i < clen) {
        var j = i + alen;
        if (document.cookie.substring(i, j) == arg) return getCookieVal(j);
        i = document.cookie.indexOf(" ", i) + 1;
        if (i == 0) break; }
    return null; }


The GetCookie( ) function begins by
(a) giving the pre-value part of the iPhone cookie (device=) an arg identifier,
(b) giving the length of arg (7) an alen identifier,
(c) assigning document.cookie.length (25) to a clen variable, and
(d) initializing a name-indexing i variable to 0.

Subsequently a while loop hunts for arg in the document.cookie string. We know that the value of the device cookie, if present, will begin alen characters after the starting d of device; a j variable is used to index the post-arg part of the cookie. The loop is set up such that its (i < clen) condition will never return false; for our example, the loop runs for two iterations.

In the first loop iteration:
j is set to 7.
document.cookie.substring(i, j) returns chips=a, which is not equal to arg.
i = document.cookie.indexOf(" ", i) + 1; pushes i to 12.

(If the device cookie were not present, then document.cookie.indexOf(" ", i) would return -1 and the loop would be terminated by the if (i == 0) break; statement.)

In the second loop iteration:
j is pushed to 19.
• The (document.cookie.substring(i, j) == arg) condition returns true. The cookieRedirect.js getCookieVal( ) function is called and passed the j index; the preceding return statement will cause the browser to exit the loop/GetCookie( ) function.

function getCookieVal(offset) {
    var endstr = document.cookie.indexOf(";", offset);
    if (endstr == -1) endstr = document.cookie.length;
    return unescape(document.cookie.substring(offset, endstr)); }


We know where the iPhone value begins; the getCookieVal( ) function determines where the value ends and extracts the value. The getCookieVal( ) function first looks for a post-j/offset semicolon in the document.cookie string; there isn't one, meaning that the device cookie is the last document.cookie cookie, and therefore the starting index for the post-value part of the cookie, variabilized as endstr, is set to document.cookie.length. A document.cookie.substring(offset, endstr) operation returns iPhone, which is unescape( )d and is returned initially to the getCookieVal( ) function call and then to the GetCookie( ) function call and is finally assigned to favorite.

With a non-null favorite in hand, we move to the cookieRedirect.js redirection code:

if (favorite != null) {
    switch (favorite) {
        case "iPhone" : url = "iphone.html"; break;
        case "iPad" : url = "ipad.html"; break;
        case "Android" : url = "android.html"; break;
        case "Palm" : url = "palm.html"; break; }
    window.location.href = url; }


The favorite value matches the iPhone label of the first case clause of the above switch statement; as a result,
(a) an iphone.html URL for a page designed for an iPhone is assigned to a url variable,
(b) a break statement terminates the execution of the switch statement, and
(c) control passes to the window.location.href = url; statement, which takes the visitor to iphone.html.

Just about every part of the "Cookie Based On A User's Selection" code leaves room for improvement - we'll clean this baby up in our next installment.

Comments: Post a Comment

<< Home

Powered by Blogger

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