reptile7's JavaScript blog
Thursday, June 28, 2012
 
All the Flowers are Right Here
Blog Entry #256

As promised, today's post kicks off a trek through the examples in Part 2 ("Positioning HTML Content") of Netscape's Dynamic HTML in Netscape Communicator (DHiNC) resource. This is something I've wanted to do for a while, and I'm finally getting around to it - some people work on antique cars, why can't I work on antique scripts? So here we go.

According to the Internet Archive, Netscape stopped hosting the DHiNC resource in 2004; current mirrors for the resource can be found at various locations, e.g., here. The DHiNC examples are well and truly obsolete as they are applications of the layer element and object, which are ONLY supported by the Netscape 4.x group of browsers. Our task is to bring these examples into the modern era by revamping them so that they work with modern browsers, including IE, Netscape's 'bête noire' at the time the DHiNC resource was written. (It is still possible to download Netscape 4.x and run the original examples therewith, but I really don't recommend that you do this.)

The DHiNC examples begin with the Fancy Flowers Farm (FFF) Example, which constitutes Chapter 10 of the resource. The FFF example is a variation on the slide show concept: one of four 'fancy flower slides' is displayed at a specific position on the page; the user chooses which slide to display via a selection list as opposed to running through the slides in sequence via a push button or link.

Netscape offered two demo pages for the FFF example:
(1) a flower.htm <layer>-based demo page and
(2) a flowercs.htm CSS/div-based demo page.
There's actually not that much difference between these pages; however, the flower.htm code features both the layer element and the layer object whereas the flowercs.htm code only features the layer object, and we will confine ourselves to the flowercs.htm code for the discussion that follows. In this entry we'll go through the flowercs.htm HTML and CSS; we'll clean up the flowercs.htm JavaScript and roll out a cross-browser demo in the next entry.

I would provide links to the flower.htm/flowercs.htm pages but, again, these demos are not going to work for you if you're not using Netscape 4.x. That said, current DHiNC mirrors provide much cleaner code samples than does the Internet Archive, so you should go to the former if for whatever reason you would like to access the original FFF example code.

Structure/presentation

The FFF grows and sells four types of flowers:
(1) Mona Lisa Tulips
(2) Mixed Dutch Tulips
(3) Bijou Violets
(4) Pink Chrysanthemums

These flowers are detailed and illustrated in a series of divs. Here's the first-in-source-order flower div:

<div id="flower0">
<hr>
<h3 align="center">Mona Lisa Tulip</h3>
<img src="images/redtul.jpg" align="left" hspace="5">
<p>These tulips have been specially designed to withstand late winter frost in areas with harsh winters. They are a beautiful red color, and we guarantee that they'll grow for at least four years in a row. Don't wait to order them, they sell fast!</p>
<br clear="all">
<p>Priced at only $1 a bulb, they are a bargain.</p>
<hr>
</div>


The following CSS is applied to the flower0 div:

#flower0 { position: absolute; left: 50; width: 400; background-color: ffffdd; border-color: white; border-width: 1; }

• The div begins and ends with a horizontal rule. Just below the top rule is a centered h3 heading. The align attribute of the h# elements is deprecated: the align="center" attribute should be replaced by an h3 { text-align: center; } style rule.

• Below the h3 heading is an img photo of two Mona Lisa tulips. The img is floated to the left of the div via an align="left" attribute. The align attribute of the img element is deprecated: we should left-float the img with an img { float: left; } style rule instead.

• An hspace="5" attribute inserts 5 pixels of horizontal white space between the left edge of the img and the left edge of the div and between the right edge of the img and the left edge of an adjacent paragraph. The hspace attribute of the img element is deprecated: the hspace="5" attribute should be replaced by a margin-left: 5px; margin-right: 5px; style declaration set.

• The img is followed by a descriptive paragraph that runs down the right side of the img; the img and the paragraph are in the same line box because the img is floated. Below the img is a second paragraph that specifies price information. The descriptive and pricing paragraphs are separated by a br element holding a clear="all" attribute, which vertically 'clears' the pricing paragraph from the floated img; the br element also inserts 16 pixels of vertical white space between the bottom edge of the img and the top edge of the pricing paragraph. The clear attribute of the br element is deprecated: the clear="all" attribute can be replaced by a br { clear: left; } style rule; alternatively and preferably, we can (a) throw out the br element, (b) clear the pricing paragraph by giving it a clear: left; style, and (c) bottom-buffer the img with a margin-bottom: 16px; style.

