reptile7's JavaScript blog
Saturday, May 09, 2009
 
Double-Cookied Script
Blog Entry #144

References
(1) The "Netscape Cookies" Appendix (C) of the JavaScript 1.3 Client-Side Reference
(2) Wikipedia's HTTP cookie page
(3-5) We previously discussed JavaScript cookies in Blog Entries #81, #82, and #83.

Ready for another go at a cookie script? Ah, I knew you were game. Today we'll check over HTML Goodies' "So, You Want To Set A Cookie, Huh?" tutorial, in which Joe offers a set-and-retrieve cookie script that he put together by heavily borrowing from the cookie script submitted by Giedrius. The original Giedrius cookie script is here; it's discussed in HTML Goodies' JavaScript Script Tips #60-64 and we went through it in Blog Entry #82. However, Joe does add a noteworthy twist to Giedrius' script: he places the script's 'set' and 'retrieve' parts on different pages so that the script's cookie more closely resembles a shopping cart cookie - this ends up causing problems for the script's demo, as we'll see later.

Set

Joe claims, Any time you set a cookie, you need to gather some information [from the user]: this is not generally true - you can of course set all of a cookie's values yourself if you want to, and most Web sites do just that when they set cookies - but it's certainly true for a shopping cart cookie. Joe's script sets a cookie whose name attribute value is dataCookie; for the cookie's value attribute value, the user is asked to enter a word into a cfd text box in a cf form

<form name="cf" action="">

Enter A Word: <input type="text" name="cfd" size="20" />

<input type="button" value="Set to Cookie" onclick="putCookie( );" />
</form>


and then click a button, triggering the putCookie( ) function in the script's 'set' part:

/* For the tutorial, Joe externalizes the following code (I've tweaked it a bit, as is my wont) here. */
var cookie_name = "dataCookie", YouEntered, index;
function putCookie( ) {
    if (document.cookie != document.cookie) index = document.cookie.indexOf(cookie_name);

    else index = -1;
    if (index == -1) {

        YouEntered = document.cf.cfd.value;

        document.cookie = cookie_name + "=" + YouEntered + "; expires=Monday, 04-Apr-2010 05:00:00 GMT"; } }


Per its identifier, the putCookie( ) function writes a cookie to the user's hard disk. In brief:

(1) The cookie's name value is dataCookie, which is assigned to a cookie_name variable prior to declaring putCookie( ).

(2) The user's text box input, document.cf.cfd.value, will be the cookie's value value, and is assigned to a YouEntered variable.

(3) cookie_name, an = sign, YouEntered, and a ; expires=Monday, 04-Apr-2010 05:00:00 GMT expires attribute string are concatenated to give the cookie, which is added to the browser's cookie collection via assignment to document.cookie (if the user has added the exact same cookie previously, the new cookie 'bumps' the old cookie - a duplicate cookie is not added).

Are you scratching your head at that if (document.cookie != document.cookie) { ... } conditional? Man, I sure was when I first saw it. In attempting to explain this code, Joe says in part:
The first thing we do is check to see if there even is a cookie. The line if (document.cookie != document.cookie) asks if the current cookie is not equal to the data entered by the user.

Writing that question actually saves a step. By asking if something is not equal to something else, I also ask if that something even exists. You see, if it doesn't exist, then it cannot be equal. Get it? I'm basically assuring that the cookie is rewritten each time the function is run.
Actually, if we want to know if there are any cookies associated with the current document (not necessarily set by the current document), then we should test therefor with an

if (document.cookie) { ... }

conditional à la Giedrius' script; to test if the user's input is present in the preexisting document.cookie string, an

if (document.cookie.indexOf(document.cf.cfd.value) != -1) { ... }

conditional would do the trick. The index = document.cookie.indexOf(cookie_name) statement in Joe's conditional would indirectly test if the dataCookie value is present in the document.cookie string: the index variable would return the index of dataCookie's starting d in document.cookie if it were and would return -1 if it weren't. However, the document.cookie != document.cookie condition does not in fact compare the preexisting document.cookie string with the user's input; rather, it compares the document.cookie return with itself, and necessarily returns false. As a result, index is set to -1 regardless of whether document.cookie does or does not contain a dataCookie cookie.

Joe evidently wants to use the index variable to flag first-time visitors (index = -1) vs. return visitors (0index < document.cookie.length-10) as Giedrius' script does. But for a shopping cart cookie, we don't really care if the user is a first-time or return visitor in the sense that the user should be able to overwrite the dataCookie cookie again and again if desired; consequently, we could easily shrink the putCookie( ) function to

function putCookie( ) {
    YouEntered = document.cf.cfd.value;

    document.cookie = cookie_name + "=" + YouEntered + "; expires=Monday, 04-Apr-2010 05:00:00 GMT"; }


without any problems. BTW, contra the tutorial, only the ; expires=Monday, 04-Apr-2010 05:00:00 GMT string in the putCookie( ) document.cookie statement needs to be "on one line"; the statement's other operands, and its operators, could be put on separate lines if for some reason you wanted to do that.

Before moving on:
Simply replacing the document.cookie != document.cookie condition with a document.cookie condition would not allow a return visitor to overwrite the dataCookie cookie; again, we are better off getting rid of the index testing code in this regard.

Fetch

So, we type something into the "Enter A Word" field. Joe correctly points out that a cookie value should not contain white space; it shouldn't contain commas or semicolons either. However, the browsers on my computer - including not only OS X browsers but also Netscape 4.61 and MSIE 4.5 in the SheepShaver environment - allow me to set with Joe's script a cookie value comprising more than one word without having to escape its space characters (with %20s or +s), e.g., I can write a dataCookie=oatmeal raisin cookie, which we'll use for the discussion below.

