reptile7's JavaScript blog
Saturday, May 26, 2012
 
Take Me to the value, Part 3
Blog Entry #252

We continue today our renovation of the cookie redirect script of HTML Goodies' "How Can I Set A Cookie Based On A User's Selection On A Form?" tutorial. In the previous post, we revamped the script's cookieRedirect.js code. Now, how 'bout them cookie.html checkboxes?

Checkbox more is less

Unlike a set of radio buttons or a non-multiple selection list, a set of checkboxes allows the user to make more than one input. Suppose our first-time cookie.html visitor owns an iPhone, an iPad, a separate smartphone running the Android OS, and an old-school Palm PDA, and accordingly checks all four "Please choose your mobile device" checkboxes. What happens?

The Saving Cookies section of the "Netscape Cookies" Appendix of the JavaScript 1.3 Client-Side Reference states:
Saving a cookie with the same PATH and NAME values as an existing cookie overwrites the existing cookie. Saving a cookie with the same PATH value but a different NAME value adds an additional cookie.
Consequently, if the visitor checks the checkboxes from top to bottom, then a single device=palm cookie will be written to cookie.html's document.cookie string. Initially checking the iPhone checkbox first sets a device=iphone cookie, which is sequentially overwritten by device=ipad, device=android, and device=palm cookies* as the visitor checks the following checkboxes. When the visitor returns to cookie.html, s/he will be redirected to palm.html and only to palm.html.

*Although not explicitly set, the path attribute for all of these cookies has the same value, more specifically, it defaults to the absolute path portion of the URI of the current frame or document in each case.

Now, we could give different names to the checkboxes - say, device0, device1, device2, and device3 - and thereby set a separate cookie for each checkbox. Upon a return visit the four cookie values could be extracted and converted to redirection URLs, which in turn could be plugged into a series of window.open( ) commands that would open the redirection pages in new windows.

function GetCookie(cookie_name) {
    var deviceURLs = new Array( );
    var aCookie = document.cookie.split("; ");
    for (var i = 0; i < aCookie.length; i++) {
        var aCrumb = aCookie[i].split("=");
        if (cookie_name + i == aCrumb[0]) deviceURLs[i] = aCrumb[1] + ".html"; }
    return deviceURLs; }
var favorite = GetCookie("device");
for (var i = 0; i < favorite.length; i++) window.open(favorite[i], "window" + i);


The above GetCookie( ) function was adapted from its namesake in the second example on Microsoft's cookie property page; it uses the split( ) method of the String object to fragment cookie.html's document.cookie string into its constituent names and values. This code works fine on my desktop iMac once browser pop-up blocking has been disabled, but I have no idea how it plays out with a mobile device. Still, even in a best-case scenario, would you want to burden the visitor with four new pages at once? I don't think so.

It gets worse. Unlike a selected radio button or selection list option, a selected checkbox can be deselected by clicking it. Deselecting a device checkbox calls the SetCookie( ) function and sets a new device=value cookie just like selecting it does. Suppose the visitor (a) checks the iPhone checkbox, (b) checks the iPad checkbox, and (c) unchecks the iPad checkbox: at the end of this interaction a device=ipad cookie will be added to cookie.html's document.cookie string, and the visitor will be redirected to ipad.html (and not to iphone.html) upon a return visit. Not what the doctor ordered, is it?

I'd say it's high time we cashed in those checkboxes for some radio buttons or a selection list, wouldn't you?

Creating a corresponding radio button menu is no more difficult than switching the type="checkbox" attributes to type="radio":

<input type="radio" name="device" value="iphone" onclick="SetCookie(this.name, this.value);">iPhone<br>
<input type="radio" name="device" value="ipad" onclick="SetCookie(this.name, this.value);">iPad<br>
...


If none of the inputs of a radio input set is given a checked attribute, then the entire set is rendered unchecked with all of the GUI browsers on my computer; the HTML 4.01 Specification's radio buttons section is not quite clear as to whether this is legit or not, although the W3C does say, Authors should ensure that in each set of radio buttons that one is initially 'on'. However, some browsers may in this situation check the first-in-source-order iPhone radio button per section 8.1.2.4 of the "Hypertext Markup Language - 2.0" RFC 1866 specification (Lynx does this, e.g.): suppose for a moment that we are dealing with such a browser and that a

document.getElementById("formID").device[0].checked = false;