• The div is given a width of 400: a CSS length lacking a unit identifier is at present illegal and doesn't seem to have been any more acceptable back in the day. In practice, the 400 is interpreted as 400px by the browsers on my computer.

• The div's background-color is set to ffffdd, and I'm pretty sure that an RGB color value lacking a preceding # is also illegal. (YEP, it's illegal: I just tested body { background-color: ffffdd; } with the W3C's CSS Validation Service and a "Value Error" was thrown.) However, the browsers on my computer all impart a #ffffdd background to the div.

• Leaving aside the question of why you would want to give the div a white border in the first place, the div's border-color: white; border-width: 1; style declarations render a 1px solid white border with Netscape 4.x but not with other CSS-supporting browsers, for which the border-style property, whose initial value is none, must be set to see a border.

Here's what it all looks like:

The Mona Lisa Tulip slide

The div is absolutely positioned and given a left offset of 50 (pixels). No top offset is specified for the div; per Section 10.6.4 of the CSS 2.1 Specification, the div's top in this case is set to the "static [normal flow] position" because the div has also not been given a height nor a bottom offset. Consequently, the div top-wise appears just below the form-containing div that precedes it in the source, as it normally would.

The other flower divs are set up just like the Mona Lisa Tulip div except that
(1) they have different background-colors and
(2) they are 'hidden' by a visibility:hide; style.

Netscape and Microsoft implemented visibility as a CSS property at about the same time, more specifically in Netscape 4 and IE 4, respectively. Netscape applied visibility to positioned elements and gave it values of show and hide|hidden for respectively showing and hiding an element; this visibility definition is only supported by Netscape 4.x. Accordingly, at the flowercs.htm page you see the last-in-source-order Pink Chrysanthemum div (and a bit of the Bijou Violet div) with a modern browser, which ignores the visibility:hide; declarations. I'll give you a full set of Netscape visibility references in the JavaScript part of the deconstruction.

For its part, Microsoft applied visibility to all elements and gave it values of visible and hidden for respectively showing and hiding an element; this visibility definition was subsequently standardized by the W3C in CSS 2.1 and has been supported by Netscape/Mozilla browsers from Netscape 6 onward.

Prior to the flower0 div is a formlayer div that holds a form1 form that holds the aforementioned selection list via which the user changes the flower slide.

#formlayer { position: relative; left: 50; }
...
<div id="formlayer">
<h3>Please select a flower:</h3>
<form name="form1">
<select name="menu1" onchange="changeFlower(this.selectedIndex); return false;">
<option>Mona Lisa Tulip</option>
<option>Mixed Dutch Tulips</option>
<option>Bijou Violet</option>
<option>Pink Chrysanthemum</option>
</select></form></div>


The formlayer div is relatively positioned and given a left offset of 50 pixels. The left edge of the formlayer div is not quite horizontally aligned with the left edges of the flower divs because an initial 8px of margin-left given to the formlayer div by the browser is left intact by the position: relative; style; to the extent that this bothers you, the formlayer div and the flower divs can be left-aligned by reducing the formlayer div's left offset to 42px.

FYI: Switching the flower div position to relative will send the Mixed Dutch Tulips div, the Bijou Violet div, and the Pink Chrysanthemum div to their normal flow positions, i.e., they will no longer be at the slide position. Conversely, changing the formlayer div position to absolute will remove the formlayer div from the normal flow and cause the Mona Lisa Tulip div to obscure the formlayer div.

At the top of the page, the formlayer div is preceded by a centered h1 heading, which is vertically flanked by horizontal rules, and an introductory paragraph.

<body color="white">
<hr>
<h1 align="center">Welcome to the Fancy Flowers Farm</h1>
<hr>
<p>We sell bulbs, seeds, seedlings, and potted plants, in all shapes, sizes, colors, and varieties. This page presents information about our most popular varieties.</p>


