reptile7's JavaScript blog
Wednesday, December 23, 2009
Into and Out of the Screen We Go
Blog Entry #166

Having done the browser sniffing thing in the last few entries, we switch gears a bit today and take up HTML Goodies' "Post by Screen Size" tutorial in order to briefly address another factor that can play a major role in the rendering/presentation of document content: screen resolution.

In thinking about how to approach this post, it occurred to me that a full discussion of the screen resolution topic would require us to delve into some rather technical matters:
• If a monitor screen has, for example, a 1024 x 768 native resolution, what does that mean in physical terms? Just what is a screen pixel? (If anyone out there is capable of writing up a good "Screen pixel" Wikipedia entry, it'd be great if you would do so.)
• If on a given computer the screen resolution setting is changed, say from 1024 x 768 to 800 x 600, what exactly happens?
• How is content - in a document, or on the desktop for that matter - 'painted' onto the screen in the first place?
Undoubtedly the engineers at IBM and Apple could answer these questions, but I can't, so we'll keep it simple in the discussion that follows.

Joe also keeps it simple in "Post by Screen Size", concerning himself with two practical issues:
(1) How do we reconcile document content with the various screen sizes out there in 'userland'?
(2) What should be done about a content element (specifically an image) that overflows a user's screen?

So many monitors, so little time

As detailed in the tutorial's "Redirection Choice" section, Joe's first plan of action for mapping content onto varying screen sizes is to create separate pages for those sizes, an approach he later concedes might be overkill; these pages are to be accessed via a 'screen sniffer' script analogous to a browser sniffer:

if (screen.width == 1600)
{ parent.location.href = "1600page.html"; }

if (screen.width == 1280)
{ parent.location.href = "1280page.html"; }

// etc.

Go here for the rest of the code. Joe's screen sniffer specifically tests for the following screen widths: 1600px, 1280px, 1152px, 1024px, 800px, 640px, and less than 640px. If your screen.width is 1600, you'll be sent to a 1600page.html page; if your screen.width is 1280, you'll be sent to a 1280page.html page; and so on. The screen object and its width property have long had cross-browser support but are not standard as of this writing; however, they are on track to be incorporated into the W3C's CSSOM View Module.

Joe provides a demo page for his screen sniffer here. The demo's ####page.html destination pages contain no real content to speak of (just a bit of text and an incorrectly pathed, and thus nonfunctioning, Back to the Tutorial hyperlink); as 'normal' pages, they are meant to be scaled so that the user doesn't have to do any (read: is spared the ultra-annoyance of) horizontal scrolling.

My iMac has a 1680 x 1050 native screen resolution, which is not flagged by Joe's screen sniffer, but it also supports a variety of other display resolutions:
Display resolutions supported by my iMac
If I downgrade my display resolution to 1280 x 800 and then go to Joe's screen sniffer demo page, I am duly redirected to the 1280page.html page.

So far we haven't said anything about what kind of content we might want to scale for different screen sizes. A purely textual document marked up in a normal manner wouldn't need to be scaled at all. But what about a really large image, huh? In fact, any block-level element - a div, a table, a form, or even a p(aragraph) - could be given a sufficiently large CSS width such that it poses a nuisance for a user with a smaller screen.

My img runneth over

As document content, an image that is wider than the user's screen will require horizontal scrolling to see all parts of the image. Joe takes up the large image/smaller screen situation in the tutorial's "Internal Page Choice" section and offers the following solution thereto: The easiest way to solve the problem is to create [smaller] version[s] of the image for [smaller screen sizes]. With a set of differently sized images in hand, and drawing inspiration from the "Internal Browser Test" code that we've been discussing in the last couple of entries, Joe then writes one of those images to the page via a modified version of the "Redirection Choice" screen sniffer:

if (screen.width < 639) document.write("Hello there");
if (screen.width == 640) document.write("<img src='630px.gif'>");
if (screen.width == 800) document.write("<img src='750px.gif'>");
if (screen.width >= 1024) document.write("<img src='850px.gif'>");

In order to illustrate the "Internal Page Choice" code, Joe created the three rectangular images below:

The 850px.gif image

The 750px.gif image

The 630px.gif image

If your screen.width is 1024 or greater, you'll see the 850px.gif image; if your screen.width is 800, you'll see the 750px.gif image; and so on. Go here for a demo.

Making a bunch of images is still too labor-intensive for my tastes, however; much better would be to proportionately shrink the largest image's width and height for smaller screen sizes as follows:

function scaleImage(imageWidth) {
if (screen.width < imageWidth) {
document.images["imageID"].width = 0.8 * screen.width;
document.images["imageID"].height = 0.8 * screen.availHeight; } }
/* For the browsers on my computer, the screen.width and screen.availWidth returns are equal but the screen.availHeight return is typically (not always) less than the screen.height return. */
<img id="imageID" width="####" height="####" onload="scaleImage(this.width);" src="myImage.gif" alt="[Alternate text]" />

To try out my script, click on the thumbnail image below - a photograph of somewhere in Mexico (downloaded once upon a time from that will occupy ca. 80%* of your 'screen real estate' should open in a new window.
*More specifically, it'll occupy 80% of the screen if your screen.width is less than the imageWidth, 1601px in this case; otherwise you'll get the actual-size (1601px-by-1198px) photo.

Somewhere in Mexico

FYI: On my computer, Firefox's screen.width return varies with and is inversely proportional to the Zoom setting; upon increasing the Zoom, the photo in the new window gets progressively smaller.

Other elements

Anything else that we need to worry about? I trust y'all know better than to deploy width:2000px; headings, paragraphs, and blockquotes, although such elements can be easily downsized via code similar to that given above:

<body onload="scalePara( );">
<p id="p1">My interminably wide paragraph, blah blah blah...</p>
var myPara = document.getElementById("p1"); = "2000px";
function scalePara( ) {
if (screen.width < parseInt( = 0.8 * screen.width; }

But some large content should be left alone, IMO. Imagine a div holding a timeline depicting historical events on a long, horizontal axis - you wouldn't really want to shrink something like that (at least I wouldn't). Or consider a table with long rows of data: in this case you might want to put the data in a spreadsheet and then make the spreadsheet file available for download (this is not the time/place to discuss CSS 2.1's "Dynamic [table] row and column effects").

All the same

In the "Post by Screen Size" introduction, Joe links to an earlier "So You Want To Get Them All The Same, Huh?" article that presents a series of twenty-one cross-platform tips for HTML artists. Some of the information in this article is seriously out of date, but the overall philosophy of the article - code to the lowest common denominator and use the most recent technologies sparingly - remains quite valid. An important corollary to this philosophy is that part of being a coder is to understand that not everyone in the world is using the same (type of) equipment that you are using, and that if you're serious about what you're doing, then you need to think about reaching out to as many users and user agents as possible.

Three of the "So You Want To Get Them All The Same, Huh?" tips are directly relevant to the "Post by Screen Size" tutorial:

Tip Fourteen: If possible, use percentages when [setting] widths.
This is a generally good practice as it will keep your content within the bounds of the user's screen/viewport (assuming, of course, that you don't set those percentages above 100%). FYI: Joe's statement that you must be using pixels when denoting an image's HEIGHT and WIDTH is incorrect. The height and width attributes of the img element have a %Length; data type, which can be specified in pixels or as a percentage.

Tip Thirteen: Force your page's width.
Tip Twenty: If you need to pick a screen resolution to design for, choose 640 x 480.
"Force"? 640 x 480?? Again, the details are much less important than the underlying idea: Keep tabs on a page's width and be able to catch when it might cause a problem for a user with a small screen.

We'll check over the remaining "JavaScript Browser Test Scripts" tutorial, "Test Visitors for the Flash Plug-In", in the next entry.


Saturday, December 12, 2009
Dealing with Isolated Proprietary Features, Part 2
Blog Entry #165

We continue today our discussion of HTML Goodies' "Internal Browser Test" tutorial and its scripts.

A bit more on the intbrowtest_example_code.html script


For the script's first two if statements, Joe chose an MSIE 4/3 'dividing line' because [v]ersion 4.0 is...when DHTML came to IE. Joe could have driven his point home by implementing the title and cursor effects dynamically, which as an added benefit would have allowed a clean separation between the (at-the-time) proprietary code and the nonproprietary code:

<script type="text/javascript">
function realDHTML( ) {
if (navigator.appName == "Microsoft Internet Explorer" && parseFloat(navigator.appVersion) >= 4.0) {
document.anchors["link1"].title = "Gotta Go To Goodies";
document.anchors["link1"].style.cursor = "help"; } }

<body onload="realDHTML( );">
<p><a name="link1" href="">HTML Goodies</a></p>
<!-- Netscape 4.x supports the id attribute for the object and layer/ilayer elements but for no other elements. -->

Feature detection

We have previously on occasion used feature detection as opposed to navigator object property returns to test for a specific browser or group of browsers. At least on the Netscape side, feature detection can be profitably applied to the intbrowtest_example_code.html script. The script's only-Netscape-4.x-supports-the-layer-element problem would be smoothly solved by replacing the
if (navigator.appName == "Netscape" && navigator.appVersion >= "4.0") test
with an
if (document.layers) test.

On the MSIE side, we could similarly replace the
if (navigator.appName == "Microsoft Internet Explorer" && navigator.appVersion >= "4.0") test
with an
if (document.all) test,
although there's no real advantage in doing so (OK, it's easier to type). Moreover, the document.all condition would additionally flag users of Opera, which like MSIE supports the all( ) collection; however, the original "Microsoft Internet Explorer"/"4.0" test will also return true for some Opera users. If for whatever reason you did want to exclude Opera users, you could do so with an
if (document.all && !window.opera) test.
The window.opera expression references an Opera-specific opera object, which is documented here. For non-Opera browsers, window.opera returns undefined, which is converted to false (and thus !window.opera evaluates to true) in a Boolean context.

if...else if...else

You may have noticed that the script's
// This code is for IE less than v4,
// This code is for Navigator v4 or less, and
// This code is for neither IE nor Navigator
sections all execute the same command, e.g.:

if (navigator.appName != "Microsoft Internet Explorer" && navigator.appName != "Netscape")
{ document.write("<a href=''>HTML Goodies</a>"); }

It would be more efficient to roll these parts of the script into a single catch-all, which could in turn serve as the final else alternative in an if...else if...else structure:
if (navigator.appName == "Microsoft Internet Explorer" && parseFloat(navigator.appVersion) >= 4.0)
{ title/cursor commands }
else if (document.layers)
{ layer element/object code }
else document.write("<a href=''>HTML Goodies</a>");
(An aside: formulating the Script Tips #13-15 browser sniffer as an else if 'cascade' would eliminate the need for the r tracking variable.)

The home page follies

MSIE 5.0 for Windows was released about two months before "Internal Browser Test" was written, and Joe concludes the tutorial with an example that wanders into the MSIE 5+ for Windows-specific world of DHTML behaviors, an interface that extend[s] the current Windows Internet Explorer object model. The tutorial's "Setting your page as Home Page: IE5.0 only!" section offers code that attempts to use Microsoft's behavior property to change the user's browser home page to the current page; this code is conditionalized via an if statement that specifically flags users of MSIE 5.0 for Windows. Here's what we've got:

if (navigator.appName == "Microsoft Internet Explorer" && navigator.appVersion == "4.0 (compatible; MSIE 5.0b2; Windows 95)") {
document.write("<style><!-- .homepage { behavior: url(#_IE_); } --></style>");
document.write("<u><span style='color:blue;cursor:hand;' class='homepage' onclick='this.setHomePage(window.location);'>Make this your home page</span></u>"); }

else document.write("Make this page your Home Page!");

The if block's second document.write( ) command writes to the page a Make this your home page span element string marked up to mimic a link: blue text, underlined, cursor set to hand.
HTML 4 deprecated the u element; text-decoration:underline; is its CSS equivalent.
hand is a nonstandard Microsoft cursor value; pointer is the corresponding standard value.

The span element is equipped with a class='homepage' attribute that ties it to the if block's first document.write( ) command, which assigns a behavior:url(#_IE_); style to the span element. The MSDN Library's behavior property page is here; checking over the page's Possible Values section, it is clear that the url(#_IE_) value is off target: specifically, url(#_IE_) should match the id attribute value of an object element that represents a [b]inary implementation of a DHTML behavior (cf. the last example on the behavior page), and there's nothing like that going on in this script.

The right behavior

Not quite a year later Joe posted a "So You Want ME As Your Homepage, Huh?" tutorial that sets the correct behavior for changing the user's home page:

<span style="cursor:hand;color:blue;text-decoration:underline;" onclick="'url(#default#homepage)'; this.setHomePage('');">Click Here to Make HTML Goodies Your Home Page</span>

Upon clicking the Click Here to Make HTML Goodies Your Home Page span text, the'url(#default#homepage)'; assignment registers with the span element/object the Windows Internet Explorer #default homePage* behavior/object, which is documented by Microsoft here. All that remains is to call the homePage setHomePage( ) method, whose parameter, in this case, specifies the URL of the user's new home page.

(*homePage or homepage? Case mistakes can stop a script dead in its tracks! On the MSDN Library's "homePage Behavior" page, the Syntax box says "homePage" but it's "homepage" in the Example. Maybe it doesn't matter - All CSS syntax is case-insensitive within the ASCII range (i.e., [a-z] and [A-Z] are equivalent), except for parts that are not under the control of CSS, the W3C stipulates - but as a non-Windows user, I am unfortunately not able to sort this out for you.)

Try me, catch me

Let's get back to the "Internal Browser Test" home page code for just a moment. As shown above, the code's if container only allows MSIE 5.0 for Windows users 'through the gate', so to speak. But what about users of more recent MSIE versions, huh?
(In the name of completeness:
According to Microsoft, MSIE 5.0 for Unix also supports the homePage behavior/object. However, like MSIE for the Mac, MSIE for Unix was discontinued long ago, and I would think that MSIE for Unix users would have moved on to other browsers by now, but if any of them are still out there, how would we accommodate those guys?)

Moreover, the behavior property is part of a proposed addition to CSS: what if the W3C were to standardize this code, leading to its widespread support by non-MSIE browsers? Ideally, we would like to enable general access to the code for current and future browsers that might support it, but at the same time ensure that the code 'fails gracefully' for browsers that don't support it. Gratifyingly, this is achievable via a try...catch statement, a common programming mechanism for handling errors and which was added to JavaScript in JavaScript 1.4.

I won't go through the try...catch statement in detail in this post - for a comprehensive treatment of the try...catch statement, see the try...catch page in the Mozilla JavaScript Reference - but will below with explanatory commentary show you how to apply it to the "So You Want ME As Your Homepage, Huh?" home page code.

We begin by separating the nonproprietary code from the proprietary code. Here's the HTML/CSS base we will use:

#span1 {
color: blue;
cursor: pointer;
text-decoration: underline; }

<span id="span1">Click Here to Make HTML Goodies Your Home Page</span>

As for the proprietary code, we will relocate the two homePage behavior commands to a newHomePage( ) script function. We coassociate the newHomePage( ) function, click events, and the span element/object in the normal way:

var mySpan = document.getElementById("span1");
mySpan.onclick = newHomePage;

function newHomePage( ) { ... }

We're ready to fill in the newHomePage( ) function body. We're not going to execute the homePage commands willy-nilly; rather, we will try to execute them by putting them in the try block of a try...catch statement:

try { = "url(#default#homePage)";
mySpan.setHomePage(""); }
catch (err) {
window.alert("This feature requires MSIE 5+ for Windows."); }

If a given browser can execute the homePage commands in the try block, it will do so, and then move to whatever follows the catch block(s), if present. However, the setHomePage( ) method call throws an error with the browsers on my computer; for these browsers, an Error object is passed to the catch block and given the catch parameter, err in this case, as an identifier. You can choose or not choose to probe properties of the Error object in the catch block - e.g., err.message returns mySpan.setHomePage is not a function when using Firefox - my preference is to simply display a This feature requires MSIE 5+ for Windows. message.

For those of you willing to try all of this out, click on the link-like text in the div below:

<title>Setting a New Home Page</title>

Click Here to Make HTML Goodies Your Home Page

If you're an MSIE 5+ for Windows user, as you are statistically likely to be, then a dialog box should pop up prompting you to confirm the new home page value before it is set for your browser; after clicking or whatever, go to the browser's Preferences pane to verify that you have a new home page and to reset your previous home page (unless you really do want to change your home page to

We'll briefly discuss the second "JavaScript Browser Test Scripts" tutorial, "Post by Screen Size", in the following entry.


Tuesday, December 01, 2009
The First Browser War, Revisited
Blog Entry #164

There are two browser sniffers in the series of scripts discussed by HTML Goodies' JavaScript Script Tips:

(1) Script Tips #10-12 present a bare-bones browser sniffer that uses a single navigator.appName-based test to distinguish between Netscape users and MSIE users.

(2) Script Tips #13-15 present a more elaborate browser sniffer that largely relies on navigator.appVersion returns to distinguish between users of various versions of MSIE and Netscape.

The Script Tips #10-12 and #13-15 scripts both send users of a specific browser/version to a different page with proprietary content appropriate for those users, which is an all-right thing to do if the destination page contains a lot of such content, but if you want to deploy only one or two proprietary features, is it really worth it to create separate pages for separate categories of users?

HTML Goodies' "Internal Browser Test" tutorial, our focus today, offers a simple and obvious approach to dealing with isolated proprietary features. Directly place the proprietary code in the block of an if statement that flags the browser(s) that can execute that code:

if (yourBrowser == "Browser X") {
proprietary code... }

Users browsing with Browser X will get the intended effect; the browsers of other users will skip over the if block. If desired, you can alert those other users to what they're missing via an accompanying else statement

else {
window.alert("You could be enjoying a really cool feature if you were surfing with Browser X."; }

but this is optional, of course.

Script overview

According to its "Getting Specific" section, the "Internal Browser Test" tutorial was written in May 1999. In reflection of the browsers in use at that time, Joe wrote for the tutorial a script containing a series of if statements that use navigator.appName and navigator.appVersion returns to test if the user's browser is MSIE 4+, Netscape 4+, or an earlier MSIE/Netscape version. Joe first applies his script to printing out a simple text string that identifies the user's browser in general terms, for example:

if (navigator.appName == "Netscape" && navigator.appVersion >= "4.0")
{ document.write("Running Netscape 4 or better?"); }
// Go here for the rest of the code.

The appName property did faithfully differentiate Netscape from Microsoft Internet Explorer back in the day, but is no longer a reliable tool for determining the make of a browser. I know of several non-Netscape browsers, including Firefox and Safari, that return Netscape for the appName property; similarly, Opera gives a Microsoft Internet Explorer appName return in some situations. If Browser X identifies itself with Browser Y's appName, does it follow that Browser X supports what Browser Y supports? This might be true some of the time, but I wouldn't bank on it.

The appVersion property returns a string beginning with a floating-point numeral, which may or may not match the actual version number of the browser, and containing other information. The navigator.appVersion >= "4.0" test is not a conventional comparison between two numbers* but rather a 'lexicographical' comparison between two strings based on the Unicode (hexadecimal) code points of their respective characters.

In illustration, let's suppose that I am surfing with Netscape 4.61, whose appVersion return on my computer is 4.61 (Macintosh; I; PPC). In comparing 4.61 (Macintosh; I; PPC) and 4.0 as strings, we begin with their first characters, which are both 4, so we go to their second characters, which are both ., so we go to their third characters, where we find a 6 vs. 0 difference. The Unicode code points of 6 and 0 are U+0036 and U+0030, respectively, and therefore 4.61 (Macintosh; I; PPC) is "greater than" 4.0 and the navigator.appVersion >= "4.0" test will return true.

*Alternatively, a comparison between numbers could be carried out via the top-level parseFloat( ) function:
if (navigator.appName == "Netscape" && parseFloat(navigator.appVersion) >= 4.0) { ... }

The numeral at the start of the appVersion string exactly or approximately matches the browser version number for 'level 4' and earlier browsers, but this is not the case for subsequent browsers (most of the time), as Joe himself points out for MSIE 5 in the "Getting Specific" section. Here are the appVersion returns for the (JavaScript-supporting) OS X browsers on my computer:
Camino 2.0:	5.0 (Macintosh; en)
Firefox 3.5.5:	5.0 (Macintosh; en-US)
MSIE 5.2.3:	4.0 (compatible; MSIE 5.0; Macintosh; I; PPC)
Opera 10.10:	9.80 (Macintosh; Intel Mac OS X; U; en)
Safari 4.0.4:	5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-us) AppleWebKit/531.21.8 (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10
So, what else does Joe do with his MSIE/Netscape 4± tests, huh?

Span, title, cursor

Joe uses his MSIE 4+ test to write to the page an HTML Goodies link whose underlying anchor element is given a cursor:help; inline style and whose parent is a span element equipped with a title='Gotta Go To Goodies' attribute: standard code that has long had cross-browser support.

if (navigator.appName == "Microsoft Internet Explorer" && navigator.appVersion >= "4.0")
{ document.write("<span title='Gotta Go To Goodies'><a href='' style='cursor:help;'>HTML Goodies</a></span>"); }
// Go here for the rest of the code.

Rather than send you to Joe's demo page, at which the title/cursor effects will only be observable by MSIE and some Opera users, let's demonstrate the above HTML/CSS here and now, shall we? Move your mouse cursor over the HTML Goodies link below (but don't click it, unless you really do want to go to the HTML Goodies home page):

HTML Goodies

Your mouse cursor should change to or display a question mark (that's what cursor:help; does) and a Gotta Go To Goodies tooltip should pop up (that's what title='Gotta Go To Goodies' does).

Quick history:
• The W3C implemented the span element in HTML 4.
• Prior to HTML 4, the title attribute was only valid for the anchor and link elements; HTML 4 now applies it to all but a handful of elements.
• As a CSS property, cursor was introduced in CSS level 2; previously it was a proprietary Microsoft DHTML property.

Netscape 4.x can't handle the above code because, although it does support the span element,
(1) it doesn't support the title attribute for the span or any other element, and
(2) it doesn't support the cursor property.
Netscape/Mozilla has supported title as a general element attribute and the CSS cursor property from Netscape 6 onward.

Back to the layer element

Falsely reasoning that you can safely assume that all versions above 4.0 will continue to support what 4.0 does - ouch! - Joe uses his Netscape 4+ test to write to the page a layer element whose visibility is toggled by moving the mouse cursor over and away from a Go to Goodies link:

if (navigator.appName == "Netscape" && navigator.appVersion >= "4.0") {
document.write("<layer name='layer1' visibility='hide' bgcolor='#ff00ff' width='100' height='100' top='75' left='450'><center><br><br>Gotta Go To Goodies</center></layer>");
document.write("<center><a href='' onmouseover=document.layer1.visibility='show' onmouseout=document.layer1.visibility='hide'>Go to Goodies</a></center>"); }

More specifically, in a maximized window on a 1024 x 768 screen, mousing over the Go to Goodies link would cause a 100px-by-100px, fuchsia-background

Gotta Go To Goodies
block of content to appear directly below the link, and mousing out from the link would cause that block of content to disappear, if your browser were to support the layer element/object. But the layer element/object is only supported by Netscape 4.x - Netscape abandoned it for Netscape 6. For those of you with an historical bent, the layer element/object and its attributes/properties are described in Chapters 8 and 9 of Netscape's "Dynamic HTML in Netscape Communicator" resource, although the above layer code is reasonably intuitive and, more importantly, is easily retooled to a corresponding cross-browser div-based layout:

#div1 {
visibility: hidden;
background-color: fuchsia;
width: 100px; height: 100px;
position: absolute; top: 75px; left: 450px; }
#div1, #div2 { text-align: center; }

<div id="div1"><br /><br />Gotta Go To Goodies</div>
<div id="div2"><a href="" onmouseover="document.getElementById('div1').style.visibility='visible';" onmouseout="document.getElementById('div1').style.visibility='hidden';">Go To Goodies</a></div>

Move your mouse cursor over and away from the Go to Goodies link in the div below to try out Joe's layer/my div effect with your browser:

<title>Layer to Div</title>

I've switched the div order, subtracted the positioning, and functionized the commands for the above demo.

Let me set your home page

Joe wraps up the tutorial by discussing a separate script that conditionalizes proprietary code for changing a user's home page, a topic that deserves more space than a quick paragraph at the end of an entry, so we'll take it up next time.


Powered by Blogger

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