JavaScript statement does not clear the iPhone button. If the visitor chooses to not interact with the radio button menu, then we shouldn't be setting a device=iphone cookie. But what if the visitor is using an iPhone and wants to be redirected to iphone.html? To set a device=iphone cookie, the visitor will have to click a non-iPhone radio button and then click the iPhone radio button - not good, needless to say. Here is one simple way to sort out these issues:

(1) Attach the "Please choose your mobile device" form legend to a checked radio button; give the button an onclick attribute that can call SetCookie( ) and set a device= cookie whose value is an empty string.

<input type="radio" name="device" value="" onclick="SetCookie(this.name, this.value);" checked>Please choose your mobile device<br>

(2) Append to the cookieRedirect.js redirection code a suitable else if (favorite === "") clause that responds to the device= cookie.

if (favorite) window.location = favorite + ".html";
else if (favorite === "") window.alert("You did not choose a mobile device during your previous visit.");


Recall that null is assigned to favorite for a first-time visit. In a logical context, the empty string and null are both convertible to false but can be distinguished via the === operator.

Reformulating the checkbox menu as a selection list is also pretty straightforward. The preceding considerations for a radio button menu likewise apply to a selection list.

<select name="device" size="5" onchange="SetCookie(this.name, this.value);">
<option value="" selected>Please select your mobile device</option>
<option value="iphone">iPhone</option>
<option value="ipad">iPad</option>
...


Demo

The demo below combines the previous post's JavaScript with a selection list. Selecting a mobile device and clicking the button will within the iframe redirect you to the appropriate redirection document (there's not much to these documents, but then again, this is just a demo).



In the following entry we'll check over the next Beyond HTML : JavaScript sector tutorial, "How To Use JavaScript To Auto-Submit Dropdowns, With No Submit Button".

Friday, May 18, 2012
 
Take Me to the value, Part 2
Blog Entry #251

In today's post we will put forward an alternative coding for the cookie redirect script of HTML Goodies' "How Can I Set A Cookie Based On A User's Selection On A Form?" tutorial. We stick with the script's checkbox interface in the Original overhaul section; we'll go after the checkboxes in the subsequent Checkbox more is less section.

Original overhaul

Setting the cookie

Recall that each cookie.html checkbox input has an onclick attribute that feeds device, this.name, and exp to the cookieRedirect.js SetCookie( ) function if the checkbox is clicked.

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

The SetCookie( ) arguments are respectively used to set the name, value, and expires attributes of the redirection cookie. If desired, the SetCookie( ) call can be equipped with additional arguments for setting the path, domain, and secure attributes of the redirection cookie, e.g.:

onclick="SetCookie('device', this.name, exp, '/', '.earthlink.net', true);"

My preferred approach to parameterizing SetCookie( ) is to
(a) set all of the checkbox names to device and
(b) give each checkbox a value attribute set to the appropriate iPhone|iPad|Android|Palm string and then
(c) pass this.name and this.value to SetCookie( ).
An element should not be passing expressions to a function if those expressions are wholly unrelated to the element; to the extent that you make use of them, the redirection cookie's expires/path/domain/secure settings should be kept out of the onclick assignment and hard-coded in the SetCookie( ) function body.

Regarding the redirection cookie's expiration date:

• The exp Date object is defined (created and postdated) globally and thus over and over for first-time/return visits; moving the exp definition to the SetCookie( ) function wouldn't guarantee that it'd be executed only once (depending on how the visitor interacts with the checkboxes) although I think we're better off putting it there than deploying it as top-level code.

• Via the Date object's getDate( ) and setDate( ) methods, we can postdate exp by a day-based calculation vis-à-vis a millisecond-based calculation. Quoting Microsoft:
dateObj.setDate(numDate);

If the value of numDate is greater than the number of days in the month stored in the Date object or is a negative number, the date is set to a date equal to numDate minus the number of days in the stored month. For example, if the stored date is January 5, 1996, and setDate(32) is called, the date changes to February 1, 1996. Negative numbers have a similar behavior.
• The toGMTString( ) method of the Date object is deprecated; you should use the toUTCString( ) method instead.

Putting it all together gives us:

function SetCookie(name, value) {
    var exp = new Date( );
    exp.setDate(exp.getDate( ) + 30);
    document.cookie = name + "=" + value + "; expires=" + exp.toUTCString( )
    /* + "; path=somePath; domain=someDomain; secure"; */ }