• The body element has never had a color attribute - the author meant bgcolor here. The bgcolor attribute is deprecated for all of the elements that could formerly take it; to give the page a white background color, a body { background-color: white; } style rule is what we want. FWIW: The color="white" attribute, which is ignored by the browser, does allow the aforediscussed border-color: white; border-width: 1; div border to be seen with Netscape 4.x, whose default body background color is #c0c0c0.

We're ready to move on to the FFF example's JavaScript. Changing the menu1 selection list's selectedIndex triggers a changeFlower( ) function, which we'll dissect in detail in the following post.

Tuesday, June 19, 2012
 
More Accept to Enable
Blog Entry #255

We return now to our discussion of HTML Goodies' "How To Use JavaScript To Ensure Users Agree Before Posting" tutorial. The JavaScript part of the "Ensure Users Agree" code is posted here as an agreeBeforePosting.js file for your downloading convenience. The tutorial's third paragraph presents and describes a demo for the code.

Commenter nullWeirdo has an eval( ) question:
simple script but many programmer said to avoid eval( )... maybe someone can explain why we need to avoid eval( )
Mozilla's current eval( ) page features a Don't use eval( ) needlessly! section that begins:
eval( ) is a dangerous function, which executes the code it's passed with the privileges of the caller. If you run eval( ) with a string that could be affected by a malicious party, you may end up running malicious code on the user's machine with the permissions of your webpage/extension.
Mozilla is referring here to the role that eval( ) can play in cross-site scripting (XSS) attacks. To my understanding, the "Ensure Users Agree" myform form - or any other form that accepts text input for that matter - could indeed be vulnerable to XSS attacks depending on what is done with the form, but such attacks should be independent of the agreeBeforePosting.js eval( ) calls, which have their own dedicated tasks (they either get the checked status of the mycheck checkbox or enable/disable the myform text inputs) and are completely separated from whatever a rogue user might enter into the myform text inputs.

Mozilla also notes:
eval( ) is also generally slower than the alternatives, since it has to invoke the JS interpreter, while many other constructs are optimized by modern JS engines.
Fair enough. In any event, we can greatly simplify the agreeBeforePosting.js code and throw out those eval( ) calls by leveraging the myform form's elements[ ] property/collection:

function toggleform(agreeForm, isChecked) {
    if (isChecked) {
        for (i = 1; i < agreeForm.elements.length; i++) agreeForm.elements[i].disabled = false;
        document.myform.pname.focus( ); }
    else for (i = 1; i < agreeForm.elements.length; i++) agreeForm.elements[i].disabled = true; }
...
<input type="checkbox" name="mycheck" value="" onclick="toggleform(this.form, this.checked);"> I Accept


elements[ ] is in the HTML DOM but is best documented in the JavaScript 1.3 Client-Side Reference. If you add a <button disabled>Submit</button> button as elements[4] to the myform form, then it will be enabled or redisabled along with the text inputs by the above code.

Commenter Mark A offers an alternate script that displays a style="visibility:hidden;" myform form if the user checks the mycheck checkbox, which is placed outside and before the form, and rehides the form if the user unchecks the checkbox. Mark's event handler code

function DisplayHide(DisplayValue) {
    if (DisplayValue) { this.document[myform.name].style.visibility = "visible"; }
    else { this.document[myform.name].style.visibility = "hidden"; } }
...
<input type="checkbox" name="mycheck" onclick="DisplayHide(mycheck.checked);"> I agree ...


works verbatim with IE and Opera and works with other browsers once the mychecked.checked DisplayHide( ) argument is changed to this.checked. I myself prefer the original design that allows the user to see the text inputs; however, if I did want to initially invisibilize the myform form, then I wouldn't reach for the CSS visibility property but rather the CSS display property, to be toggled between none and block, as a visibility:hidden; form would unnecessarily take up space on the page.

wrap aside

Interestingly, Mark equips the myform form's textarea element with a wrap="physical" attribute. The wrap attribute has widespread browser support and has been standardized via HTML5; however, neither the W3C, Microsoft, nor Mozilla lists physical as a legitimate value for wrap.

On my iMac, the effect of the wrap="physical" attribute is browser-dependent. With IE, Chrome, and Safari, wrap="physical" is equivalent to wrap="hard", which treats an unforced line break as a CR-LF (carriage return-line feed) and encodes it as %0D%0A; with the Mozilla browsers and Opera, wrap="physical" is equivalent to wrap="soft", which treats an unforced line break as a space and replaces it (plus any preceding white space) with a +. Given this discrepancy, and given that I see no point in keeping track of all of the line breaks in the user's textarea input even if wrap="physical" consistently mapped onto wrap="hard", and given that wrap defaults to soft, I'd say that we should throw the wrap="physical" attribute out.

