Copyright ©1996, Que Corporation. All rights reserved. No part of this book may be used or reproduced in any form or by any means, or stored in a database or retrieval system without prior written permission of the publisher except in the case of brief quotations embodied in critical articles and reviews. Making copies of any part of this book for any purpose other than your own personal use is a violation of United States copyright laws. For information, address Que Corporation, 201 West 103rd Street, Indianapolis, IN 46290 or at support@mcp .com.

Notice: This material is excerpted from JavaScript By Example, ISBN: 0-7897-0813-2. The electronic version of this material has not been through the final proof reading stage that the book goes through before being published in printed form. Some errors may exist here that are corrected before the book is published. This material is provided "as is" without any warranty of any kind.

Chapter 16 - Cookies

Cookies were originally used only by server-side scripts, but through JavaScript they're now accessible from the client side. The term cookie applies to the objects used on the Web to store information on the client's system. All this information is kept in a single file—cookies.txt—with one cookie per line. Cookies are transported between the server and the client in the HTTP headers (the packets that the server and clients use to identify themselves). Because not all browsers support cookies yet, it's a good idea to check out the following Web page from time to time, to see who does and who doesn't support them:

As you can see in figure 16.1, the browser has stored three cookies so far. One of the cookies is from Microsoft's home page, and the other two are from Netscape.

Figure 16.1

Example of the information that can be stored in cookies.txt.

What are cookies used for? Well, let's use the MSN home page as a starting point. The cookie stores ID information that's passed to the server at http://www.msn.com when I contact the site. A CGI script on the server searches a server-side file to determine my setup. It remembers what stock quotes I want to see, what search sites I want listed, and what news services I want to see.

The Netscape cookies store registration information for the browser. Many of the search utilities use cookies to store your search preferences. Two notable examples of such sites are InfoSeek and ftpsearch.com. Look at your cookies.txt file, and then visit either of these two sites and set a particular search preference.

In Navigator, you must close the browser for the cookie information to actually be written to the file. Until the browser is shut down, any new or changed cookies are kept in memory.

Now thanks to JavaScript, you too can set cookies from your pages. Cookies can be used in a multitude of ways, but the most important use is to store data across multiple pages. If index1.hmtl has a script that captures a user's first name, wouldn't you like to use that information later on, like perhaps on index2.html? Well, you know that functions and variables are lost if you change pages or reload the original. Now you just set a cookie and change the pages! The new page can read the cookie information from the client's side and pass it to a variable in the new script.

Particulars

Each cookie has six elements, at most: name, value, expires, domain, and secure. These elements store the scope of the cookie's reach and the actual data stored.

The name Element

Each cookie must have a name. Without a name, how can you address it? The name is a string of characters, excluding the semicolon, comma, and space. If you must use one of these characters, some form of encoding must be used. Cookies are named just like you name variables; myCookie, lastTimeVisited, and card5 are all valid cookie names. For more information on variable names, check out Chapter 5, "Programming Basics."

The value Element

Just as every cookie must have a name, it also must have a value. The value is the information that's actually stored in the cookie, and is a string made of any combination of characters (21, Wednesday, and Navigator2.0 are all valid values).

The expires Element

Can a cookie live forever? That depends on what you set the expiration date to. All computers have a problem with dates after the year 1999—a hardware limitation that you must view as your own also. Cookies without a set expiration date expire at the end of the user's session. The cookie is alive until the user closes his or her browser.

Just because a cookie has expired doesn't mean it will be removed from the cookie text file. It may live there until its space is required to store another cookie. It just won't be sent to a requesting server.

An expiration date string looks like this:

Wdy, DD-Mon-YY HH:MM:SS GMT

Specifying an expiration date for a cookie is optional.

The domain Element

It wouldn't do for a cookie created by my site to affect a script on your site, so each cookie must have a domain entry. For top-level domains such as .com, .edu, or .mil, the domain name must have at least two periods (.) in it. For lower-level domains such as .ga, .uk, or .ca, the domain must have three periods. The domain is automatically set to the basic domain of your site's location. If you had pages at http://www.dscga.com/~user, then by default any cookie you set will be valid for the dscga.com domain. If you want your cookies to apply only to visits on the www3.dscga.com server, you have to specify that when the cookie is set.

Only sites within a particular domain can set cookies for that domain.

The path Element