...
<input type="checkbox" name="device" value="Android" onclick="SetCookie(this.name, this.value);">Android


Other notes:

(1) In the original SetCookie( ) function the value argument is subjected to an escape( ) operation, which might be needed if the visitor were inputting the value; however, you the Webmaster are the one setting that value, and as long as you don't put any 'offending characters' (spaces, commas, semicolons, non-ASCII characters) in the value, then the escape( ) treatment is unnecessary. BTW, the escape( ) function is deprecated.

(2) The use of arguments as a property of a Function object as appears in the original SetCookie( ) function (e.g., var argv = SetCookie.arguments;) is also deprecated, but as shown above the arguments code - with or without a preceding SetCookie reference, and including the argv[i] ?: conditional stuff - is easily chucked once the SetCookie( ) parameterization has been brought under control.

Getting the cookie

The cookieRedirect.js script sports discrete functions for locating the redirection cookie's name (GetCookie( )) and for extracting the redirection cookie's value (getCookieVal( )). Does it seem to you, as it does to me, that this is taking the modularity/division-of-labor thing a bit too far? We have previously worked with value-extracting cookie scripts, e.g., the HTML Goodies JavaScript Script Tips #60-63 Script: these scripts extract a cookie value à la the getCookieVal( ) function; however, they determine the (j/offset) starting index of the value via a straightforward

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

statement and not via a separate function/while loop, and there's no reason to not do this in the cookieRedirect.js script and thereby cut out the GetCookie( ) function.

function getCookieVal(cookieName) {
    var nameIndex = document.cookie.indexOf(cookieName);
    if (nameIndex != -1) {
        var valueIndex = document.cookie.indexOf("=", nameIndex) + 1;
        var endstr = document.cookie.indexOf(";", valueIndex);
        if (endstr == -1) endstr = document.cookie.length;
        return document.cookie.substring(valueIndex, endstr); }
    else return null; }
var favorite = getCookieVal("device");


The original getCookieVal( ) return is subjected to an unescape( ) operation, which is unnecessary if the SetCookie( ) value didn't need to be escape( )d in the first place (vide supra). Like the escape( ) function, the unescape( ) function is deprecated.

Redirecting the visitor

Is it really necessary to run through a switch statement to map a cookie value onto its redirection URL? Nope, not at all. If we were to all-lowercase the iphone/ipad/android/palm checkbox names ⇒ values, then an

if (favorite) window.location = favorite + ".html";

statement* that (a) appends an .html extension to favorite and
(b) assigns the resulting string to window.location
is all we would need to redirect the visitor - couldn't be simpler, eh?

*The original statement's condition tests if favorite is not equal to null, which is overkill given that non-empty strings convert to true in a logical context whereas null itself converts to false in a logical context.

Delete it

Sandwiched between the SetCookie( ) function and the var favorite = GetCookie("device"); statement in the cookieRedirect.js script is a DeleteCookie( ) function:

function DeleteCookie(name) {
    var exp = new Date( );
    exp.setTime(exp.getTime( ) - 1);
    var cval = GetCookie(name);
    document.cookie = name + "=" + cval + "; expires=" + exp.toGMTString( ); }


The DeleteCookie( ) function overwrites the original device=value cookie with a new device=value cookie whose expiration date is set one millisecond into the past, effectively deleting the original device=value cookie. The DeleteCookie( ) function is never called and can itself be deleted; conversely, there is something to be said for appending a button to the checkbox menu

<button type="button" onclick="DeleteCookie('device', this.form);">Clear the cookie and reset the form</button>

and an arguments[1].reset( ); command to the DeleteCookie( ) function body so that the visitor can start over, if desired.

Checkbox more is less

Let's do this next time.

Friday, May 11, 2012
 
Take Me to the value
Blog Entry #250

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

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

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

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

Pre-selection

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

Please choose your mobile device:
iPhone
iPad
Android
Palm

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

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

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

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


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

var favorite = GetCookie("device");

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

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

Selection

Suppose our first-time visitor checks the iPhone checkbox.

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

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

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


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

device=iPhone; expires=exp.toGMTString( )

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

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

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

I'm back!

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

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

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


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

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

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

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

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

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


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

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

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


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

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

Wednesday, May 02, 2012
 