Microsoft alleges, To detect the difference between soft and hard you must submit the content within the textArea to an HTTP server: not true. Mark includes a button with his form and sets the form method to get; upon typing a wrapping string in the myform textarea field and clicking the button, I am easily able to sort out the various wrap values from the resulting query string(s) in the browser window's address bar right on the desktop.

All good things must come to an end, and it's time for us to finally break free from the HTML Goodies site. Per Blog Entry #115, we will in the next entry switch gears and begin working through and modernizing the examples in Part 2 of Netscape's "Dynamic HTML in Netscape Communicator" resource: first up is the Fancy Flowers Farm Example.

Monday, June 11, 2012
 
Accept to Enable
Blog Entry #254

Today's post will discuss HTML Goodies' "How To Use JavaScript To Ensure Users Agree Before Posting" tutorial, which was authored by Scott Clark. The "Ensure Users Agree" tutorial offers a script that respectively enables or disables the controls of a form depending on whether the user accepts or does not accept the Webmaster's terms of service via checking or not checking an "I Accept" checkbox. The "Ensure Users Agree" script is very slightly adapted from a Webdeveloper.com "[Enable] form fields if checkbox is checked" script crafted by savvykms, a.k.a. Savvy.

The HTML

The "Ensure Users Agree" script works with a form holding four controls.
elements[0] is the gatekeeper checkbox.
elements[1] and elements[2] are Name: and E-Mail: text fields, respectively.
elements[3] is a Post: textarea field.
Here's the form code:

<form name="myform">
<p>To accept the license terms for posting, click the checkbox below:<p>
<input type="checkbox" name="mycheck" onclick="toggleform('document.myform', 'mycheck', 'pname,pemail,ptext');"> I Accept</p>
<table><tr>
<td><b>Name:</b></td><td><input type="textbox" id="pname" name="pname" disabled="true"></td>
</tr><tr>
<td><b>E-Mail:</b></td><td><input type="textbox" name="pemail" disabled="true"></td>
</tr><tr>
<td><b>Post:</b></td><td><textarea name="ptext" disabled="true"></textarea></td>
</tr></table></form>


HTML notes

(1) The text inputs and their preceding text strings are placed in a table in order to horizontally align the left edges of the input boxes; if you don't care about this, then you can throw out the table markup (there's no table in Savvy's original code).

(2) The text inputs are initially disabled via a disabled="true" attribute. In fact, true is not a legal value for the HTML disabled attribute: the correct formulation should be either disabled or disabled="disabled". However, the "Notes on invalid documents" section of the HTML 4.01 Specification recommends:
If a user agent encounters an attribute value it doesn't recognize, it should use the default attribute value.
Accordingly, the browser replaces true with disabled, the #IMPLIED default value for the disabled attribute, and disables the text inputs.

(3) Similarly, textbox is not a legal value for the type attribute of the input element; the value we really want is text, which fortunately is the attribute's default value, and the browser replaces textbox with text per the preceding note.

(4) Semantically, the Name:, E-Mail:, and Post: strings are labels, so we should mark them up that way. If we keep the layout table, then the label strings and the text inputs will have to be explicitly associated via label for attributes and input id attributes, e.g.:

label { font-weight: bold; }
...
<td><label for="input1">E-Mail:</label></td>
<td><input type="text" id="input1" name="pemail" disabled></td>


If we ditch the table, then we can associate them implicitly:

<label>E-Mail: <input type="text" name="pemail" disabled></label>

The JavaScript

Checking the mycheck checkbox calls a toggleform( ) function and passes thereto three arguments.
arguments[0] is document.myform, which refers to the parent form.
arguments[1] is mycheck, which refers to the checkbox itself.
arguments[2] is pname,pemail,ptext, a comma-delimited list of the names of the form controls we want to enable.
These arguments are passed as strings (not as reference expressions) and are respectively given the identifiers formstr, chkobstr, and obstr.

function toggleform(formstr, chkobstr, obstr) { ... }