A cookie can be particular to only a certain level of a site. If you have a Web site that caters to registered and unregistered clients, your cookie information may apply only to users who can enter your "registered" pages.

The path is optional and, if missing, is assumed to be the path of the page that set the cookie.

The secure Flag

The secure flag is a Boolean value (either true or false). By default, it's considered to be false. If it's set to true, the cookie will be presented only to a server your browser considers to be secure.

Limitations

There's a limit to the number of cookies a site can set for a single client. Only 20 cookies can be set for each server or domain. If you have two servers—www.dscga.com and www3.dscga.com—and fail to specify the domain, the cookie defaults to dscga.com. If you specify the exact server address, however, each site can set 20 cookies—not 20 combined.

Keep in mind that if you have your site on a popular server such as aol.com, it's entirely possible that other subscribers to that service may have already set 20 cookies from that server's domain (through visiting all the other pages on that server). The only alternative is to register your own domain, thus giving yourself 20 cookies.

Not only are you limited by the number of cookies that can be set by each server, but each client is only required to store a maximum of 300 cookies. If a client has 300 cookies and a site sets a new cookie, one of the previously existing cookies will be removed.

Each individual cookie has a limit of its own. The cookie can't exceed 4K (4,096 bytes) in size, including the name and the other information combined.

JavaScript and Cookies

Now that you understand the basics, let's get down to business. Various JavaScript functions have been created to set, read, and delete cookies. Because of the simple nature of the functions, many of them look quite similar. The cookies in this chapter are patterned after the public domain versions authored by Bill Dortch. In keeping with the cookie theme, I have given the functions quite unique names.

Baking a Cookie

The very first task that you must perform is to actually set—or, as I like to say, bake—the cookie. Figure 16.2 shows how the BakeCookie() function (in listing 16.1) allows for the use of all the possible arguments.

Figure 16.2

The recipe on how to properly bake a cookie.

Listing 16.1

function BakeCookie(name, value) {

var argv = BakeCookie.arguments;

var argc = BakeCookie.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" : "");

}

BakeCookie() requires only the cookie name and value, but you can set the other four arguments, if needed, without changing the function's design. The optional arguments must be used in the proper order. If an argument is skipped, an empty placeholder must be set. For example, if you want to create a secure cookie but don't want to set expires, path, or domain, you call BakeCookie() as follows:

BakeCookie(“MyNewCookie”, “My Value”, “”, “”, “”, true);

Reading a Cookie

A cookie doesn't do much good if you can't recall the information. So to gain any "nourishment" from the baked cookie, you must "eat" it. When a cookie request is received by the client, it searches its cookies.txt file for a match. It first matches the name. If the name is found and a second cookie exists, it then tries to match the path.

The function returns the value of the cookie if a match has been made. If the cookie isn't present on the client or the paths don't match, the function returns a NULL value. Listing 16.2 shows how this is done in code.

Listing 16.2

function EatCookie(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 EatCookieVal (j);

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

if (i == 0) break;

}

return null;

}

Unless you want to parse the cookie value manually, it's recommended that you use the EatCookieVal() function (in listing 16.3), which complements the EatCookie() function. EatCookieVal() breaks the cookies.txt file into "bite-sized" pieces, returning the correct cookie out of the "batch."

Listing 16.3

function EatCookieVal(offset){

var endstr=document.cookie.indexOf(";",offset);

if (endstr == -1)

endstr = document.cookie.length;

return unescape(document.cookie.substring(offset,endstr));

}

Deleting a Cookie

Deleting a cookie is quite simple—set its expiration date to NULL (effectively deleting the expiration date). When the user's session is completed, the cookie is expired (cookies with no expiration date are deleted when the browser is closed). Listing 16.4 shows how to delete a cookie.

Listing 16.4

function TossCookie(name) {

var exp = new Date();

exp.setTime (exp.getTime() - 1); // This cookie is history

var cval = GetCookie (name);

document.cookie = name + "=" + cval + "; expires=" + exp.toGMTString();

}

Uses of Cookies

The ability to store information about a specific user that relates to an individual Web page makes cookies very powerful things. Some of the possible uses for cookies include the following:

Of these examples, the following sections examine the first two.

How Many Times Have I Been Here?

In this first example, shown in listing 16.5, I'll track the total number of visits a user has made to your page. This is accomplished by using a cookie to store the value between sessions.