Calendar Sprawl III
Blog Entry #249

In today's post we'll wrap up our look at the Calendar snippet of HTML Goodies' "Top 10 JavaScript Snippets for Common Tasks" tutorial by taking care of some outstanding issues that we didn't get to in the last couple of entries.

The changeBg( ) function

You may have noticed that the span.c1, span.c2, and span.c3 style rule sets begin with a cursor:hand; declaration. With IE, Chrome, and Safari, moving the mouse cursor over a calendar date changes the cursor into a little hand

The hand cursor
(go here for a page of downloadable cursor image links)

cueing the user that, à la a link, something will happen if the date is clicked: that something is a highlighting of the date by giving it a yellow background color via the if clause of the snippet's changeBg( ) function

function changeBg(id) {
    if (eval(id).style.backgroundColor != "yellow") { eval(id).style.backgroundColor = "yellow"; }
    else { eval(id).style.backgroundColor = "#ffffff"; } }


which is triggered by the onclick='changeBg(this.id);' attribute that each tables[2] date span

text += "<td align='center'><span id='sp" + aa + "' onclick='changeBg(this.id);'>1</span></td>";

is equipped with. Clicking the highlighted date removes the highlight (sets the date's background color to white) via the function's else clause.

There's no need for the changeBg( ) function to reach for the eval( ) function or even the getElementById( ) method in order to access a given date span and its child style object; if we were to feed this.style (vis-à-vis this.id) to the changeBg( ) function, then the function can be recast as:

function changeBg(spanStyle) {
    spanStyle.backgroundColor = spanStyle.backgroundColor != "yellow" ? "yellow" : "white"; }


The hand value of the CSS cursor property is not standard and is not supported by the Mozilla browsers nor by Opera; the cursor: declaration can be legitimized and cross-browserized by switching the hand value to pointer. Even without a cursor:pointer;, however, the onclick='changeBg(this.id);' event handler is operative for the Mozilla browsers and Opera, i.e., the calendar dates can still be un/highlighted as described above, although the user is unlikely to intuit that the dates are actionable.

We noted two entries ago that IE is the only major browser that will apply the CSS width and height properties to inline spans. With IE, the uniform width of the tables[2] columns is effectively set by the width:30; declaration of the various span. style rule sets; in this case clicking a calendar date highlights the entire date cell. (OK, there's a 1px-thick td padding belt that isn't yellowized, but close enough, eh?)

IE highlight

With non-IE browsers, the varying widths of the tables[2] columns are effectively set by the Sun-to-Sat day headings of the first row; in this case clicking a calendar date highlights a content-hugging span whose width is less than that of the relevant day heading.

Non-IE highlight

Doesn't look as nice, does it? (FYI: Switching the day/date font family from Arial to Courier or another monospace font equalizes the column widths but still gives a doesn't-span-the-entire-cell highlight.) Fortunately, there are two simple ways to produce a cell-spanning highlight for non-IE browsers:

(1) Add a display:inline-block; declaration to the span. styles.

.label, .c1, .c2, .c3 { display: inline-block; }

The width:30; declaration (and also the height:16; and text-align:center; declarations) will now apply to the tables[2] day/date spans.

(2) Subtract the span element markup and directly apply the width and other style properties to the tables[2] cells.