The toggleform( ) function first gets the checked status of the mycheck checkbox via the following statement:

var checked = eval(formstr + "." + chkobstr + ".checked");

formstr, ., chkobstr, and .checked are concatenated and the resulting document.myform.mycheck.checked expression string is fed to the top-level eval( ) function, which evaluates the string to give true, which is assigned to a checked variable.

Next, the obstr string is split( ) to give an obs array of control name substrings.

var obs = obstr.split(",");

A for loop subsequently prepends formstr and . to each obs substring.

for (i = 0; i < obs.length; i++) { obs[i] = formstr + "." + obs[i]; }

We now have an array of stringified references for the disabled text inputs, as though we had coded:

obs = ["document.myform.pname", "document.myform.pemail", "document.myform.ptext"];

After the loop an if statement tests if checked is equal to false:

if (checked == false) { ... } // Alternatively: if (!checked) { ... }

It isn't, so control passes to an accompanying else clause:

else {
    for (i = 0; i < obs.length; i++) { enableob(obs[i]); }
    document.getElementById("pname").focus( ); }


The else clause sequentially feeds the obs strings to an enableob( ) function.

function enableob(o) { eval(o + ".disabled = false"); }

A .disabled = false string is appended to each obs/o string; the resulting document.myform.controlName.disabled = false statement string is executed by the eval( ) function to enable the corresponding control. Unlike the HTML disabled attribute, the DOM disabled attribute (its HTMLInputElement interface listing is here) in a JavaScript context takes a literal true or false (1 or 0 respectively will also work).

Once the text inputs are enabled, focus is given to the Name:/pname field by the document.getElementById("pname").focus( ); command.

Redisabling the text inputs

We noted two entries ago that both checking and unchecking a checkbox dispatch a click event. Per the foregoing discussion:
(1) Unchecking the mycheck checkbox calls the toggleform( ) function and passes it document.myform, mycheck, and pname,pemail,ptext arguments.
(2) The checkbox's checked status is read and found to be false.
(3) The obstr argument is split( ) into control name substrings, which are converted into stringified object references.
The aforementioned if (checked == false) statement kicks in:

if (checked == false) {
    for (i = 0; i < obs.length; i++) { disableob(obs[i]); } }
...
function disableob(o) { eval(o + ".disabled = true"); }

/* enableob( ) and disableob( ) both appear before toggleform( ) in the source, but the function order here is immaterial. */

Complementing the enableob( ) function is a disableob( ) function that disables each text input by setting its DOM disabled attribute to true.

That wraps up our script deconstruction. In the following post I'll have a few more things to say about the "Ensure Users Agree" tutorial - in particular, I'll show you a much simpler way to code the toggleform( )/enableob( )/disableob( ) functionality - and then I'll briefly outline what we'll be doing next.

Sunday, June 03, 2012
 
Select and Submit
Blog Entry #253

In this post we'll take a quick look at HTML Goodies' "How To Use JavaScript To Auto-Submit Dropdowns, With No Submit Button" tutorial, which was authored by Scott Clark. The "Auto-Submit Dropdowns" tutorial offers a code snippet for submitting a form upon selecting a selection list option without clicking a button. With no further ado, here's the snippet:

<form>
<select name="myfield" onchange="this.form.submit( );">
<option selected>Milk</option>
<option>Coffee</option>
<option>Tea</option>
</select>
<noscript><input type="submit" value="Submit"></noscript>
</form>


A myfield selection list is equipped with Milk, Coffee, and Tea options; the Milk option is pre-selected. Upon selecting the Coffee or Tea option, the selection list's parent form is submitted via the selection list's onchange="this.form.submit( );" event handler. With respect to the form data set, the Milk/Coffee/Tea option labels respectively double as option values, which do not need to be explicitly set.

The selection list is followed by a button wrapped in a noscript element, which zeroes out the button for JavaScript-enabled users. An argument can be made that this is a non-semantic use of the noscript element as the noscript element is meant to provide alternate content when a script is not executed; moreover, the noscript element shouldn't have an input element child as it has a (%block;)+ content model. The state-of-the-art way to zero out the button in this situation is to give it an identifier, e.g., an id="input0" attribute, and then dynamically set its CSS display property to none:

