CONTENTS
One of the challenges of writing applications for the World Wide
Web has been the inability of the Web to maintain state.
That is, after a user sends a request to the server and a Web
page is returned, the server forgets all about the user and the
page she has just downloaded.
If the user clicks on a link, the server doesn't have background
information about what page the user is coming from and, more
importantly, if the user returns to the page at a later date,
there is no information available to the server about the user's
previous actions on the page.
Maintaining state can be important to developing complex interactive
applications. Several sites work around this problem using complex
server-end CGI scripts. However, Navigator 2.0 and 3.0 addresses
the problem with cookies: a method of storing information
locally in the browser and sending it to the server whenever the
appropriate pages are requested by the user.
JavaScript provides the capability to work with client-side state
information stored as cookies.
In addition to cookies, JavaScript offers the navigator
object, which provides information about the version of the browser
a user has and, in the future, will likely include methods to
customize the browser.
The information available in the navigator
object can be useful for a number of purposes, including ensuring
that users are using a version of the browser that supports all
the features of a script. The navigator
object also includes properties for working with file types and
installed plug-ins.
In this chapter, we take a detailed look at using cookies in the
JavaScript applications as well as how to use the navigator
object, including
- What cookies are
- Examples of using cookies
- Cookies and CGI
- Using cookies in JavaScript
- Information provided by the navigator
object
Cookies provide a method to store information at the client side
and have the browser provide that information to the server along
with a page request.
Note |
The term cookies has no special significance. It is just a name in the same way Java is just a name for Sun's object-oriented programming language.
|
In order to understand how the mechanism works, it is important
to have a basic understanding of how servers and clients communicate
on the World Wide Web using the hypertext transfer protocol
(HTTP).
The hypertext transfer protocol is fairly simple. When a user
requests a page, an HTTP request is sent to the server. The request
includes a header that defines several pieces of information,
including the page being requested.
The server returns an HTTP response that also includes a header.
The header contains information about the document being returned,
including its MIME type (such as
text/html for a standard
HTML page or image/gif for
a GIF file).
These headers all contain one or more fields of information in
a basic format:
Field-name: Information
Cookie information is shared between the client browser and a
server using fields in the HTTP headers. The way it works is fairly
simple-in theory.
When the user requests a page for the first time, a cookie (or
more than one cookie) can be stored in the browser by a Set-Cookie
entry in the header of the response from the server. The Set-Cookie
field includes the information to be stored in the cookie along
with several optional pieces of information, including an expiry
date, path, and server information, and if the cookie requires
security.
Then, when the user requests a page in the future, if a matching
cookie is found among all the stored cookies, the browser sends
a Cookie field to the server
in a request header. The header will contain the information stored
in that cookie.
The Set-Cookie and Cookie
fields use a fairly simple syntax to transfer significant information
between the client and server.
Set-Cookie takes the form:
Set-Cookie: name=VALUE; expires=DATE;
path=PATH; domain=DOMAIN; secure
The name=VALUE entry
is the only required piece of information that must be included
in the Set-Cookie field.
This is simply a string of characters defining information to
be stored in the cookie for later transmission back to the server.
The string cannot contain semicolons, commas, or spaces.
All the other entries in the Set-Cookie
field are optional and are outlined in Table 9.1.
Table 9.1. Optional attributes for Set-Cookie.
Name | Description
|
expires=DATE
| Specifies the expiry date of a cookie. After this date the cookie will no longer be stored by the client or sent to the server (DATE takes the form Wdy, DD-Mon-YY HH:MM:SS GMT-dates are only
stored in Greenwich Mean Time). By default, the value of expires is set to the end of the current Navigator session.
|
path=PATH
| Specifies the path portion of URLs for which the cookie is valid. If the URL matches both the path and domain, then the cookie is sent to the server in the request header. (If left unset, the value of path is
the same as the document that set the cookie.)
|
domain=DOMAIN
| Specifies the domain portion of URLs for which the cookie is valid. The default value for this attribute is the domain of the current document setting the cookie.
|
secure
| Specifies that the cookie should only be transmitted over a secure link (i.e. to HTTP servers using the SSL protocol-known as HTTPS servers).
|
By comparison, the Cookie
field in a request header contains only a set of name-value pairs
for the requested URL:
Cookie: name1=VALUE1; name=VALUE2 ...
It is important to realize that multiple Set-Cookie
fields can be sent in a single response header from the server.
Note |
A cookie that has the same path and name as an existing cookie will overwrite the old one-this can be used as a way of erasing cookies-by writing a new one with an expiry date that has already passed.
|
There are some limitations on the use of cookies. Navigator will
store only 300 cookies in total. Within that 300, each cookie
is limited to four kilobytes in length, including all the optional
attributes, and only 20 cookies will be stored for each domain.
When the number of cookies is exceeded, the browser will delete
the least recently used and when the length of a cookie is too
long, the cookie is trimmed to fit.
There are several ways that cookies can be used to enhance interactive
applications.
For instance, there are sites using cookies to implement shopping
carts. That is, a user traverses multiple pages at a site and
selects items he wants to buy. The selections are stored in cookies
until a JavaScript script or CGI script is executed to total up
the purchases.
Other applications that could use cookies include
- Reminder calendars that use cookies to store appointments
and other messages.
- Country tours that users can take during several visits to
a Web site-cookies are used to remember where the user left off.
- Adventure games that use cookies to keep track of pertinent
character data and the current state of the game.
In order for cookies to be useful, it is necessary for the server
to be able to take advantage of the cookie information it receives
and for the server to be able to generate cookie headers if they
are needed.
This is primarily done by using CGI scripts.
For instance, if you want to provide a custom search tool that
would search World Wide Web indexes selected by the user, you
would need to develop a system that follows this basic pattern:
- User calls the site by using an URL that requests a CGI script.
- The script checks whether it is the user's first time at the
site by checking whether there is a Cookie
field in the HTTP request header.
- If there is no cookie, the script sends back a new search
page with all choices unselected and an empty search field.
- If there is a Cookie
field, the script interprets the cookie and returns a page with
all the user's previous choices selected.
- When the user conducts a search, the script returns the search
results along with a Set-Cookie
field in the header to reset the cookie to the newly selected
values that the user used for the search.
This type of application could produce results similar to Figures
9.1 and 9.2.
Figure 9.1 : If the user has never visited the URL, a page with a new form is sent to the user.
Figure 9.2 : On subsequent visits, the user receivers a page in the same state as he or she last left it.
To implement this type of server-side processing for cookies may
require significant increases in the load on a Web server. With
this model, most pages are being built dynamically based on receiving
cookie information in the header.
This is in contrast to typical Web pages, which are static, and
all the server needs to do is send the correct file to the client
without any additional processing.
In JavaScript, however, cookies become available for processing
by the client.
JavaScript makes the cookie
property of the document
object available for processing. The cookie
property exposes all the attributes of cookies for the page to
the script and enables the script to set new cookies. In this
way much, if not all, of the server-end processing that would
be done to take advantage of cookies can now be done by the client
in a JavaScript script.
The cookie property simply
contains a string with the value that would be sent out in a Cookie
field for that page.
As a string, it can be manipulated like any other string literal
or variable using the methods and properties of the string
object.
Note |
In, "Strings, Math, and the History List," we will take a detailed look at the string object and all of its methods and properties.
|
By assigning values to document.cookie,
it is possible to create new cookies. The value of the string
assigned to the cookie property
should be the same as what would be sent by the server in the
Set-Cookie header field.
For instance, if you create two cookies named cookie1
and cookie2 as follows:
document.cookie = "cookie1=First_cookie";
document.cookie = "cookie2=Second_cookie";
then document.write(document.cookie)
would produce output that looks like this:
cookie1=First_cookie; cookie2=Second_cookie
If you want to set optional properties such as the expiry date
or path, you can use a command like:
document.cookie = 'cookie1=First_cookie;
expires=Mon,
01-Jul-95 12:00:00 GMT; path="/"';
This would create a cookie named cookie1
that expires at noon on 1 July 1995 and is valid for all documents
in the default domain because the path is set to the top-level
directory for the domain.
Of course, times are likely to be set using offsets from the current
time. For instance, you may want an expiry date one day or one
year from the current date. You can use the methods of the Date
object to achieve this:
expires = new Date();
expires.setTime (expires.getTime() + 24 * 60 * 60 * 365 * 1000);
document.cookie = "cookie2=Second_cookie; expires="
+ expires.toGMTString();
These commands use the Date
object to set a time one year after today by adding 24¥60¥60¥365¥1000
(the number of milliseconds in one year) to the current date and
time. You can then use expires.toGMTString()
to return the date string in GMT time as required by the cookie.
Note |
setTime(), getTime(), and toGMTString() are methods of the Date() object, which is discussed in more detail later in this chapter.
|
In this example, you are going to use cookies to expand the functionality
of the script you created in Exercise 8.4 in the previous chapter.
In Exercise 8.4, you extended the simple color testing application
to include the capability for the user to select a URL to test
the colors.
In this example you further extend the script so that if a user
has entered a URL, it is stored in a cookie, as are the colors.
The next time the user returns to the page, the URL is recalled,
loaded, and displayed with the stored colors. The expiry date
for a cookie should be 30 days from the current date.
In order to achieve this, you need to do several things. You need
to save the colors and URLs as cookies whenever they are changed.
You also need a function that can decode the cookie when the page
is loaded for the first time.
Listing 9.1 includes these additions.
Listing 9.1. Keeping track of the user's color choices.
<HTML>
<HEAD>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FORM OTHER BROWSERS
var expires = new Date();
expires.setTime (expires.getTime() + 24 * 60 * 60 * 30 * 1000);
var expiryDate = expires.toGMTString();
function display(form) {
parent.output.document.bgColor = form.bg.value;
parent.output.document.fgColor = form.fg.value;
parent.output.document.linkClor = form.link.value;
parent.output.document.alinkColor = form.alink.value;
parent.output.document.vlinkColor = form.vlink.value;
}
function loadPage(url) {
var toLoad = url.value;
if (url.value == "")
toLoad = "sample.htm";
open (toLoad,"output");
}
function newCookie(name,value) {
document.cookie = name + "=" + value + ";
expires=" + expiryDate;
}
function getCookie(name) {
var cookieFound = false;
var start = 0;
var end = 0;
var cookieString = document.cookie;
var i = 0;
// SCAN THE COOKIE FOR name
while (i <= cookieString.length) {
start = i;
end = start + name.length;
if (cookieString.substring(start,end)
== name) {
cookieFound = true;
break;
}
i++;
}
// IS name FOUND?
if (cookieFound) {
start = end + 1;
end = document.cookie.indexOf(";",start);
if (end < start)
end = document.cookie.length;
return document.cookie.substring(start,end);
}
return "";
}
// STOP HIDING SCRIPT -->
</SCRIPT>
</HEAD>
<BODY onLoad="loadPage(document.forms[0].url); display(document.forms[0]);">
<CENTER>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
document.write('<H1>The Color Picker</H1>');
document.write('<FORM METHOD=POST>');
document.write('Enter Colors:<BR>');
var thisCookie = ((document.cookie != "") &&
(document.cookie != null));
var bg = (thisCookie) ? getCookie("bg") : document.bgColor;
var fg = (thisCookie) ? getCookie("fg") : document.fgColor;
var link = (thisCookie) ? getCookie("link") : document.linkColor;
var alink = (thisCookie) ? getCookie("alink") : document.alinkColor;
var vlink = (thisCookie) ? getCookie("vlink") : document.vlinkColor;
var url = (thisCookie) ? getCookie("url") : "sample.htm";
document.write('Background: <INPUT TYPE=text NAME="bg"
VALUE="' + bg + '"
onChange="newCookie(this.name,this.value);">
... ');
document.write('Text: <INPUT TYPE=text NAME="fg"
VALUE="' + fg + '"
onChange="newCookie(this.name,this.value);"><BR>');
document.write('Link: <INPUT TYPE=text NAME="link"
VALUE ="' + link + '"
onChange="newCookie(this.name,this.value);">
...');
document.write('Active Link: <INPUT TYPE=text NAME="alink"
VALUE="' + alink + '"
onChange="newCookie(this.name,this.value);"><BR>');
document.write('Followed Link: <INPUT TYPE="text"
NAME="vlink" VALUE ="' + vlink
+
'" onChange="newCookie(this.name,this.value);"><BR>');
document.write('Test URL: <INPUT TYPE="text" SIZE=40
NAME="url" VALUE="' + url +
'"
onChange="newCookie(this.name,this.value); loadPage(this);"><BR>');
document.write('<INPUT TYPE=button VALUE="TEST"
onClick="display(this.form);">');
document.write('</FORM>');
// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>
</CENTER>
</BODY>
</HTML>
|
In order to use cookies to store the current state for the color tester application, you have to add two new functions (newCookie() and getCookie()) to the header, as well as alter the script that dynamically generates the HTML form in
the upper frame. The other two functions remain unchanged.
|
The newCookie() function
is the simpler of the two new functions. It stores a cookie given
a name and value as arguments. The expiryDate
variable is a global variable created by taking the current date,
adding 30 days, and then converting it to a string in Greenwich
Mean Time using toGMTString():
var expires = new Date();
expires.setTime (expires.getTime() + 24 * 60 * 60 * 30 * 1000);
var expiryDate = expires.toGMTString();
The getCookie() function
is designed to return a particular cookie value. What makes this
somewhat complicated is that document.cookie
contains a string of name-value pairs separated by a semicolon
followed by a space.
In order to find the particular value you want and return it,
you need to do some relatively sophisticated processing on the
document.cookie string.
function getCookie(name) {
var cookieFound = false;
var start = 0;
var end = 0;
var cookieString = document.cookie;
You start by declaring the variables. cookieFound
is a Boolean variable, which you use to keep track of whether
a name-value pair matching the argument has been found. start
and end are used to hold
indexes for the substring()
function and cookieString
holds the value of document.cookie
simply because it is a little easier to read-it's my personal
preference.
var i = 0;
// SCAN THE COOKIE FOR name
while (i <= cookieString.length) {
start = i;
end = start + name.length;
if (cookieString.substring(start,end)
== name) {
cookieFound = true;
break;
}
i++;
}
This loop is fairly simple. You use i
as the counter and simply loop through cookieString
character by character and check the substring
starting at i that is the
length of the name you're looking for. If there is a match, you
set cookieFound to true
and break out of the loop.
// IS name FOUND?
if (cookieFound) {
start = end + 1;
end = document.cookie.indexOf(";",start);
if (end < start)
end = document.cookie.length;
return document.cookie.substring(start,end);
}
If you've found a cookie that matches the name you are looking
for, then you set start to
the value of end + 1-this
means you are starting after the equal sign that follows the name.
Next you use indexOf() to
look for the semicolon that may be ending the cookie (unless it
is the last cookie in the string). You store the value in end.
We will see more of the string.indexOf()
method in when we discuss the
string object in more detail.
The method takes two arguments: indexOf(string,startIndex)
and starts searching for string
from the index startIndex.
The value returned is the index where string
first occurs. If indexOf()
doesn't find the character it is looking for, it returns a value
of zero.
Once you have a value for end,
you check if a semicolon was found. If not, you know the name-value
pair is the last in the list and you can set end
to the last character in cookieString,
which is the value of cookieString.length.
Finally, you return the substring indicated by start
and end.
return "";
}
If you haven't found a cookie, you simply return an empty string.
All of the HTML output is done from a JavaScript script in the
body of the document.
<BODY onLoad="loadPage(document.forms[0].url);
display(document.forms[0]);">
<CENTER>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
document.write('<H1>The Color Picker</H1>');
document.write('<FORM METHOD=POST>');
document.write('Enter Colors:<BR>');
Here you output the title and set up the form.
var thisCookie = ((document.cookie !=
"") && (document.cookie != null));
var bg = (thisCookie) ? getCookie("bg") : document.bgColor;
var fg = (thisCookie) ? getCookie("fg") : document.fgColor;
var link = (thisCookie) ? getCookie("link") : document.linkColor;
var alink = (thisCookie) ? getCookie("alink") : document.alinkColor;
var vlink = (thisCookie) ? getCookie("vlink") : document.vlinkColor;
var url = (thisCookie) ? getCookie("url") : "sample.htm";
The form elements are built dynamically so that the contents of
each field match any existing cookies. If there is no cookie,
then the contents of the color fields will match the defaults
for the browser.
You do this by checking whether the cookie exists and then using
conditional expressions to assign values to several variables,
that will be used later to build the actual form elements. If
cookies exist, you get the values by calling getCookie().
If not, you use the appropriate color properties of the document
object, except in the case of url
which you assign to a default URL.
document.write('Background: <INPUT
TYPE=text NAME="bg" VALUE="' + bg + '"
onChange="newCookie(this.name,this.value);"> ...
')
document.write('Text: <INPUT TYPE=text NAME="fg"
VALUE="' + fg + '"
onChange="newCookie(this.name,this.value);"><BR>');
document.write('Link: <INPUT TYPE=text NAME="link"
VALUE ="' + link + '"
onChange="newCookie(this.name,this.value);"> ...');
document.write('Active Link: <INPUT TYPE=text NAME="alink"
VALUE="' + alink + '"
onChange="newCookie(this.name,this.value);"><BR>');
document.write('Followed Link: <INPUT TYPE="text"
NAME="vlink" VALUE ="' + vlink
+
'" onChange="newCookie(this.name,this.value);"><BR>');
document.write('Test URL: <INPUT TYPE="text" SIZE=40
NAME="url" VALUE="' + url +
'"
onChange="newCookie(this.name,this.value); loadPage(this);"><BR>');
document.write('<INPUT TYPE=button VALUE="TEST"
onClick="display(this.form);">');
Once you have calculated the initial values for the form fields,
you use document.write()
to output the HTML for each field. In each text entry field you
use onChange to store a new
cookie when the user changes the value of the field. The button
calls display() on a click
event.
As I mentioned earlier, the information stored in the name-value
pair of a cookie cannot contain any spaces. This poses something
of a limitation because many applications will need to store complete
phrases or strings containing spaces in cookies.
The solution to this lies in encoding the illegal characters in
a cookie. Netscape suggests using an encoding scheme such as that
used in URL strings.
However, any coding scheme will work. For instance, alternative
characters such as % or +
could be used for spaces and a similar approach could be taken
for other illegal characters, such as semicolons.
Any script that is going to build cookies using white spaces and
other illegal characters, or that is going to read similar cookies,
will need to include methods for dealing with these characters.
JavaScript provides the escape()
and unescape() methods, which
take a string as an argument. escape()
returns the string encoded like an URL and unescape()
translates it back from this encoding.
An easier recipe for cookies.
|
It should be clear now that some type of standardized method for creating new cookies, reading existing cookies, and encoding cookies would make writing scripts much easier.
Just as he wrote the hIdaho Frameset, Bill Dortch has developed a set of freely available functions to perform all these tasks. The functions are available at
http://www.hidaho.com/cookies/cookie.txt.
The source code is reproduced on the CD-ROM:
<script language="javascript">
<!-- begin script
//
// Cookie Functions - Second Helping (21-Jan-96)
// Written by: Bill Dortch, hIdaho Design <bdortch@netw.com>
// The following functions are released to the public domain.
//
// The Second Helping version of the cookie functions dispenses with
// my encode and decode functions, in
favor of JavaScript's new built-in
// escape and unescape functions,
which do more complete encoding, and
// which are probably much faster.
//
// The new version also extends the SetCookie function, though in
// a backward-compatible manner, so if you used the First Helping of
// cookie functions as they were written,
you will not need to change any
// code, unless you want to take advantage of the new capabilities.
//
// The following changes were made to SetCookie:
//
// 1. The expires parameter is now optional - that is, you can omit
// it instead of passing it null to expire the cookie at the end
// of the current session.
//
// 2. An optional path parameter has been added.
//
// 3. An optional domain parameter has been added.
//
// 4. An optional secure parameter has been added.
//
// For information on the significance of these parameters, and
// and on cookies in general, please refer to the official cookie
// spec, at:
//
// http://www.netscape.com/newsref/std/cookie_spec.html
//
//
// "Internal" function to return the decoded value of a cookie
//
function getCookieVal (offset) {
var endstr = document.cookie.indexOf (";", offset);
if (endstr == -1)
endstr = document.cookie.length;
return unescape(document.cookie.substring(offset, endstr));
}
//
// Function to return the value of the cookie specified by "name".
// name - String object containing the cookie name.
// returns - String object containing the cookie value, or null if
// the cookie does not exist.
//
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 to create or update a cookie.
// name - String object containing the cookie name.
// value - String object containing the cookie value. May contain
// any valid string characters.
// [expires] - Date object containing the
expiration data of the cookie. If
// omitted or null, expires the cookie
at the end of the current session.
// [path] - String object indicating the path
for which the cookie is valid.
// If omitted or null, uses the path of the calling document.
// [domain] - String object indicating
the domain for which the cookie is
// valid. If omitted or null,
uses the domain of the calling document.
// [secure] - Boolean (true/false) value
Indicating whether cookie transmission
// requires a secure channel (HTTPS).
//
// The first two parameters are required.
The others, if supplied, must
// be passed in the order listed above.
To omit an unused optional field,
// use null as a place holder.
For example, to call SetCookie using name,
// value and path, you would code:
//
// SetCookie ("myCookieName", "myCookieValue", null, "/");
//
// Note that trailing omitted parameters
do not require a placeholder.
//
// To set a secure cookie for path "/myPath", that expires after the
// current session, you might code:
//
// SetCookie (myCookieVar, cookieValueVar, null,
"/myPath", null, true);
//
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 to delete a cookie.
(Sets expiration date to current date/time)
// name - String object containing the cookie name
//
function DeleteCookie (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();
}
// end script -->
</script>
The source code should be included in the header of any document that includes scripts that work with cookies.
Although Dortch has done a good job of documenting each of the functions in the comments of the source code, we will run through them all in the next few sections.
The getCookieVal() function.
This function is an internal function called by GetCookie(). Given the index of the first character of the value of a name-value pair in a cookie, it returns the value as an unencoded string.
The function uses the unescape method to decode the value.
The GetCookie() function.
The getCookie() function is used to retrieve the value of a particular cookie. It takes the name of the cookie as an argument and returns the value. If the cookie doesn't exist, the function returns a null value.
The SetCookie() function.
This function can be used to create a new cookie or to update an existing cookie. The function requires two arguments and can take several optional arguments:
setCookie(name,value,expires,path,domain,secure)
where expires, path, domain, and secure are optional parameters, and name and value are required. Name, value, path, and domain should be strings. expires
should be passed as a Date object and secure should be a Boolean value.
The order of the arguments is important, so if you want to leave out a particular value in the middle of the order, you should pass the null value as a placeholder.
The DeleteCookie() function.
This function does just what the name suggests: deletes the cookies specified by a name argument. The cookie is deleted by updating it with an expiry date equal to the current date and time.
|
You are now going to use cookies to develop a more sophisticated
application.
Most users of the World Wide Web are well aware that the Web can
be a great source of the latest news. However, finding just the
right news can be a little daunting. The process can take loading
a variety of news providers' Web pages to get all the information
you want.
Using a combination of cookies and frames, you are going to build
an application that provides news from multiple sources in one
browser window.
The concept is simple: The screen is divided into two main sections-the
left side contains a form for manipulating the application, and
the right side contains three frames displaying the news sources
selected by the user.
In the control frame, users should be provided with three drop-down
selection lists to enable them to select the news sources for
each of the three frames on the right. In addition, users should
be able to add news sources to the list, as well as delete sources
from the list.
Cookies are used to store the list of news sources, as well as
the currently selected sources for each frame.
The application is created using scripts in two files: the top-level
frameset called news.htm
(see Listing 9.2) and the main control file that you will call
control.htm (see Listing
9.3). The file wait.htm is
a placeholder that is displayed when the three news source frames
on the right side are first created (see Listing 9.4).
Listing 9.2. The parent frameset (news.htm).
<!-- SOURCE CODE FOR TOP-LEVEL FRAMESET
-->
<HTML>
<HEAD>
<TITLE>Example 9.2</TITLE>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
//
// WE NEED TO IncLUDE THE COOKIE FUncTIONS
//
//
// Cookie Functions - Second Helping (21-Jan-96)
// Written by: Bill Dortch, hIdaho Design
<bdortch@netw.com>
// The following functions are released to the public
domain.
//
// "Internal" function to return the decoded value of
a cookie
//
function getCookieVal (offset) {
var endstr = document.cookie.indexOf (";",
offset);
if (endstr == -1)
endstr = document.cookie.length;
return unescape(document.cookie.substring(offset,
endstr));
}
//
// Function to return the value of the cookie specified
by "name".
//
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 to create or update a cookie.
//
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 to delete a cookie. (Sets expiration date
to current date/time)
//
function DeleteCookie (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();
}
//
// END OF THE COOKIE FUncTIONS. OUR SCRIPT STARTS HERE.
//
function getURL(frame) {
var name = GetCookie(frame);
return GetCookie(name);
}
function initialize() {
if (GetCookie("sites") == null) {
var expiryDate = new Date();
expiryDate.setTime(expiryDate.getTime()
+ (365 * 24 * 60 * 60 * 1000));
SetCookie("sites","CNN,USA-Today,Yahoo",expiryDate,"/");
SetCookie("CNN","http://www.cnn.com/",expiryDate,"/");
SetCookie("USA-Today","http://www.usatoday.com/",expiryDate,"/");
SetCookie("Yahoo","http://www.yahoo.com/headlines/news/",expiryDate,"/");
SetCookie("frameOne","CNN",expiryDate,"/");
SetCookie("frameTwo","USA-Today",expiryDate,"/");
SetCookie("frameThree","Yahoo",expiryDate,"/");
SetCookie("number","3",expiryDate,"/");
}
}
initialize();
var frameOne = getURL("frameOne");
var frameTwo = getURL("frameTwo");
var frameThree = getURL("frameThree");
// STOP HIDING HERE -->
</script>
</HEAD>
<FRAMESET COLS="35%,*" onLoad="parent.frames['frameOne'].location=frameOne;
parent.frames['frameTwo'].location=frameTwo;
parent.frames['frameThree'].location=frameThree;">
<FRAME SRC="control.htm" NAME="control">
<FRAMESET ROWS="33%,33%,*">
<FRAME SRC="wait.htm" NAME="frameOne">
<FRAME SRC="wait.htm" NAME="frameTwo">
<FRAME SRC="wait.htm" NAME="frameThree">
</FRAMESET>
</FRAMESET>
</HTML>
Listing 9.3. The source code for control.htm.
<!-- SOURCE CODE FOR control.htm -->
<HTML>
<HEAD>
<TITLE>Example 9.3</TITLE>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
//
// WE NEED TO IncLUDE THE COOKIE FUncTIONS
//
//
// Cookie Functions - Second Helping (21-Jan-96)
// Written by: Bill Dortch, hIdaho Design
<bdortch@netw.com>
// The following functions are released to the public
domain.
//
// "Internal" function to return the decoded value of
a cookie
//
function getCookieVal (offset) {
var endstr = document.cookie.indexOf (";",
offset);
if (endstr == -1)
endstr = document.cookie.length;
return unescape(document.cookie.substring(offset,
endstr));
}
//
// Function to return the value of the cookie specified
by "name".
//
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 to create or update a cookie.
//
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 to delete a cookie. (Sets expiration date
to current date/time)
//
function DeleteCookie (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();
}
//
// END OF THE COOKIE FUncTIONS. OUR SCRIPT STARTS HERE.
//
function getURL(frame) {
var name = GetCookie(frame);
return GetCookie(name);
}
var expiryDate = new Date();
expiryDate.setTime(expiryDate.getTime() + (365 * 24 * 60 * 60
* 1000));
var number = parseInt(GetCookie("number"));
var siteList = GetCookie("sites");
var sites = new createArray(number);
sites = extractSites(siteList,number);
function createArray(num) {
for (var i=1; i <= num; i++)
this[i] = "";
this.length = num;
}
function extractSites(list,num) {
var results = new createArray(num);
var first = 0;
var last = 0;
for (var i = 1; i <= num; i ++) {
first = (i == 1) ? 0 : last+1;
last = (i == num) ? list.length : list.indexOf(",",first+1);
results[i] = list.substring(first,last);
}
return results;
}
function makeList() {
var result = "";
for (var i = 1; i <= number; i++) {
result += sites[i];
result += (i == number) ? ""
: ",";
}
return result;
}
function getList(frame) {
var result = '<SELECT NAME="' + frame + '"
onChange="loadURL(this);">';
for (var i = 1; i<=number; i++) {
result += '<OPTION';
result += (GetCookie(frame) == sites[i])
? " SELECTED" : "";
result += ">" + sites[i]
+ "\n";
}
result += "</SELECT>";
return result;
}
function addURL(form) {
if ((form.name.value == "") || (form.name.value
== null)) {
returnl
}
var name = form.name.value;
var url = form.url.value;
SetCookie(name,url,expiryDate,"/");
sites[++number] = name;
SetCookie("sites",makeList(),expiryDate,"/");
SetCookie("number",number,expiryDate,"/");
window.open("control.htm","control");
}
function deleteURL(form) {
var name = form.name.value;
var gone = false;
for (var i=1; i<=number; i++) {
if (sites[i] == name) {
gone = true;
number--;
for (var j=i; j<=number;
j++) {
sites[j] = sites[j+1];
}
sites[number+1] = null;
break;
}
}
if (gone) {
SetCookie("number",number,expiryDate,"/");
SetCookie("sites",makeList(),expiryDate,"/");
var today = new Date();
SetCookie(name,GetCookie(name),today,"/");
}
window.open("control.htm","control");
}
function loadURL(field) {
var frame = field.name;
var index = field.selectedIndex;
var name = field.options[index].text;
var url = GetCookie(name);
window.open(url,frame);
SetCookie(frame,name,expiryDate,"/");
}
// Set things up before building forms
var oneList = "";
var twoList = "";
var threeList = "";
oneList = getList("frameOne");
twoList = getList("frameTwo");
threeList = getList("frameThree");
// STOP HIDING HERE -->
</script>
</HEAD>
<BODY>
<H1>The<BR>News<BR>Source</H1>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
document.write("<FORM METHOD=POST>");
document.write("Source One:");
document.write(oneList);
document.write("</FORM>");
document.write("<FORM METHOD=POST>");
document.write("<BR>");
document.write("Source Two:")
document.write(twoList);
document.write("</FORM>");
document.write("<FORM METHOD=POST>");
document.write("<BR>");
document.write("Source Three:");
document.write(threeList);
document.write("</FORM>");
// STOP HIDING -->
</SCRIPT>
<BR>
<FORM METHOD=POST>
Name:
<INPUT TYPE="text" NAME="name">
<BR>
URL:
<INPUT TYPE="text" NAME="url">
<BR>
<INPUT TYPE="button" VALUE="Add URL" onClick="addURL(this.form);">
<BR>
<INPUT TYPE="button" VALUE="Delete URL"
onClick="deleteURL(this.form);">
</FORM>
</BODY>
</HTML>
Listing 9.4. Creating a Wait message.
<!-- SOURCE CODE FOR wait.htm -->
<HTML>
<BODY>
<H1>Please Wait ...</H1>
</BODY>
</HTML>
The results should look like Figures 9.3 and 9.4.
Figure 9.3 : Using cookies to create a useroriented custom news sources Web page.
Figure 9.4 : Users can add a new URL and automatically update selection lists.
|
This program is somewhat complex in that it uses dynamically generated HTML, both in the parent frameset and the main HTML file in the control frame. Both files include Bill Dortch's cookie functions because the scripts in both files need to access the
cookies.
|
In order to understand how the application works, you need to
understand how you are using cookies to store all the information.
You need to keep track of the following information:
- A name and URL for each option in the selection lists
- The last loaded (selected) option for each of the three frames
- A list of all the names of the options
- The number of options currently available
All this information is stored in cookies. You keep an optionName=url
cookie for each option. In addition, you have three cookies of
the form frameName=optionName
for each of the frames. The list of all names is stored in the
form sites=optionName1,optionName2,optionName3,
and so on, where the list is comma-separated and encoded using
Bill Dortch's functions. The number of options is stored in the
cookie number=numberOfOptions.
In the parent frameset, you have written two functions of your
own, as well as building the HTML for the entire frameset using
the document.write() method.
function getURL(frame) {
var name = GetCookie(frame);
return GetCookie(name);
}
The getURL() function accepts
a frame name as a parameter. It first gets the name of the option
for the frame from the appropriate cookie and then gets the URL
for that option name with another call to GetCookie().
The URL is returned.
function initialize() {
if (GetCookie("sites") == null) {
var expiryDate = new Date();
expiryDate.setTime(expiryDate.getTime()
+ (365 * 24 * 60 * 60 * 1000));
SetCookie("sites","CNN,USA-Today,Yahoo",expiryDate,"/");
SetCookie("CNN","http://www.cnn.com/",expiryDate,"/");
SetCookie("USA-Today","http://www.usatoday.com/",expiryDate,"/");
SetCookie("Yahoo","http://www.yahoo.com/headlines/news/",expiryDate,"/");
SetCookie("frameOne","CNN",expiryDate,"/");
SetCookie("frameTwo","USA-Today",expiryDate,"/");
SetCookie("frameThree","Yahoo",expiryDate,"/");
SetCookie("number","3",expiryDate,"/");
}
}
The initialize() function
is the first function called in the script. It simply checks whether
any sites are currently stored as cookies using if
(GetCookies("sites") == null). If there
are no sites stored, the function defines an initial list of three
options and stores all the relevant information in the appropriate
cookies.
After calling initialize()
to ensure you have sites stored in cookies, you use getURL()
to extract the URLs for the three news frames from the cookies.
Finally, the three URLs are loaded into their respective frames
by using an onLoad event
handler in the FRAMESET tag.
This ensures that all the frames are loaded and ready when you
attempt to open the URLs in them. The parent frameset itself divides
the right-hand column into three frames where you load wait.htm
as a placeholder until the various news sources begin to load.
In the left-column frame you load control.htm,
which is the main application:
<FRAMESET COLS="35%,*" onLoad="parent.frames['frameOne'].location=frameOne;
parent.frames['frameTwo'].location=frameTwo;
parent.frames['frameThree'].location=frameThree;">
<FRAME SRC="control.htm" NAME="control">
<FRAMESET ROWS="33%,33%,*">
<FRAME SRC="wait.htm" NAME="frameOne">
<FRAME SRC="wait.htm" NAME="frameTwo">
<FRAME SRC="wait.htm" NAME="frameThree">
</FRAMESET>
</FRAMESET>
Once the frameset is built, you use getURL()
to extract the URLs for the three news frames from the cookies.
Then you use window.open()
to open the appropriate URL in each frame.
The file control.htm (refer
back to Listing 9.3) is where all the interactive work of the
application takes place. The file includes several HTML forms
that provide the three drop-down selection lists-one for each
of the three news frames-and a simple form to add and remove URLs
from the list.
In order to implement all of the functionality you need, control.htm
includes several additional functions in the header of the file.
var expiryDate = new Date();
expiryDate.setTime(expiryDate.getTime() + (365 * 24 * 60 * 60
* 1000));
var number = parseInt(GetCookie("number"));
var siteList = GetCookie("sites");
var sites = new createArray(number);
sites = extractSites(siteList,number);
function createArray(num) {
for (var i=1; i <= num; i++)
this[i] = "";
this.length = num;
}
You start by setting up all the global variables you are going
to need throughout the script. expiryDate
is set to one year after the current date and is used whenever
you create new cookies or update existing ones.
The variable number contains
the number of options in the selection lists and is initially
extracted from the appropriate cookie using GetCookie().
Also, siteList is the string
of option names from the sites
cookie. It is passed to extractSites
to fill up the array sites,
which is used throughout the script to reference option names.
You have used a standard createArray()
type function to build the array sites.
function extractSites(list,num) {
var results = new createArray(num);
var first = 0;
var last = 0;
for (var i = 1; i <= num; i ++) {
first = (i == 1) ? 0 : last+1;
last = (i == num) ? list.length : list.indexOf(",",first+1);
results[i] = list.substring(first,last);
}
return results;
}
The extractSites() function
accepts two arguments: the string of comma-separated option names
and the number of options in the list. It returns an array of
option names.
The real work of the function all takes place in the for
loop. The loop is repeated once for each of the options in the
list. The first step is to figure out the index of the first character
of the next option name in the list. The command first
= (i == 1) ? 0 : last+1; does this by checking whether
the loop counter is still at the start (equal to 1).
If it is, the value of first
is zero; otherwise, it is one character past the last character
of the previous name, which was stored in last.
The index of the last character is calculated in a similar way
using the command:
last = (i == num) ? list.length : list.indexOf(",",first+1);
This command checks whether the counter is at its final value.
If it is, then last should
be set to the last character in the string using list.length.
Otherwise, the indexOf()
method is used to find the next comma in the string.
These two lines are a good example of using conditional expressions
to write what would otherwise be a set of bulkier if-else
statements.
Once first and last
are calculated, then the next entry in the array is set using
list.substring() to extract
the option name from the string.
Finally, the array results
is returned as the result of the function.
function makeList() {
var result = "";
for (var i = 1; i <= number; i++) {
result += sites[i];
result += (i == number) ? ""
: ",";
}
return result;
}
makeList() is used to perform
exactly the opposite function of extractSites().
Given the array of option names stored in the global array sites,
it returns a single string containing a comma-separated list of
options.
This is done, again, in a single for
loop that loops through each entry in the array and adds it to
the result variable using
the += concatenation operator.
The command
result += (i == number) ? ""
: ",";
uses a conditional expression to make sure that a comma is added
only after an entry from the array, if it is not the last entry
in the array.
function getList(frame) {
var result = '<SELECT NAME="' + frame + '"
onChange="loadURL(this);">';
for (var i = 1; i<=number; i++) {
result += '<OPTION';
result += (GetCookie(frame) == sites[i])
? " SELECTED" : "";
result += ">" + sites[i]
+ "\n";
}
result += "</SELECT>";
return result;
}
The getList() function is
used to build the drop-down selection menus for each of the three
frames based on the current cookie settings. The function accepts
a frame name as an argument and returns, as a single string, the
SELECT HTML container, complete
with all options set and the appropriate one preselected.
The function uses the frame
argument to correctly set the NAME
attribute of the SELECT tag.
The onChange event handler
contains the function call to load a new URL when the user chooses
a new option.
The for loop in the function
builds the list of options. It does this by looping through each
option and adding an OPTION
tag to the string. It then uses sites[i]
to add the name to be displayed for each option. The command
result += (GetCookie(frame) == sites[i])
? " SELECTED" : "";
checks if the current option is stored in the cookie for the frame.
If it is, then the SELECTED
attribute is added to the OPTION
tag so that that option will appear selected initially.
function addURL(form) {
if ((form.name.value == "") || (form.name.value
== null)) {
returnl
}
var name = form.name.value;
var url = form.url.value;
SetCookie(name,url,expiryDate,"/");
sites[++number] = name;
SetCookie("sites",makeList(),expiryDate,"/");
SetCookie("number",number,expiryDate,"/");
window.open("control.htm","control");
}
addURL() is invoked when
the user clicks on the Add URL button near the bottom of the frame.
It adds a new entry to the selection lists for each frame.
Given the form object as
an argument, the addURL()
function extracts the name and URL from the fields in the form
and then sets the cookie for that name, as well as updates the
sites array and stores new
sites and number
cookies.
Notice the use of the unary increment operator in sites[++number]
= name; to increment the value of the number
variable before using it as an index to sites.
This effectively adds a new entry to sites
before the next line:
SetCookie("sites",makeList(),expiryDate,"/");
This line updates the sites
cookie by calling makeList()
to build a comma-separated string out of the updated array.
Finally, the function reloads control.htm
into control to rebuild the
selection menus.
function deleteURL(form) {
var name = form.name.value;
var gone = false;
for (var i=1; i<=number; i++) {
if (sites[i] == name) {
gone = true;
number--;
for (var j=i; j<=number;
j++) {
sites[j] = sites[j+1];
}
sites[number+1] = null;
break;
}
}
if (gone) {
SetCookie("number",number,expiryDate,"/");
SetCookie("sites",makeList(),expiryDate,"/");
var today = new Date();
SetCookie(name,GetCookie(name),today,"/");
}
window.open("control.htm","control");
}
In this excerpt, the deleteURL()
function removes an entry from the list of options.
This process is actually a bit more complicated than adding a
new URL to the list because a little bit of work needs to be done
to remove an entry from the middle of the array and then close
up the hole that this creates in the array.
The function uses a for loop
to move through the array. The if
statement checks whether the current entry matches the one you
want to delete.
If there is a match, then the work begins. gone
is set to true so that later
in the function you know a match was found-after all, the user
could incorrectly type the name of the entry to delete. Next,
number is decreased to reflect
the fact that the number of entries in the list will decrease
by one. Another for loop
is used to count from the current index of the entry you are deleting
to the new value of number
(that is, one before the current last entry in the array).
The command sites[j] = sites[j+1];
copies the array entry immediately following the current entry
into the current entry. In this way, you fill in the hole created
by removing an entry.
Finally, after the for loop
finishes, you set the previous last entry to the null
value with sites[number+1] = null;.
Once you finish the for loop,
if you have found an entry to delete, you update the sites
and number cookies just as
you did in addURL() and then
you remove the cookie for the deleted entry by updating it with
an expiry date equal to the current date and time.
function loadURL(field) {
var frame = field.name;
var url = GetURL(frame);
open(url,frame);
SetCookie(frame,name,expiryDate,"/");
}
The loadURL() function is
invoked when the user changes the value of one of the selection
lists. It receives the field
object for the selection list as an argument.
Based on this information, it can extract the frame name, which
is actually the NAME value
of the selection element. Using this information, you can call
getURL() to get the URL for
that frame.
Once you have this information, you can open the URL in the frame
and then update the cookie for that frame to reflect the new selection
by the user.
// Set things up before building forms
var oneList = "";
var twoList = "";
var threeList = "";
oneList = getList("frameOne");
twoList = getList("frameTwo");
threeList = getList("frameThree");
The last thing you do in the header of the document is set up
three variables containing the selection lists for the three different
news source frames by calling getList().
document.write("<FORM METHOD=POST>");
document.write("Source One:");
document.write(oneList);
document.write("</FORM>");
document.write("<FORM METHOD=POST>");
document.write("<BR>");
document.write("Source Two:")
document.write(twoList);
document.write("</FORM>");
document.write("<FORM METHOD=POST>");
document.write("<BR>");
document.write("Source Three:");
document.write(threeList);
document.write("</FORM>");
In the body of the document, you use scripts to build each of
the three drop-down selection lists. Each list is a separate form,
but could just as easily have been a single form.
<FORM METHOD=POST>
Name:
<INPUT TYPE="text" NAME="name">
<BR>
URL:
<INPUT TYPE="text" NAME="url">
<BR>
<INPUT TYPE="button" VALUE="Add URL" onClick="addURL(this.form);">
<BR>
<INPUT TYPE="button" VALUE="Delete URL"
onClick="deleteURL(this.form);">
</FORM>
The last element in the document is the form used to add and delete
entries from the list of options. The form contains two text entry
fields and two buttons that invoke either addURL()
or deleteURL() in their onClick
event handlers.
As mentioned at the beginning of this chapter, the navigator
object makes information about the current version of Navigator
available to scripts.
The properties of the navigator object are outlined in Table 9.2.
Table 9.2. Properties of the navigator
object.
Name | Description
|
appName
| The name of the application in which the page is loaded represented as a string (i.e. "Netscape").
|
AppVersion
| The version information of the current browser as a string in the form "2.0 (Win16; I)" where 2.0 is the version number, Win16 is the platform, and I indicates the international version (as
opposed to U for the domestic version).
|
appcodeName
| The code name of the current browser (i.e. "Mozilla").
|
MimeTypes
| An array of objects reflecting the MIME types supported by the browser. The mimeTypes property is discussed in more detail in Chapter 14.
|
Plugins
| An array of objects reflecting the plug-ins installed in the browser. The plugins property is discussed in more detail in Chapter 14.
|
UserAgent
| The user agent for the current browser as a string in the form "Mozilla/ 2.0 (Win16; I)".
|
In order to understand the significance of this information, it
is important to understand the concept of the user agent. In the
initial communication between the client and the server during
an HTTP request, the browser sends the user agent string to the
server. That information becomes available on the server for a
number of uses, including processing by CGI scripts or for delivering
specific versions of the pages based on the nature of the client
browser.
The user agent information has two parts separated by a slash:
a code name for the browser and the version information for the
browser. This is the information stored in the appcodeName
and appVersion properties.
At first, it may seem as though this information serves little
practical use-but it can be very useful.
For instance, during the beta development of the Navigator 2.0
and 3.0 browser, releases of new versions of the betas were quite
frequent. Each version supported new features of JavaScript and
fixed problems with earlier implementations, which sometimes created
incompatibilities.
Many page authors are now using the properties of the navigator
object to check whether the browsers being used will support the
features used in the script. If not, users are alerted so that
they don't try to run the script and get errors-or even find Netscape,
or their pcs, crashing.
For example, if a page should only be run with Navigator 2.0 beta
6a on any platform and beta 6a is the latest version, the HTML
file should look like this:
<HTML>
<HEAD>
<TITLE>navigator Example</TITLE>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
function checkBrowser() {
if ((navigator.appVersion.substring(0,6) != "2.0b6a")
&&
(navigator.appName
!= "Netscape))
alert("Please use version 2.0b6a of the Netscape Navigator
web
browser with this page.");
}
Rest of script
// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>
</HEAD>
<BODY onLoad="checkBrowser();">
HTML code
</BODY>
</HTML>
Similarly, in the future, if different browsers support JavaScript,
they may offer different features or additional objects, and the
use of the navigator object
can help script authors ensure that their scripts run on the largest
number of browsers while also taking advantage of the unique features
of each.
In this chapter, you learned about an extremely useful feature
of JavaScript: the cookie
property.
Using the cookie property,
scripts can set and read cookies that store state information
in the client browser. Using cookies, you can retain information
between sessions to produce applications that outlive the currently
loaded document and even the current browser session.
Once again, Bill Dortch has provided the JavaScript community
with a set of freely available functions that make setting and
retrieving cookies easier.
In addition to cookies, you took a look at the navigator
object and how it can be used to ensure that users are using the
appropriate browser for your scripts.
In the next chapter, we take a look at a variety of objects and
features of JavaScript that we haven't covered in detail yet,
including the string object,
the Math object, and the
history object.
Command/Extension | Type
| Description |
Set-Cookie
| HTTP header | Sets cookies in the client browser-part of an HTTP response header
|
Cookie
| HTTP header | Returns cookies to the server-part of an HTTP request header
|
expires
| Set-Cookie attribute
| Indicates the expiry date for a cookie (in GMT)
|
path |
Set-Cookie attribute
| Used to set the path for files applicable to a cookie
|
domain
| Set-Cookie attribute
| Used to set the domain for files applicable to a cookie
|
secure
| Set-Cookie attribute
| Specifies that a cookie should be transmitted only on secure links
|
cookie
| JavaScript property | String containing the value of cookies for the current document
|
indexOf()
| JavaScript method | A method of the string object that returns the index of the next occurrence of a substring in a string
|
escape()
| JavaScript method | Encodes a string using URL encoding
|
unescape()
| JavaScript method | Decodes a string encoded using URL encoding
|
appName
| JavaScript property | The name of the browser application as a string
|
appVersion
| JavaScript property | The version number and platform of the browser as a string
|
appcodeName
| JavaScript property | The code name of the browser as a string
|
mimeTypes
| JavaScript property | An array of objects reflecting the MIME types supported by the browser
|
plugins
| JavaScript property | An array of objects reflecting the plug-ins installed in the browser
|
userAgent
| JavaScript property | The user agent string for the browser
|
link()
| JavaScript method | Method of the string object that encloses the string in an <A> HTML tag
|
fontcolor()
| JavaScript method | Method of the string object that sets the HTML font color for the string
|
Q | How can I be sure that other people's applications and scripts won't overwrite the cookies that I have created?
|
A | If you are setting cookies with the specific path of your document, then it is possible for other documents to create cookies with the same name, domain, and path. However, these cookies do not overwrite your cookies.
Instead, document.cookies will contain two entries with the same name, but different values. Only a script in the file in the same original location can overwrite cookies created by that file.
|
Q | Can I rely on cookies to store information vital to my application between sessions?
|
A | Not really. While most users will not delete their browsers or change browsers between sessions-which would mean the loss of cookie information-there are too many variables to be sure that your cookies still will be
present when the user next returns to your page. For instance, cookies could have been added exceeding the limits, and your cookies could be the ones that get deleted. It is generally good to write your scripts in such a way that if the cookies you are
looking for no longer exist, you can perform alternate actions and recreate the cookies.
|
Q | Why does the navigator object have both the appName and appcodeName properties? They seem to be pretty much the same thing in different forms.
|
A | While it's true that generally you can glean the same information from both properties (such as Mozilla for Netscape, and so on), it is not always the case that you will know that the code name for Lynx or the
application name for a less well-known browser. Having both properties, you have the maximum information available to determine the client browser.
|
- A user loads the page http://sample.page/sample/file.html
on 1 January 1996 at noon (GMT), and a script in it contains the
following lines:
document.cookie = "user=joe; expires=Wed,
31-Jan-96
00:00:00 GMT; path=/";
document.cookie = "message=hello";
document.cookie = "food=lasagna; expires=Wed, 31-Jan-96 00:00:00
GMT;
domain=another.site;
path=/anotherpath/";
document.cookie = "color=blue; expires=Tue, 02-Jan-96 12:00:00
GMT;
domain=sample.page;
path=/sample/";
If the user accesses the following pages at the following
times, what will be displayed by the command document.write(document.cookie):
a. http://sample.page/otherfile.html
on 3 January
b. http://sample.page/sample/file.html
on 31 January at noon (GMT)
c. http://another.site/anotherfile.html
on 31 January at 11:59 a.m.
- Extend your script in Listings 9.2 through 9.4 (the news sources
example) so that some sort of error checking takes place. If the
user tries to add an entry with no URL, the user should get an
error. In addition, if the user tries to add more than 10 entries
to the list, prompt the user for the name of an entry to replace
and check the name to make sure it is valid before proceeding.
- Design a page that asks questions of first-time visitors (or
those who haven't visited for more than 30 days). Ask for their
favorite color and their favorite food (from a list of options)
and then customize the Web page to include the specified background
color and a picture of the food indicated.
Every time users come to the page within 30 days of their last
visit, build the specified page without asking for the information.
- The following strings would be displayed:
a. user=joe.
b. There are no cookies-user
expired at midnight, 12 hours earlier.
c. There are no cookies-The only cookie set for this
site is at a lower level path than the user is accessing.
- All the changes are needed in the addURL()
function:
function addURL(form) {
var name = form.name.value;
var url = form.url.value;
if ((name == "") || (name == null) || (url
== "") ||
(url
== null)) {
alert ("Please Enter both a name and URL.");
form.name.focus();
return;
}
if (number == 10) {
var delete = prompt("Cannot enter
more than 10 items.\n
Enter
the name of an entry to replace or enter nothing
to
stop adding" + name + ".","");
if ((delete == "") || (delete == null)) {
form.name.focus();
return;
}
var i=1;
while (i <= number) {
if (sites[i] == delete) {
form.name.value
= delete;
deleteURL(form);
form.name.value
= name;
break;
}
if (i == number) {
delete = prompt("No
such entry to delete. Cannot enter more
than
10 items.\nEnter the name of an entry to replace or
enter
nothing to stop adding" + name + ".","");
i = 0;
}
i++;
}
}
SetCookie(name,url,expiryDate,"/");
sites[++number] = name;
SetCookie("sites",makeList(),expiryDate,"/");
SetCookie("number",number,expiryDate,"/");
window.open("control.htm","control");
}
You can add the following elements to the function
to perform the necessary error checking and to make sure the user
doesn't enter too many elements.
if ((name == "") || (name ==
null) || (url == "") || (url == null)) {
alert ("Please Enter both a name
and URL.");
form.name.focus();
return;
}
This if statement
checks whether either field is the empty string or the null
value. If the result is true,
then an alert message is displayed, focus is returned to the form,
and the function ends with the return
statement.
The next if statement handles
the 10-item limitation on the list. The work that takes place
if there are already 10 entries in the list is a bit more complex
than the previous if statement.
var delete =
prompt("Cannot enter more than 10 items.\n
Enter
the name of an entry to replace or enter nothing
to
stop adding" + name + ".","");
The first step is to ask users what to do because
there are too many entries already. They can either provide an
item to replace or they can cancel the addition. The user's selection
is stored in the delete variable.
if ((delete == "") || (delete
== null)) {
form.name.focus();
return;
}
Once the user responds, the script immediately checks
whether the user has decided to cancel the addition of the new
entry by entering nothing in the prompt dialog box. If the user
is canceling, then focus is returned to the form, and the function
exits with the return statement.
var i=1;
while (i <= number) {
if (sites[i] == delete) {
form.name.value
= delete;
deleteURL(form);
form.name.value
= name;
break;
}
The while loop
is used to check whether the entry the user wants to replace actually
exists. This is done by looping through each entry in the array.
The first step, then, is to check whether the current entry matches
the user's selection. If it does, then the value of delete
is temporarily stored in the name
field of the form, deleteURL()
is called, and then the name
field is returned to its previous value. The break
statement ends the while
loop.
if
(i == number) {
delete = prompt("No
such entry to delete. Cannot enter more
than
10 items.\nEnter the name of an entry to replace or
enter
nothing to stop adding" + name + ".","");
i = 0;
}
The next step in the loop is to see if you have exhausted
all the entries in the list. You check this with the condition
if (i == number). If the
last entry has been reached without a match in the previous if
statement, then the user is again prompted to enter an item to
replace or to enter an empty string. Then the counter is reset
to start checking again.
- The following script achieves the desired effect. The output
looks like Figures 9.5 and 9.6.
Figure 9.5 : The script prompts the first-time visitor for the color and food information.
Figure 9.6 : On subsequent visits, information stored in the cookies automatically formats the page.
<HTML>
<HEAD>
<TITLE>Exercise 9.3</TITLE>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
//
// WE NEED TO IncLUDE THE COOKIE FUncTIONS
//
//
// Cookie Functions - Second Helping (21-Jan-96)
// Written by: Bill Dortch, hIdaho Design
<bdortch@netw.com>
// The following functions are released to the public
domain.
//
// "Internal" function to return the decoded value of
a cookie
//
function getCookieVal (offset) {
var endstr = document.cookie.indexOf (";",
offset);
if (endstr == -1)
endstr = document.cookie.length;
return unescape(document.cookie.substring(offset,
endstr));
}
//
// Function to return the value of the cookie specified
by "name".
//
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 to create or update a cookie.
//
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 to delete a cookie.
Â(Sets expiration date to current date/time)
//
function DeleteCookie (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();
}
//
// END OF THE COOKIE FUncTIONS. OUR SCRIPT STARTS HERE.
//
var expiryDate = new Date();
expiryDate.setTime(expiryDate.getTime() + (30 * 24 * 60 * 60 *
1000));
var food = new createArray(3);
food[1] = "Beets";
food[2] = "Jello-Pudding";
food[3] = "Cockroaches";
function createArray(num) {
this.length = num;
for (var i = 1; i <= num; i++)
this[i] = "";
}
function listArray(stuff) {
var result = "";
for (var i = 1; i <= stuff.length; i++)
result += i + ". " + stuff[i]
+ "/";
return result;
}
var color = "";
var favFood = "";
function initialize() {
if (GetCookie("color") == null) {
color = prompt("Enter your favorite
Netscape color.","A Color");
SetCookie("color",color,expiryDate);
var foodNum = prompt("Food: - "
+ listArray(food),"0");
favFood = food[foodNum];
SetCookie("food",favFood,expiryDate);
} else {
color = GetCookie("color");
SetCookie("color",color,expiryDate);
favFood = GetCookie("food");
SetCookie("food",favFood,expiryDate);
}
}
// STOP HIDING HERE -->
</SCRIPT>
</HEAD>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
initialize();
document.write('<BODY BGCOLOR="' + color + '">');
document.write("<CENTER>")
document.write('<IMG SRC="' + favFood + '.gif">');
// STOP HIDING -->
</SCRIPT>
<H1>Welcome to the favorite food and color page</H1>
</CENTER>
</BODY>
</HTML>
The logic behind this script is rather simple, but it provides
an example of how to rebuild cookies after they expire and how
to keep cookies current when it is relevant to do so.
The createArray() and listArray()
functions should be obvious. listArray()
returns a string containing all the items in an array numerically
listed by their index numbers in the array. This is used to prompt
users for their food choices.
The initialize() function
is where all the work takes place. The function checks for the
existence of the color cookie.
If the cookie doesn't exist, the program prompts users for the
food and color information and stores it in the appropriate cookies,
as well as setting the color
and favFood global variables
for use in the body of the document.
If the cookies exist, the values are loaded into color
and favFood and then the
cookies are updated so that their expiry dates get reset to 30
days into the future.
In the body of the document, a script is used to set the BGCOLOR
attribute of the BODY tag
and the SRC attribute of
the IMG tag based on the
color and favFood
variables