reptile7's JavaScript blog
Monday, April 23, 2012
 
Approaching iCal
Blog Entry #248

We return now to our discussion of the changeCal( ) function of the Calendar snippet of HTML Goodies' "Top 10 JavaScript Snippets for Common Tasks" tutorial. At this point in our journey, here's what we've got on the page:

The writeCalendar( ) function output

In this post we'll replace those 1s with a display suitable for the current month/year, April 2012 at this writing. More specifically as regards the tables[2] table:
(1) We'll put a week's worth of grayed-out dates for March 2012 in the second row.
(2) We'll run the 30 days of April 2012 from the first cell of the third row to the second cell of the seventh row in a normal manner, and highlight the current date while we're at it.
(3) We'll finish out the seventh row with five grayed-out dates for May 2012.

The April 2012 calendar display

Our first step is to respectively get the current month's getMonth( ) index and the current year from the selMonth and selYear selection lists that were created in the writeCalendar( ) function.

var currM = parseInt(document.calForm.selMonth.value);
var currY = parseInt(document.calForm.selYear.value);


As for all other control types, the value property of the select object (first implemented by Microsoft, now standard) has a string data type. Use of the parseInt( ) function in the preceding statements is in fact not necessary as the JavaScript engine will carry out string-to-number type conversion on the fly for the code to come, but there's no harm in leaving the parseInt( ) calls in place.

The first date of the current month/year - 1 April 2012, which falls on a Sunday - will serve as a base for setting the dates of the tables[2] table, so let's go get that date:

var mmyyyy = new Date( );
mmyyyy.setFullYear(currY);
mmyyyy.setMonth(currM);
mmyyyy.setDate(1);
var day1 = mmyyyy.getDay( );
if (day1 == 0) { day1 = 7; }


The setFullYear( ) method of the Date object can optionally take arguments specifying a month and a date

mmyyyy.setFullYear(currY, currM, 1);

and thus the above setMonth( ) and setDate( ) commands are unnecessary.

As detailed below, the day1 value - initially 0, set to 7 by the if (day1 == 0) day1 = 7; statement - is used to delimit the March/April/May zones of the display. The day1 value also determines the number of March days that lead up to 1 April; commenting out the if statement will remove the March zone, push the April zone up a row, and give a 1 May to 12 May zone in the last two rows of the tables[2] table.

The changeCal( ) function next creates an arrN array

var arrN = new Array(41); // That should be 42, Alexander

of date numbers (a) that will be respectively loaded into the date spans (the span element children of the td cells of the lower six rows) of the tables[2] table and (b) whose arrN indexes will map onto the ids of those spans. The arrN array can be divided into a March part, an April part, and a May part; the March part is populated by:

var prevM;
if (currM != 0) { prevM = currM - 1; }
else { prevM = 11; }
for (ii = 0; ii < day1; ii++) { arrN[ii] = maxDays(prevM, currY) - day1 + ii + 1; }


An if...else statement assigns the getMonth( ) index of the previous month - that would be 2 for March - to a prevM variable. The statement can be written more concisely via the ?: conditional operator:

var prevM = currM ? currM - 1 : 11;

Subsequently a for loop calls the snippet's maxDays( ) function

function maxDays(mm, yyyy) {
    var mDay;
    if ((mm == 3) || (mm == 5) || (mm == 8) || (mm == 10)) { mDay = 30; }
    else {
        mDay = 31;
        if (mm == 1) {
            if (yyyy/4 - parseInt(yyyy/4) != 0) { mDay = 28; }
            else { mDay = 29; } } }
    return mDay; }


and passes thereto the prevM index and the currY year in order to get the mDay date number for the last day of the prevM month, 31 for March. The mDay return is adjusted (- day1 + 1) to give the date number that belongs in the first cell of the second row, 25: let's call this number row2Sun. The row2Sun value is incremented by the loop to generate the remaining March date numbers for the second row; the 25-to-31 numbers are respectively assigned to the first seven 'boxes' of the arrN array, as though we had coded arrN = [25, 26, 27, 28, 29, 30, 31];.

Each loop iteration runs the maxDays( ) function and determines the row2Sun date number; these operations can and should be 'factored out':

var row2Sun = maxDays(prevM, currY) - day1 + 1;
for (ii = 0; ii < day1; ii++) arrN[ii] = row2Sun + ii;