window.onload = function ( ) { document.getElementById("input0").style.display = "none"; }

A demo for the above code is provided in the tutorial's second paragraph.
Spoiler alert:
The parent form's method value defaults to get and its action value 'resolves' to the URL of the tutorial page; as a result, the tutorial page reloads and a ?myfield=optionValue string is tacked on to the page's URL in the browser window's address bar upon selecting the Coffee or Tea option.

Commenter jmrker points out an obvious design flaw:
One other problem, easily corrected, would be the condition where a selection initialized in the drop down is the value the user wants to submit. Then the form would not allow submission unless the user changes from "milk" to something else, which would not be the option the user wanted to send. Easiest correction is to remove the 'selected' from the 'milk' option and add an option with a blank value as the selected option. This forces user to make a change from a blank field for the "onchange=" function to be called.
Long story short: prepend an <option value="" selected>Please choose a beverage</option> option to the option list so that the user can choose/submit the Milk option.

Other notes:

(1) Regarding the Dropdowns in the tutorial title, the auto-submit snippet is of course applicable to list-box selection lists generated by the size and/or multiple attributes of the select element



as well as to drop-down selection lists.



That said, it doesn't make much sense to apply the snippet to a multiple selection list because the parent form will submit as soon as the user selects an option.

(2) In the tutorial's last paragraph, Scott states:
Note that the onchange event can be added to any form, and any element, including radio buttons, select elements, and even text fields.
Classical JavaScript bound the onChange event handler to the FileUpload, Select, Text, and Textarea client-side objects; similarly, HTML 4.01 binds the onchange attribute to the input, select, and textarea elements. However, HTML 4.01's definition of the change event implies that onchange shouldn't apply to radio buttons (or to checkboxes):
The [on]change event occurs when a control loses the input focus and its value has been modified since gaining focus.
Blurring a radio button at most changes the button's checked status; the button's underlying value is not changed. In practice, I find that checking an

<input type="radio" name="myfield" value="Milk" onchange="this.form.submit( );" /> Milk

radio button does indeed trigger the button's onchange event handler with all of the OS X GUI browsers on my computer excepting IE, with which the onchange code is executed when the button is unchecked*; given this discrepancy, I think we should stick with a non-multiple selection list for our auto-submission control, don't you?

*According to a "Fires late on a radio/checkbox button" comment at the bottom of Microsoft's onchange event page, If you use onchange on a radio or checkbox button, the onchange event will not fire until you 'blur' the element. A bit of clarification is in order. Not all browsers will let you run through a set of radio buttons via the tab key, but IE will. Simply blurring a checked or unchecked radio button with IE is not enough to dispatch a change event; rather, to trigger onchange code it is necessary to actively check the radio button in question and then uncheck it by choosing another radio button. IE's checkbox onchange behavior is a bit weird - I'll spare you the details - but we shouldn't be using checkboxes here for the same reason we shouldn't be using a multiple selection list.

(3) The "Auto-Submit Dropdowns" tutorial is somewhat reminiscent of a "Submit The Form Using Enter" tutorial we covered a little over two years ago. The "Submit The Form Using Enter" tutorial addresses the auto-submission of a form by hitting the enter key when a text field has focus; in the tutorial's final section author Joe Burns recommends (not an exact quote), "Only do this if the field is the only one in the form," which is good advice for today's auto-submit snippet as well: the to-be-submitted form should contain the myfield selection list and that's it.

In conclusion, Scott exhorts us to make the world a better place by deploying the select-and-submit snippet so that JavaScript-enabled users will be saved the frustration and effort of submitting a form manually. I myself do not view clicking a button to be such an ordeal, but I guess not everyone shares my heroism in this regard. ;-)

The 'law of diminishing returns' is rapidly setting in vis-à-vis the Beyond HTML : JavaScript sector. Most of the sector's remaining tutorials I am not inclined to go through; these tutorials by and large present information in an abstract context whereas the thrust of this blog is to wrestle with blocks of code that actually do something - it doesn't have to be a productive something, it can be an educational something or a fun something, but it has to be something.

So in the next entry we will discuss one last sector tutorial, "How To Use JavaScript To Ensure Users Agree Before Posting", and then we will at long last bolt the HTML Goodies site - more on this later.


Powered by Blogger

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