Listing 16.5

<html>

<head>

<script language="Javascript">

<!-- hide from non-JavaScript browsers

function getCookieVal (offset) {

var endstr = document.cookie.indexOf (";", offset);

if (endstr == -1)

endstr = document.cookie.length;

return unescape(document.cookie.substring(offset, endstr));

}

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;

}

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" : "");

}

function DeleteCookie(name) {

var exp = new Date();

FixCookieDate (exp); // Correct for Mac bug

exp.setTime (exp.getTime() - 1); // This cookie is history

var cval = GetCookie (name);

if (cval != null)

document.cookie = name + "=" + cval + "; expires=" + exp.toGMTString();

}

var expdate = new Date();

var num_visits;

expdate.setTime(expdate.getTime() + (5*24*60*60*1000));

if (!(num_visits = GetCookie("num_visits")))

num_visits = 0;

num_visits++;

SetCookie("num_visits",num_visits,expdate);

document.write("Thank you for visiting us.<br>");

document.write("You have loaded this page <font size=5>"+num_visits+"</font> times.<br>");

// end hide -->

</script>

</html>

Notice the lines

if (!(num_visits = GetCookie("num_visits")))

num_visits = 0;

If you just tried to pull a cookie value but the cookie didn't exist, the interpreter would return a num_visits is undefined error. You can circumvent this problem be using the NOT (!) operator. Figure 16.3 illustrates how cookies can work on a Web page.

Figure 16.3

This shows a viewer the total number of visits to that page.

Has This Page Changed Since my Last Visit?

Although the preceding cookie can be useful, it may appear as nothing more then a cheap gimmick to your visitors. Not many Web surfers really care how many times they have hit a page. On the other hand, a dedicated Web surfer does like to know when a certain page has changed. Although it's perhaps not as efficient as a Web spider or robot might be, the example in listing 16.6 uses a cookie to determine whether a page has changed since the last visit.

Listing 16.6

<html>

<head>

</head>

<script language="JavaScript">

<!-- hide from non-JavaScript browsers

function getCookieVal (offset) {

var endstr = document.cookie.indexOf (";", offset);

if (endstr == -1)

endstr = document.cookie.length;

return unescape(document.cookie.substring(offset, endstr));

}

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;

}

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" : "");

}

function DeleteCookie(name) {

var exp = new Date();

FixCookieDate (exp); // Correct for Mac bug

exp.setTime (exp.getTime() - 1); // This cookie is history

var cval = GetCookie (name);

if (cval != null)

document.cookie = name + "=" + cval + "; expires=" + exp.toGMTString();

}

var cookie_date=new Date(document.lastModified);

var expdate = new Date();

expdate.setTime(expdate.getTime()+(5*24*60*60*1000));

document.write("This page last updated on: "+document.lastModified);

document.write("<br>");

if (!(cookie_date == GetCookie("cookie_date"))){

SetCookie("cookie_date",cookie_date,expdate);

document.write("<font color='Red'>This page has changed since your last visit!</font><br>");

}

// end hide -->

</script>

</html>

Just as the preceding example checked for the existence of the cookie, the same thing is done here. But rather than use an integer value, I used a date value in the cookie. The test is as simple as comparing the document.lastModified value, located in the cookie, to the current document.lastModified. If it matches, the page hasn't been modified; if it's different, the cookie's value is reset, and the visitor is alerted. Figure 16.4 shows how this code works.

Figure 16.4

Here, the viewer sees how many days it's been since the page was last changed.

Review Questions

The answers to the review questions are in Appendix D.

1. How are cookies transported?

2. What are the only two required attributes of a cookie?

3. Which system reads the cookie—the server or the client?

4. Where is the cookie stored?

5. What are the minimum number of cookies a client should be ready to receive?

6. Can cookie names have spaces in them?

7. Can a page loaded from the HTTP server at www.gatech.com set a cookie with a DOMAIN attribute valid for only www.psu.com?

Exercises

8. Create a cookie that stores the name of the state you live in. Have the cookie expire one week from today.

9. Now delete your new cookie, using a function call (of course).

10. Write a cookie with the following attributes:

11. When finished setting the cookie to your local cookies.txt file, visit http://www.lobosoft.com/que to test your cookie!

12. Advanced: Is it possible to have a cookie that represents an array of information? If you believe it's possible, demonstrate it.