.label, .c1, .c2, .c3 { width: 30px; height: 16px; background: white; font: bold 13px Arial, sans-serif; }
.c1, .c2, .c3 { cursor: pointer; text-align: center; }
.label, .c1 { color: black; }
.c2 { color: red; }
.c3 { color: #b0b0b0; }
...
for (ii = 0; ii <= 6; ii++) { text += "<th class='label'>" + arrD[ii] + "</th>"; }
...
for (ii = 0; ii <= 6; ii++) { text += "<td id='sp" + aa + "' onclick='changeBg(this.style);'></td>"; aa += 1; }


29 February

The snippet's maxDays( ) function contains the following statement for determining the last date in non-leap year and leap year Februaries:

if (mm == 1) {
    if (yyyy/4 - parseInt(yyyy/4) != 0) { mDay = 28; }
    else { mDay = 29; } }


The (yyyy/4 - parseInt(yyyy/4) != 0) if condition tests if the yyyy year value is not a multiple of 4: the condition returns true if the yyyy/4 division gives a floating-point number. The statement can be written more simply via the % arithmetic operator and by taking into account that 0 converts to false in a logical context:

if (mm == 1) mDay = yyyy % 4 ? 28 : 29;

In the tutorial comment thread, leap points out that the maxDays( ) leap year code does not catch all non-leap years:
If you're going to include a leap day calculation, get the formula correct:
mDay = (year mod 4 != 0 or year mod 100 == 0) ? 28 : 29;
The code below allows Feb 29 1900 which is incorrect.
if (mm == 1) { if (yyyy/4 - parseInt(yyyy/4) != 0) { mDay = 28; } else { mDay = 29; } }
leap is correct that 1900 was not a leap year; however, leap's proposed (year mod 4 != 0 or year mod 100 == 0) condition for flagging a non-leap year would misidentify years that are a multiple of 400 (e.g., 1600, 2000) as non-leap years when they are in fact leap years. Here's the statement we really want:

if (mm == 1) mDay = (yyyy % 4) || (!(yyyy % 100) && (yyyy % 400)) ? 28 : 29;

If (a) the yyyy year value is not a multiple of 4, OR
(b) the yyyy year value is a multiple of 100 AND is not a multiple of 400 (e.g., 1800, 2100),
then yyyy is a non-leap year; otherwise it's a leap year. See Wikipedia's "Leap year" entry for more on this.

Place it

At the end of the Calendar section, Scott tells the reader to place the writeCalendar( ) function trigger in the body of your document ... where you want the calendar to show up. This sort of vague instruction is OK for the Date Display and IP Address snippets as these snippets output #PCDATA text, which can indeed be plopped just about anywhere on a Web page (or written to the browser window's title/status bars); in contrast, the Calendar snippet gives us an actual structure to deal with.

As noted at the outset of our Calendar analysis, the calendar code's outermost element is the calForm form: the form element is a block-level element whose rendered width spans the width of the viewport. If we throw out the calForm form, then the tables[0] table becomes the outermost element: the table element has a shrink-to-fit rendered width but is also a block-level element. So if you're content with the calendar occupying its own block on the page, then you're good to go - no changes to the calendar's default rendering are needed. N.B. The body element has a (%block;|SCRIPT)+ +(INS|DEL) content model, so you can validly deploy the <script type="text/javascript">writeCalendar( );</script> script element as a child of the body element.

If for whatever reason you would like the calendar to have an inline rendering, then the easiest route to that end is to hold on to the calForm form and give it a display:inline-block; style. Flanking text will appear at the baseline of the calendar; giving the inline-block form a vertical-align:middle; style will lift the text to the vertical center of the calendar.

Alternatively, you might want to override* the calendar's default rendering in the opposite sense, i.e., perhaps you would rather put the second script element in a floated or absolutely positioned div container in order to separate the calendar from the rest of the page's content - that's what I would probably do.

*Actually, floats and absolutely positioned elements themselves establish new block formatting contexts.

A final comment on design

I'm not entirely comfortable with the Calendar snippet's non-separation of structure and behavior, but I can see why Alexander wrote it that way: after all, the calendar is not meant to be a major part of the page but rather a small, decorative widget. Moreover, I'm not happy with the snippet's extensive use of document.write( ) commands (all those HTML end-tags that need to be escaped). The JavaScript Source page on which I found the Calendar snippet says that the snippet was written in 2005; this being the case, it is striking that there are no DOM method calls - e.g., getElementById( ), createElement( ), appendChild( ) - in the snippet.

Nevertheless, it does make sense to use JavaScript to assemble some parts of the calender, in particular the tables[2] table and also the selMonth and selYear selection lists.

for (ii = 0; ii <= 11; ii++) {
    monthOption = document.createElement("option");
    monthOption.value = ii;
    monthOption.text = arrM[ii];
    document.getElementById("select1").appendChild(monthOption);
    if (ii == mm) document.getElementById("select1").options[ii].selected = true; }
document.getElementById("select1").onchange = changeCal;
...
<select id="select1" name="selMonth"></select>


IMO the rest of the calendar is better written out as normal HTML, but to each his own.

Working through scripts like the Calendar snippet (warts and all) is definitely the best way to learn HTML/CSS/JavaScript - good job, Alexander!

In the following entry we'll move on to the next Beyond HTML : JavaScript sector tutorial, "How Can I Set A Cookie Based On A User's Selection On A Form?".


Powered by Blogger

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