We click the button and then follow the Click to go to another page link to the tutorial's demo page, whose source contains the script's 'retrieve' part:

var cookie_name = "dataCookie", YouWrote, index, namestart, nameend;
function getName( ) {

    if (document.cookie) {
        index = document.cookie.indexOf(cookie_name);
        if (index != -1) {

            namestart = document.cookie.indexOf("=", index) + 1;

            nameend = document.cookie.indexOf(";", index);
            if (nameend == -1) nameend = document.cookie.length;

            YouWrote = document.cookie.substring(namestart, nameend);

            return YouWrote; } } }


YouWrote = getName( );
if (YouWrote == "dataCookie") YouWrote = "Nothing_Entered";


Joe pads this code out somewhat for the demo page, as follows:

(1) Prior to the getName( ) function, Joe adds a testCookie( ) function that
(a) uses a while loop to search for dataCookie in the document.cookie string; if dataCookie is present, testCookie( ) then
(b) uses indexOf( ) and substring( ) commands to extract the dataCookie value, which is returned and printed out on the second line of the demo page display -
at least that's what's supposed to happen. In practice, the aforementioned while loop contains an uppercase-for-lowercase error

end = start + name.Length; // should be: end = start + name.length;

that prevents testCookie( ) from working/returning properly.

I originally wasn't going to put the testCookie( ) function in front of you (I view it as excess baggage, and it could be written in a lot fewer lines of code anyway), but maybe I should do so - testCookie( ) and its calling expression appear in the div below:

function testCookie(name) {
    var cookieFound = false;
    var start = 0;
    var end = 0;
    var cookieString = document.cookie;
    var i = 0;
    while (i <= cookieString.length) {
        start = i;
        end = start + name.Length;
        if (cookieString.substring(start,end) == name) { 
            cookieFound = true;
            break; } 
        i++; }
    if (cookieFound) {
        start = end + 1;
        end = document.cookie.indexOf(";",start);
        if (end < start) {
            end = document.cookie.length; }
        return document.cookie.substring(start,end); }
    else {
        return "No cookie matching was found"; } }
testcx = testCookie("dataCookie");

(2) In the getName( ) function, the if (index != -1) { ... } block is followed by an

else return "No cookie name found." + Test;

statement in case dataCookie is not found in document.cookie. The Test variable is declared locally in the getName( ) function

var Test = cookie_name;

on the line following the if (document.cookie) declaration and (like cookie_name) evaluates to dataCookie.

(3) The getName( ) function concludes with an

else return "No cookie ID found.";

statement in case there are no cookies associated with the current document.

For a dataCookie=oatmeal raisin cookie, here's what we should see on the demo page:

You Entered oatmeal raisin.
oatmeal raisin
You Entered:
<<...Back

(The display is actually double-spaced, via p elements.)

The display's first three lines are respectively coded by the following source commands:
(1) document.write("You Entered " + YouWrote + "."); /* As shown above, YouWrote is the getName( ) return. */
(2) document.write(testcx); // Similarly, testcx is the testCookie( ) return.
(3) document.write("You Entered: <input type='text' size='30' value='" + YouWrote + "' />");
/* In the preceding command, YouWrote must be surrounded by single quotes; otherwise, you'll only see oatmeal in the text box. */


Excluding the <<...Back link, the demo page display when using Internet Explorer is

You Entered oatmeal raisin.
You Entered:

if the text box value remains unquoted per the demo source. MSIE detects the dataCookie=oatmeal raisin cookie and the demo page does at least return oatmeal raisin for YouWrote. The display's second line 'disappears' because, to make a long story short, the testCookie( ) document.cookie.substring(start, end) command, in which the start and end parameters evaluate to NaN due to the L-for-l error discussed above, returns an empty string.

In brief, here's how getName( ) extracts the oatmeal raisin value from the dataCookie=oatmeal raisin cookie:

(1) First, the index of the beginning o of oatmeal raisin in the document.cookie string is located with
namestart = document.cookie.indexOf("=", index) + 1;
this index is assigned to the variable namestart.

(2) We then locate the index one past that of the final n of oatmeal raisin in document.cookie with either
nameend = document.cookie.indexOf(";", index);
if dataCookie=oatmeal raisin is not the last document.cookie cookie, or
if (nameend == -1) nameend = document.cookie.length;
if dataCookie=oatmeal raisin is the last document.cookie cookie (in this case, the index corresponds to the null character position in a C string); this index is assigned to the variable nameend.

(3) Finally, we return and assign to YouWrote the oatmeal raisin subset of the document.cookie string with
YouWrote = document.cookie.substring(namestart, nameend);.

Curiously, for the most recent version of MSIE that I have on my computer - that would be MSIE 5.2.3 for Mac OS X - no domain=.htmlgoodies.com-type cookies are set when I visit the "So, You Want To Set A Cookie, Huh?" tutorial (or any other HTML Goodies page), and dataCookie=oatmeal raisin is the only cookie in the document.cookie string; in getName( ), index, namestart, and nameend thus have the values 0, 11, and -1, respectively.

FYI: The 'retrieve' script's final
if (YouWrote == "dataCookie") YouWrote = "Nothing_Entered";
conditional only comes into play if the user were to actually enter dataCookie into the cfd field, and not if nothing is entered, as incorrectly stated in the tutorial; in the latter case, the cookie value would not be the name of the cookie but an empty string.

So far, so good - at least when using Internet Explorer. But things go south with other browsers, as we'll see in our next episode.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

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