The leap year part of the maxDays( ) function is flawed - we'll get it sorted out later. For now let's move to the arrN array's April part, which is populated by:

var aa = 1;
for (ii = day1; ii <= day1 + maxDays(currM, currY) - 1; ii++) {
    arrN[ii] = aa;
    aa += 1; }


The 1-to-30 date numbers of April are sequentially represented by an aa variable; these numbers are respectively loaded into the next thirty boxes of the arrN array (arrN[7] through arrN[36]) by the above loop. The loop's upper boundary, ii = 36, is determined by calling the maxDays( ) function with currM and currY inputs and adjusting (+ day1 - 1) the mDay = 30 return; these operations should again be carried out prior to the loop and not iteratively, e.g., by a var endofcurrM = day1 + maxDays(currM, currY) - 1; statement.

Lastly, the May part of the arrN array is populated by:

aa = 1;
for (ii = day1 + maxDays(currM, currY); ii <= 41; ii++) {
    arrN[ii] = aa;
    aa += 1; }


aa is reset to 1 for the 1-to-5 May date numbers, which are respectively loaded into the arrN[37]-to-arrN[41] boxes of the arrN array. Using the above endofcurrM variable, the May loop's initialExpression can and should be reformulated as ii = endofcurrM + 1. (The initialExpression is not executed iteratively but if we've already done the day1 + maxDays(currM, currY) calculation then there's no reason to do it again.)

With the arrN array now complete, the changeCal( ) function then gives a white background color to all of the tables[2] date spans:

for (ii = 0; ii <= 41; ii++) { eval("sp" + ii).style.backgroundColor = "#ffffff"; }

At first glance this statement may seem unnecessary as the span.c1, span.c2, and span.c3 rule sets in the snippet's style block will impart a white background color to these spans, but it does have a purpose, namely, it clears any highlights effected by the changeBg( ) function (which we'll cover in the next post) as well as the current date highlight if the user changes the calendar month or year. Note the use of the eval( ) function to convert the "sp" + ii strings to id-value object references; actually, Microsoft itself would recommend that we alternatively access the date spans via the getElementById( ) method, i.e.:

for (ii = 0; ii <= 41; ii++) { document.getElementById("sp" + ii).style.backgroundColor = "#ffffff"; }

The remainder of the changeCal( ) function consists of a for loop that
(a) loads the arrN date numbers into the tables[2] date spans,
(b) indirectly applies various styles to the tables[2] date spans, and
(c) highlights the current date.
The loop uses a revolving dCount variable to flag the Sun and Sat columns in order to redden the April dates in those columns; dCount's values run in a 0-to-6 cycle and map onto the 0-to-6 returns of the Date object's getDay( ) method.

Just before the loop, dCount is initialized to 0; the loop then kicks off with an if block that handles the March and May dates of the display:

var dCount = 0;
for (ii = 0; ii <= 41; ii++) {
    if (((ii < 7) && (arrN[ii] > 20)) || ((ii > 27) && (arrN[ii] < 20))) {
        eval("sp" + ii).innerHTML = arrN[ii];
        eval("sp" + ii).className = "c3"; }


The previous/current month boundary and the current/next month boundary are each set via a combination of arrN index and value; these combinations can be determined by considering extreme cases involving a non-leap year February.

• We know that the tables[2] table will contain at least one and as many as seven dates for the previous month via the day1 variable. Suppose that the current month is a March whose first day falls on a Sunday. If the preceding month is a non-leap year February, then the row2Sun value will be 22; for all other cases - for all other month boundaries, be it a leap year or not - the row2Sun value will be higher. It follows that a given arrN value will belong to the previous month if the value's arrN index is 6 or lower AND the value itself is 22 or higher.

• Suppose that the current month is a non-leap year February whose first day falls on a Monday. The 28 days of that February will run from the second cell of the second row to the first cell of the sixth row and will be followed by 13 March dates; for all other cases there will be fewer next month dates (the minimum is four, for a current month that begins on a Sunday and has 31 days) and those dates will begin at a later position in the table. It follows that a given arrN value will belong to the next month if the value's arrN index is 29 or higher AND the value itself is 13 or lower.

In sum, the precise previous/current month boundary is
ii <= 6 && arrN[ii] >= 22
and the precise current/next month boundary is
ii >= 29 && arrN[ii] <= 13.
The above if condition unnecessarily provides a bit of margin for error but is serviceable.

Via the ii counter/index and the innerHTML property, the March and May date numbers are written to their corresponding tables[2] date spans; the className of those spans is then set to c3 in order to gray out the March/May date numbers via the span.c3 style rule set:

span.c3 { cursor: hand; color: #b0b0b0; width: 30; height: 16; text-align: center; margin-top: 0; background: #fff; font: bold 12px Arial; }

The if block is followed by an else clause that handles the April dates of the display.

else {
    eval("sp" + ii).innerHTML = arrN[ii];
    if ((dCount == 0) || (dCount == 6)) { eval("sp" + ii).className = "c2"; }
    else { eval("sp" + ii).className = "c1"; }
    if ((arrN[ii] == dd) && (mm == currM) && (yyyy == currY)) { eval("sp" + ii).style.backgroundColor = "#90ee90"; } }


The April date numbers are loaded into their corresponding tables[2] date spans à la the March/May date numbers. If dCount is 0 or 6, i.e., if arrN[ii] is a Sunday or Saturday date number, then the number is reddened via the span.c2 style rule set:

span.c2 { cursor: hand; color: red; width: 30; height: 16; text-align: center; margin-top: 0; background: #fff; font: bold 13px Arial; }

Otherwise the date numbers are rendered in a customary black color via the span.c1 style rule set:

span.c1 { cursor: hand; color: black; width: 30; height: 16; text-align: center; margin-top: 0; background: #fff; font: bold 13px Arial; }
/* Prior to going through the code I didn't notice that the April date numbers have a slightly larger font size (13px) than do the March/May date numbers (12px) - I myself would equalize them. */

The dCount if...else statement can be condensed via the ?: operator:
eval("sp" + ii).className = dCount == 0 || dCount == 6 ? "c2" : "c1";

A concluding if statement imparts a #90ee90 (light green) highlight to the date span for the current (mm dd, yyyy) date.

At the bottom of the loop, dCount is incremented or reset as necessary:

dCount += 1;
if (dCount > 6) { dCount = 0; }
/* Alternatively: dCount = dCount == 6 ? 0 : dCount + 1; */
} }
</script>


A new month/year

As noted in the previous post, the selMonth and selYear selection lists are bound to the changeCal( ) function via an onchange='changeCal( );' attribute. Changing the calendar's month or year re-calls the changeCal( ) function and we go through the whole shebang all over again: we go get the currM month index and the currY year from the selection lists, we create a new mmyyyy Date object set to the first date of the chosen month/year, we repopulate the arrN array via mmyyyy's day1 value, etc.

In the following entry we'll discuss the snippet's changeBg( ) function, clear up the leap year conditional in the maxDays( ) function, and address the placement of the calendar.

Friday, April 13, 2012
 
Calendar Coding Girl or Boy
Blog Entry #247

In today's post we will take up the Calendar snippet of HTML Goodies' "Top 10 JavaScript Snippets for Common Tasks" tutorial. The tutorial specifies no authorship nor a source for the Calendar snippet (or for any of the other snippets, for that matter), but a Google search led me to a JavaScript Source page that credits the snippet to Alexander Babichev.

Do you buy a 16-month calendar every December?


The Calendar snippet codes a 60-month calendar. Via two selection lists, the calendar provides access to all twelve months of the current year, the next two years, and the two preceding years, and the year range can be expanded if desired. The calendar is self-renewing: without making any changes, it will work 5, 10, 20 years down the line. And it is easily placed in the future or past; it can be used to create a 1941-1945 calendar, for example.

The calendar code's use of id attribute values as object references implies that the code was originally written for Internet Explorer; however, we recently noted in Blog Entry #244 that this practice is now legit for all of the major browsers. The snippet runs smoothly with the various OS X GUI browsers on my computer excepting IE 5.2.3, and a very minor change allows it to work with IE 5.2.3 as well.

Overview

As you might guess, the calendar has a tabular layout.
• The calendar is framed by a two-row outer table.
• The outer table's first row contains a table housing the month/year selection lists.
• The outer table's second row contains a table comprising a 7-by-7 grid of cells that hold the calendar's day/date data.
None of these tables has an identifier, so let's respectively call them tables[0], tables[1], and tables[2], as though we were getting them with a var tables = document.getElementsByTagName("table"); command.

Unlike most block-level elements, a table element has a shrink-to-fit width (by default, unless you set it otherwise). The width of the calendar is effectively set by the tables[2] table; the tables[1] table is given a width='100%' attribute so that its width is equal to the tables[2] table width.

The calendar's outermost element is actually a form element, whose only role is to provide a name='calForm' attribute for accessing the month/year selection lists via document.calForm.selectName expressions. The calForm form can be thrown out given that we can get the selection lists via getElementById( ) commands (or a getElementsByTagName("select") command if you prefer).

The snippet begins with a style element that applies various styles to the tables[2] day/date data. The style block is followed by a script element comprising four functions:

(1) A writeCalendar( ) function assembles the calendar markup as one long text string, which is written to the page via a document.write(text); command; there is no separation of structure and behavior in the snippet.

(2) A changeCal( ) function populates the lower six rows of the tables[2] table with dates for the current month or for the user's chosen month plus some grayed-out dates for the preceding and following months à la a normal calendar. (The tables[2] day heading row is filled by the writeCalendar( ) function.)

(3) The changeCal( ) function calls on a maxDays( ) function that determines the number of days in the current/chosen month and in the preceding month; this information is used to divide the lower six rows of the tables[2] table into zones for the current/chosen month, the preceding month, and the following month.

(4) A changeBg( ) function allows the user to highlight (give a yellow background color to) the dates in the lower six rows of the tables[2] table by clicking on them.

The snippet concludes with a second script element containing a writeCalendar( ) function call, which sets the snippet in motion.

<script type="text/javascript">writeCalendar( );</script>

Write it

The lines below get us to the first cell of the one-row tables[1] table, which holds the month selection list:

function writeCalendar( ) {
    ...
    var text = ""; /* This line can be deleted if we declare text on the next line. */
    text = "<form name='calForm'>";
    text += "<table border='1'>";
    text += "<tr><td>";
    text += "<table width='100%'><tr>";
    text += "<td align='left'>"; /* The align setting is unnecessary. */


The following code creates the month selection list:

var arrM = new Array("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December");
var now = new Date
var mm = now.getMonth( );
text += "<select name='selMonth' onchange='changeCal( );'>";
for (ii = 0; ii <= 11; ii++) {
    if (ii == mm) { text += "<option value='" + ii + "' selected>" + arrM[ii] + "</option>"; }
    else { text += "<option value='" + ii + "'>" + arrM[ii] + "</option>"; } }
text += "</select>";


• The arrM array's month elements map onto the 0-to-11 returns of the Date object's getMonth( ) method.
• The now Date object's constructor syntax is not standard (Date should be followed by parentheses) but it throws no errors with the browsers on my computer.
• The selection list is given a selMonth name and is bound to the changeCal( ) function via an onchange attribute.
• The first-in-source-order option reads January and has a value of 0; the next option reads February and has a value of 1; and so on. The current (mm) month is preselected via the for loop's if clause.

An adjoining tables[1] cell

text += "</td>";
text += "<td align='right'>";


holds the year selection list, which is created by the following code:

var yyyy = now.getFullYear( );
var arrY = new Array( );
for (ii = 0; ii <= 4; ii++) { arrY[ii] = yyyy - 2 + ii; }
text += "<select name='selYear' onchange='changeCal( );'>";
for (ii = 0; ii <= 4; ii++) {
    if (ii == 2) { text += "<option value='" + arrY[ii] + "' selected>" + arrY[ii] + "</option>"; }
    else { text += "<option value='" + arrY[ii] + "'>" + arrY[ii] + "</option>"; } }
text += "</select>";


• The arrY array holds five years centered around the current (yyyy) year and in chronological order, as though we had coded var arrY = [2010, 2011, 2012, 2013, 2014];.
• The selection list is given a selYear name and is bound to the changeCal( ) function via an onchange attribute.
• This being the year 2012, the first-in-source-order option reads 2010 and has a value of 2010; the next option reads 2011 and has a value of 2011; and so on. The current year is preselected.

By tweaking the above for loops you can expand the calendar year range. If you would like the calendar to have, say, a seven-year range centered around the current year, then
(1) iterate the ii counter from 0 to 6 in both loops,
(2) subtract 3 from yyyy in the first loop, and
(3) use an ii == 3 if condition in the second loop.
That's all there is to it.

Moving along, the lines below get us to the tables[2] table:

text += "</td>";
text += "</tr></table>"; // Closes the tables[1] table
text += "</td></tr>"; // Closes the first row of the tables[0] table
text += "<tr><td>";
text += "<table border='1'>";


The tables[2] table's first row holds the Sun-to-Sat day headings, and is written with:

var arrD = new Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
text += "<tr>";
for (ii = 0; ii <= 6; ii++) { text += "<td align='center'><span class='label'>" + arrD[ii] + "</span></td>"; }
text += "</tr>";


• The arrD array's day heading elements map onto the 0-to-6 returns of the Date object's getDay( ) method.
• The align='center' cell alignment would be redundant if we were to mark up the cells as th elements, as we should.
• Each cell is equipped with a span element child having a class='label' CSS identifier. Here's the span.label rule set in the snippet's style block:

span.label { color: black; width: 30; height: 16; text-align: center; margin-top: 0; background: #fff; font: bold 13px Arial; }

Style bullet points:
• The span part of the selector is unnecessary.
• The width and height settings lack unit identifiers, which is illegal.
• More importantly, the CSS width and height properties do not apply to inline span elements. (Ah, but IE will let you set a CSS width and height for an inline span - this would seem to be the smoking gun that the snippet was indeed written for IE.)
The CSS text-align property doesn't apply to inline span elements either.
(The width, height, and text-align properties duly apply to display:inline-block; spans, but that's not what we've got.)
• The use of th cells would allow us to throw out the bold part of the font declaration.
• Lastly, I see no difference at all upon commenting out the margin-top: 0; declaration.

Is the span wrapper necessary in the first place? Nah - the color, width, height, background, and font settings can all be applied to the th parent(s).

The remaining 6 rows/42 cells of the tables[2] table are written by a 'two-dimensional loop':

aa = 0;
for (kk = 0; kk <= 5; kk++) {
    text += "<tr>";
    for (ii = 0; ii <= 6; ii++) {
        text += "<td align='center'><span id='sp" + aa + "' onclick='changeBg(this.id);'>1</span></td>";
        aa += 1; }
    text += "</tr>"; }


The align='center' cell alignment is all right here although I myself would specify this in the style block. Each cell is 'initialized' with a 1* unnecessarily wrapped in a span element. From left to right and top to bottom, the spans are given ordinalized ids: sp0, sp1, ... sp41. Each span is bound to the changeBg( ) function via an onclick attribute.

*FWIW: With IE 5.x for Mac, the attempted replacement of these 1s with the current month's dates gives a really strange display.
The Calendar snippet display with IE 5.2.3 for Mac
This is evidently a version bug as I am able to run the snippet with IE 4.5 in the SheepShaver environment without any problems; removing the 1s (leaving the cells empty to begin with) sorts out the situation.

We wrap up the writeCalendar( ) function by (1) closing the tables[2] table, the tables[0] table, and the calForm form, (2) writing the text string to the page, and (3) calling the changeCal( ) function.

text += "</table></td></tr></table></form>";
document.write(text);
changeCal( ); }


changeCal( ) intro

The changeCal( ) function begins by creating a now Date object and fetching now's getDate( ), getMonth( ), getDay( ), and getFullYear( ) returns.

function changeCal( ) {
    var now = new Date( );
    var dd = now.getDate( );
    var mm = now.getMonth( );
    var dow = now.getDay( );
    var yyyy = now.getFullYear( );


Actually, this same series of statements appears at the beginning of the writeCalendar( ) function. The above discussion attaches the now/mm and yyyy assignments to the writeCalendar( ) sections to which they are relevant; I didn't mention the dd and dow assignments because the writeCalendar( ) function makes no use of them. The changeCal( ) function doesn't act on the dow return either; it employs the now, dd, mm, and yyyy returns to lightgreen-highlight the current day, and that's it.

Anyway, if we were to specify the now, dd, mm, and yyyy assignments as top-level code

<script type="text/javascript">
var now = new Date( );
var dd = now.getDate( );
var mm = now.getMonth( );
var yyyy = now.getFullYear( );
function maxDays(mm, yyyy) {
    ...


then we would only have to write them out once. And once we've done that, then moving the calendar to the future or past is a simple matter of putting an appropriate Date.parse( )-compatible dateString into the now constructor, e.g.:

var now = new Date("Jan 1, 1943");

We'll get into the meat of the changeCal( ) function in the next entry.

Tuesday, April 03, 2012
 
Bookmark, IP, Back
Blog Entry #246

We return now to our tour of HTML Goodies' "Top 10 JavaScript Snippets for Common Tasks" tutorial. In this entry we'll discuss the Add to Favorites and IP Address snippets.

Your new fave

The Add to Favorites snippet codes a link that when clicked opens a dialog box prompting the user to add the current Web page to the browser's favorite/bookmark collection.

<a href="javascript:window.external.AddFavorite('http://www.yoursite.com', 'Your Site Name');">Add to Favorites</a>

The AddFavorite( ) method was implemented by Microsoft in IE 5 for Windows (IE 5 for Mac doesn't support it). The first AddFavorite( ) parameter specifies the URL for the favorite; the optional second AddFavorite( ) parameter specifies a title for the favorite. As a favorite is a browser-specific datum, you'd think that the AddFavorite( ) method would be associated with the navigator object; instead, Microsoft tied AddFavorite( ) to an external object that allows access to an additional object model provided by host applications of the Windows Internet Explorer browser components.

Scott correctly notes that this snippet only works with IE; other browsers throw a window.external.AddFavorite is not a function (or equivalent) error upon clicking the Add to Favorites link. Of the browsers on my computer, Firefox, Chrome, Camino, and Netscape 9 support the external object to an extent, but none of these browsers supports the AddFavorite( ) method.

The snippet should not be deployed as is: non-IE users should at least get a 'Houston, we have a problem'-type message rather than just an error. One very good approach to this situation is to have the href JavaScript URL call a setFavorite( ) function that respectively places the AddFavorite( ) command and the non-IE message in the try and catch clauses of a try...catch statement.

function setFavorite( ) {
    try { window.external.AddFavorite("http://www.yoursite.com", "Your Site Name"); }
    catch(err) { window.alert("Please type Control-D/Command-D to bookmark this page."); } }


Now, if in the future non-IE browsers add support for the AddFavorite( ) method (I see that Ian 'hixie' Hickson is thinking about bringing window.external into HTML5), then users of those browsers will be good to go. An External interface has been brought into HTML5 but it doesn't include the AddFavorite( ) method.

On its AddFavorite( ) page, Dottoro notes that Firefox supports an addPanel( ) method that does what AddFavorite( ) does.

window.sidebar.addPanel("Your Site Name", "http://www.yoursite.com", "");

The addPanel( ) method is officially associated with Mozilla's proprietary sidebar object, and has an inverse-AddFavorite( ) syntax in that its first parameter specifies a title for the bookmark and its second parameter specifies the bookmark's URL; it also takes a required third parameter that can be used to specify a customized URL for the bookmark but is typically 'left blank' (set to an empty string).

In truth, you would never pick up from the aforelinked Mozilla window.sidebar page that the addPanel( ) method has anything to do with bookmarks at all; moreover, the page states that addPanel( ) has been obsolete since Firefox 23 [and is now only supported by Mozilla's SeaMonkey browser]. Dottoro's AddFavorite( ) page offers an AddFavorite( )/addPanel( ) code sample and accompanying demo that worked smoothly with Firefox and Netscape 9 when I first tested it

The Firefox Add Bookmark dialog box

but throws a window.sidebar.addPanel is not a function TypeError with the version of Firefox (48.0.2) currently on my computer. In its code sample, Dottoro conditions the addPanel( ) command via an if (window.sidebar) { ... } clause and the AddFavorite( ) command via an if (window.external && ("AddFavorite" in window.external)) { ... } clause; the "AddFavorite" in window.external subcondition employs the in operator, which tests if a given property or method is part of an object's interface.

Interestingly, probing the external object with a for...in statement reveals that Firefox and Netscape 9 associate the addPanel( ) method with the external object as well as with the sidebar object.

for (var i in window.external) { document.write("window.external." + i + " = " + window.external[i] + "<br>"); }

Firefox output:
window.external.QueryInterface = function QueryInterface() { [native code] }
window.external.addPanel = function addPanel() { [native code] }
window.external.addPersistentPanel = function addPersistentPanel() { [native code] }
window.external.addSearchEngine = function addSearchEngine() { [native code] }
window.external.AddSearchProvider = function AddSearchProvider() { [native code] }
window.external.IsSearchProviderInstalled = function IsSearchProviderInstalled() { [native code] }


Besides addPanel( ), Firefox's external object interface includes a QueryInterface( ) method, an addPersistentPanel( ) method, an addSearchEngine( ) method, an AddSearchProvider( ) method, and an IsSearchProviderInstalled( ) method; Netscape 9's external object interface also includes these methods plus sixteen other 'members'. However, the Mozilla Developer Network's Web site does not have a window.external page as of this writing.

Your IP address, comrade

The IP Address snippet is a small script that displays the user's IP address:

<script type="text/javascript">
var ip = '<!--#echo var="REMOTE_ADDR"-->';
document.write("Your IP address is: " + ip);
</script>


I looked at this guy and said, "This doesn't look like JavaScript." Sure enough, the <!--#echo var="REMOTE_ADDR"--> thing that is assigned as a string to the ip variable isn't JavaScript at all but what is called a "server side include": an instruction carried out by the server that serves the page to the user.

As it happens there are three brief tutorials on server side includes ("SSI") at the HTML Goodies site:
(1) "SSI: The Include Command"
(2) "SSI: Dates and Times"
(3) "SSI: File Returns"
Written in 2000 by Joe Burns, these tutorials are not findable via the site's left-hand navigation menu (their URLs indicate that they should be in the Webmaster Tips sector, but they're not there), but they do crop up in the Text section of the site's "Master List" page.

In his "SSI: The Include Command" tutorial introduction, Joe says that Server Side Includes (SSIs) are Perl language-based commands; in fact, SSI were developed by the National Center for Supercomputing Applications (NCSA) for use by the NCSA HTTPd server. (Were the NCSA folks inspired by Perl? Maybe, I don't know.) Documentation for SSI is today maintained by the Apache Software Foundation.

Apache specifies the syntax of a basic SSI command as:

<!--#element attribute=value attribute=value ... -->

Attribute values must be quoted. The command has a <!--# open delimiter and a --> close delimiter; the former cannot be followed by white space whereas the latter is supposed to be preceded by white space. If the server is not configured to parse SSI, then on the client side the browser sees the command as a comment and does not render it.

Getting back to the IP Address snippet, the echo element and the var attribute are used to print the value of a variable, in this case REMOTE_ADDR, a standard CGI environmental variable that holds the user's IP address. The REMOTE_ADDR value is CDATA and therefore the entire command can alternatively be placed in any can-contain-#PCDATA element; it's not necessary to use JavaScript and a document.write( ) command to display the REMOTE_ADDR value.

<div>Your IP address is: <!--#echo var="REMOTE_ADDR" --></div>

For a functioning echo var="REMOTE_ADDR" demo, check out JavaScript Kit's "Cut & Paste IP address display (using SSI and JavaScript)" page and its script that loads the REMOTE_ADDR value into a readonly text box. (Frustratingly, neither the home.earthlink.net server, which I used to use for my demos, nor the reptile7.blogspot.com server is configured to parse SSI.)

For other server-side approaches (and one client-side approach) to getting the user's IP address, see About.com JavaScript's "Obtaining Your Visitor's IP Address" tutorial.

Lagniappe: Looking Backward

The tutorial's JavaScript Back Button snippet codes a push button interface

<input type="button" value="Previous Page" onclick="history.go(-1);">

and a link interface

<a href="javascript:history.back(1);">Previous Page</a>

for mimicking the browser's 'Back' button. Why history.go(-1) in one and history.back(1) in the other? Moreover, I was a bit suspicious about history.back(1)'s 1 argument. I looked up the back( ) method of the History object and here's what I found:

On the Microsoft side, history.back( ) can take an integer argument specifying the number of locations to go back in the browser's history list (Microsoft is not clear as to whether "history list" means "current session history") but doesn't have to, in which case history.back( ) is equivalent to history.go(-1).

On the Mozilla side, history.back( ) takes no arguments and is equivalent to history.go(-1).

The W3C has brought the History interface into HTML5 and has adopted the Mozilla history.back( ) syntax.

The tutorial's Break Out of Frames, Focus OnLoad, and Specify Referring Page snippets are not worth our while, bringing us at last to the Calendar snippet, which we'll take on in the following entry.


Powered by Blogger

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