reptile7's JavaScript blog
Tuesday, October 23, 2007
 
Bouncer Script B: XHTML Validation
Blog Entry #92

As promised, in today's post we will validate the password-protection script of HTML Goodies' JavaScript Script Tip #74. Setting the bar high, as is our wont, we will below bring the Script Tip #74 Script in conformance with the XHTML 1.0 Strict Document Type Definition (DTD). But first, a bit of preparation is in order...

From HTML to XHTML

The basic differences between XHTML and HTML are listed in Chapter 4 of the XHTML 1.0 Specification; the following sections thereof are most relevant to the Script Tip #74 Script:

4.2. Element and attribute names must be in lower case
With only a few exceptions, the HTML element and attribute names in the original Script Tip #74 Script are written in uppercase letters; however, I lowercased these names for the script version that I put in a div two entries ago.

4.1. Documents must be well-formed
4.3. For non-empty elements, end tags are required
There are no overlapping (improperly nested) elements in the Script Tip #74 Script - so far, so good. As for the script's non-empty elements, five tags are missing:
(1-2) The html element start-tag and end-tag
(3) The head element start-tag
(4) The form element end-tag
(5) The body element end-tag

The W3C informs us:
In SGML-based HTML 4 certain elements were permitted to omit the end tag; with the elements that followed implying closure. XML does not allow end tags to be omitted. All elements other than those declared in the DTD as EMPTY must have an end tag.
The HTML 4 Index of Elements shows that there are four elements whose start-tags and end-tags are both optional: html, head, body, and tbody. Section 3.1.1 of the XHTML 1.0 Specification states that a strictly conforming XHTML document necessarily has an html element start-tag equipped with an xmlns="http://www.w3.org/1999/xhtml" attribute. A literal reading of Section 4.3's tag requirement thus suggests that a valid XHTML document can still omit the start-tags of the head and body (and, when relevant, tbody) elements as long as the end-tags for these elements are present.

The Abstract for the XHTML 1.0 Specification proclaims that XHTML 1.0 [is] a reformulation of HTML 4 as an XML 1.0 application, however, and the element production in the XML 1.0 Specification

element ::= EmptyElemTag | STag content ETag

clearly shows that if an element is present in an X(HT)ML document, then both its start-tag and end-tag are required (i.e., there's no ? following STag or ETag). I also note that, unlike their HTML counterparts, the element declarations in the XHTML DTDs do not contain the - and O indicators for required and optional tags, respectively; for example:

<!ELEMENT p %Inline;> (XHTML)
vs.
<!ELEMENT P - O (%inline;)* -- paragraph --> (HTML)

The XHTML declarations do not distinguish between required and optional tags because, well, there are no optional tags in XHTML. So for validation purposes, the missing tags listed above must all be added to the Script Tip #74 Script. (But we don't need to add <tbody> and </tbody> tags, even though the script contains a table, for a reason discussed below.)

4.6. Empty Elements
(See also Appendix C's C.2. Empty Elements)
I trust y'all know that with respect to empty elements and XHTML compliance you're supposed to add a space and a slash before the final > character, e.g.:
• Convert <br> to <br />
• Convert <hr> to <hr />
• Convert <input id="input0" name="iName" maxlength="15"> to
<input id="input0" name="iName" maxlength="15" /> - etc.

The document type declaration

A valid (X)HTML document must include a document type (DOCTYPE) declaration, which is placed at the top of the document prior to the html element start-tag. (It follows that if a document doesn't have a DOCTYPE declaration - and there are quite a few Web pages out there that don't have them - then it hasn't been validated, even if it otherwise conforms to one of the W3C's DTDs.) The DOCTYPE declaration for an XHTML 1.0 Strict document is:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

The MSDN Library's !DOCTYPE page provides the best 'anatomy' of a DOCTYPE declaration that I've seen.

Case-sensitivity is important! I can confirm that DOCTYPE must be in uppercase, html must be in lowercase*, and PUBLIC must be in uppercase; otherwise, a validator will determine that the document is not valid.
(*As shown below, an uppercase HTML is OK for HTML validation.)

According to the W3C, the http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd URL, termed the system identifier, allows user agents to download the DTD and any entity sets that are needed, i.e., if the user is online, the browser 'patches in' to the system identifier resource (the DTD) in much the same way that a script element's src="myScript.js" attribute connects the browser to an external script. Does this happen in practice? I don't know. It is clear that the HTML 4.01 Transitional DTD (or its equivalent) is already hard-coded into current browsers given that they can render (X)HTML documents without being connected to the Web.

Many Web pages have DOCTYPE declarations without system identifiers; for example, at the top of the source of EarthLink's home page, we find:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

This declaration is acceptable for HTML validation but not for XHTML validation. To validate an XHTML document against one of the XHTML DTDs at the W3C's Web site, the PUBLIC availability keyword, the -//W3C//DTD XHTML 1.0 (Strict | Transitional | Frameset)//EN public identifier, and the system identifier must all appear in the declaration.

From invalid to valid

In summary, our necessary pre-validation changes are:
(1) Subtract the script's presentational elements and attributes and replace them with style rules (we did this two entries ago).
(2) Add the proper DOCTYPE declaration to the top of the document.
(3) Recast the html element start-tag as: <html xmlns="http://www.w3.org/1999/xhtml">.
(4) Make the document well-formed (i.e., correctly tag the start and end of all elements).
(5) Lowercase the names of elements and attributes.

So, I uploaded an appropriately modified Script Tip #74 Script document to my EarthLink server space. I surfed over to the W3C's Markup Validation Service and entered the document's URL into the Address: field in the Validate by URI section. Lights...camera...action!! I clicked the Check button; up popped a "This page is not Valid XHTML 1.0 Strict!" page detailing a "problem" and three "errors" in my document:

Problem: "No Character Encoding Found! Falling back to UTF-8."

Man, you'd think EarthLink would deal with this on the server side; evidently not. Per the W3C's recommendation, I added the meta element below to the head of my document:

<!-- Follow the title element with: -->
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />

Character encodings and the setting thereof are treated here in the HTML 4.01 Specification.

Error #1: "The [first-in-source-order br] element is not allowed to appear in the context in which you've placed it."

Regarding the large Login heading that begins the document body, my document replaced

<p align="center"><br><font size="5" face="arial"><u><b>Login</b></u></font></p>

with

<br /><h2>Login</h2>

With this formulation, the br element is now a child element of the body element, which is a violation of the body element's content model. To make a long story short, in the DTD the br element is a member of the %special.pre; set of elements, which is exclusive to the %Block; set of elements that can be body element children. Anyway, I thought, "Is this br element really necessary? Let's get rid of it," and I accordingly commented it out.

Error #2: "<form name='iAccInput'>: there is no attribute 'name'"

Section 4.10 of the XHTML 1.0 Specification declares, Note that in XHTML 1.0, the name attribute of [the a, applet, form, frame, iframe, img, and map] elements is formally deprecated, and will be removed in a subsequent version of XHTML - very sloppy of me to have missed that. I removed the name="iAccInput" attribute and recast the script element's

iName = document.iAccInput.iName.value;
AccId = document.iAccInput.iAccID.value;

statements as:

iName = document.forms[0].iName.value;
AccId = document.forms[0].iAccID.value;

Error #3: "<form name='iAccInput'>: required attribute 'action' not specified"

With respect to the script's execution, there's no need for the form element to have an action attribute because we're not submitting the form to a processing agent; checking the DTD, however, we see that the action attribute of the form element does indeed have a #REQUIRED default value designation. The %URI; action value's replacement text is "CDATA" - CDATA is defined here in the HTML 4.01 Specification as a sequence of characters from the document character set and may include character entities - so, reasoning that an empty string is still a string, I simply added action="" to the form element start-tag.

Alternatively, we could remove the form element container and reference the iAccInput controls in the script element via document.getElementById("input#") expressions:

iName = document.getElementById("input0").value;
AccId = document.getElementById("input1").value;

BTW, I had already replaced the script element's language="javascript" attribute with a type="text/javascript" attribute; the absence of a type attribute, which is also #REQUIRED, would have thrown another error.

Having made the above changes, I reuploaded my script document and resubmitted it to the W3C validator; this time I was greeted with a "This Page Is Valid XHTML 1.0 Strict!" page! Success!

XHTML and the tbody element

So why don't we need to add a tbody element to the Script Tip #74 Script? In HTML, the tbody element is necessarily a child element of the table element:

<!ELEMENT TABLE - - (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+)>

However, XHTML revises the content model of the table element so that the tbody element is an optional child of the table element:

<!ELEMENT table (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))>

We'll go through Script Tip #75's password-protection script in the following entry.

reptile7

Saturday, October 13, 2007
 
Bouncer Script B: JavaScript
Blog Entry #91

We return now to HTML Goodies' JavaScript Script Tip #74's password-protection script. Having mowed through the script's HTML in the previous entry, we are ready to put its JavaScript under the microscope. Without further delay, here's what happens when the user follows Script Tip #74's "Here's the Script" link to the script demo page:

<body onload="window.status=('You will need a password and username to proceed further...');">

When the demo page document loads, the string You will need a password and username to proceed further... is written to the browser window's status bar.

"What's with the parentheses surrounding the window.status value?"

I was wondering that myself; they are, of course, 100% unnecessary.

<td id="cell1">
<input id="input0" name="iName" maxlength="15" /><br /><br />
<input id="input1" name="iAccID" maxlength="15" />
</td>

The user enters respectively a user name and a password into the iName and iAccID text fields in the second table cell.

<td id="cell2">
<input type="button" value="Login" onclick="Getstats( );" />

The user attempts to access the target page by clicking the third table cell's Login button, which triggers the Getstats( ) function in the document head.

function Getstats( ) {
window.status = "Attempting to Login to user area.";

The string Attempting to Login to user area. is written to the status bar.

var iName, AccId;
iName = document.iAccInput.iName.value;
AccId = document.iAccInput.iAccID.value;

The variables iName and AccId are first declared but not initialized; subsequently, the value values (i.e., user input) for the iName and iAccID fields are respectively assigned to iName and AccId.

if (iName == "" || AccId == "") {
alert("\nERROR\nYou must enter ALL Details,\nto View your statistics.\n");
window.status = "Missing data or Invalid. Check spelling and Ensure Names are in Correct Case."; }

If either iName or AccId is an empty string, i.e., if the user leaves blank either the iName field or the iAccID field, then an alert( ) box pops up

Alert( ) box for blank user name or password fields

and (after the alert( ) OK button is clicked) another message is written to the status bar. We noted in Blog Entry #82 that a \n newline induces a line break on a prompt( ) box - so it goes for an alert( ) box as well.

else {
var location = ("pw" + iName + AccId + ".html");
// The parentheses surrounding the location value are unnecessary.

If iName and AccId are not empty strings, then the user's entered user name (iName) and password (AccId) are concatenated with the strings pw and .html as shown above, and the resulting URL string is assigned to the variable location. Although "location" is not a JavaScript reserved word, Joe correctly notes in Script Tip #74 that 'location' isn't a good [choice for a variable identifier] - never name a variable after an actual JavaScript command.

this.location.href = location;
window.status = " Verifying: " + iName + "-" + AccId + " Please wait........"; } }

A link to the location URL is set up by assigning location to this.location.href. The this keyword is unnecessary and should be removed (location is of course a property of the top-level window object); indeed, it's downright strange that this statement doesn't throw an error. (The this operator is used in the body of constructor functions to associate custom properties and methods with custom objects - see Chapter 7 ("Working with Objects") of the JavaScript 1.5 Core Guide - but that's not what's going on here.)

If the user name and password are correct, then the user is sent to the target page, e.g., at the script's demo page, iName=script and AccId=tip inputs take the user to a "You're in!" pwscripttip.html page. If either the user name or the password is incorrect, then the user is taken to a "404/File not found" page (assuming that there's no pw_wrongUserName_wrongPassword.html page in the current directory) - the browser takes care of the bad logins for you, as Joe puts it.

In principle, yet another message is written to the status bar at this point. In practice, on my computer and regardless of browser, neither the Attempting to Login... nor the Verifying:...Please wait........ message is visible in the status bar long enough for me to see it when both the iName and iAccID input fields have not been left blank. (If the user doesn't enter a user name or a password, then the Attempting to Login... message is visible in the status bar when the \nERROR\n... alert( ) box pops up, but if the alert( ) command is commented out, then only the subsequent Missing data or Invalid... message is seen.)

Separating the script's HTML and JavaScript

At the beginning of Blog Entry #86, we listed several practical reasons why HTML and JavaScript should normally not be commingled. The small amount of JavaScript in the Script Tip #74 Script's HTML is easily relocated to the script's script element:

(1) Replace the body element's onload/window.status command with the following script element block:

function intromessage( ) {
window.status = "You will need a password and username to proceed further..."; }
window.onload = intromessage;

As we have already replaced the bgcolor="#00ff00" attribute with a style rule, this reduces the body element start-tag to simply <body>.

(2) Similarly, replace the Reset button's onclick/window.status command with the following script element block:

function resetmessage( ) {
window.status = "RESET: Please enter your USERNAME and ACCOUNT ID."; }
document.forms[0].elements[3].onclick = resetmessage;

The original Reset button's value=" Reset " attribute is unnecessary and can be removed. Recast the Reset button as:

<input type="reset" />

Unlike var variableName = functionName( ); statements, the object.onevent = functionName; statements above are not function calls; rather, each of these statements merely assigns a reference for the functionName( ) function to the onevent property of the object in question, and as for an onevent="functionName( );" HTML attribute, it is the associated event itself (in the cases above, the loading of the document and the clicking of the Reset button) that triggers functionName( ) - see the "Defining an Event Handler" section of the JavaScript 1.3 Client-Side Guide.

(3) Finally, we can replace the Login button's onclick="Getstats( );" function call with the following script element statement:

document.forms[0].elements[2].onclick = Getstats;

With this formulation, I found that the

var location=("pw" + iName + AccId + ".html"); this.location.href = location;

code threw an error

Runtime error: 'this.location' is not an object

when I tried to link to a custom pwscripttip.html; however, the slightly modified commands below smoothly took me to the target page:

var newLocation = "pw" + iName + AccId + ".html";
location.href = newLocation;

One more point: after making these changes, relocate the script element itself to the document body and place it after the iAccInput form element, or the browser will throw an error when it hits the first-in-source-order document.forms[0].elements[#].onclick = functionName; statement.

XHTML compliance/validation

My virgin experience with the W3C's (X)HTML validator definitely deserves its own entry and we'll get to it next time.

reptile7

Thursday, October 04, 2007
 
Bouncer Script B: HTML/CSS
Blog Entry #90

Today's and the next posts will examine the Script Tip #74 Script, the second of three password-protection scripts that are discussed by HTML Goodies' JavaScript Script Tips #73-75. We saw in the previous entry that the Script Tip #73 Script contained its password and its target Web page URL, which were unrelated to each other. In contrast, the Script Tip #74 Script's target Web page URL is partially composed of the script password. As we'll see later, the Script Tip #74 Script concatenates an inputted password with other text strings to give a value for a location.href command: if this value matches the target page URL, then the user is linked to the target page; if not, the user is taken to a "File not found" page.

The Script Tip #74 Script is reproduced in the div below:

<script language="javascript">

//---------------------------------
// Login script by Alex Keene 1997
// info@firstsound.com
// ---------------------------------

function Getstats( ) 
{
window.status = ("Attempting to Login to user area.");

var AccId;
var iName;
AccId = document.iAccInput.iAccID.value;
iName = document.iAccInput.iName.value;

if (AccId == "" || iName == "")
{ 
alert("\nERROR\nYou must enter ALL Details,\nto View your statistics.\n");
window.status = ("Missing data or Invalid. Check spelling and Ensure Names are in Correct Case.");
} 
else
{

var location = ("pw" + iName + AccId + ".html");
this.location.href = location;
window.status = (" Verifying: " + iName + "-" + AccId + " Please wait........");
}
}

</script>

</head>

<body bgcolor="#00ff00" onload="window.status=('You will need a password and username to proceed further...');">
<p align="center"><br><font size="5" face="arial"><u><b>Login</b></u></font></p>
<hr>
<form name="iAccInput">
<center>
<table border="3">
<tr>
<td align="right"><font size="3" color="#ff0000" face="arial"><b>User Name:</b></font><br><br>
<fontsize="3" color="#ff0000" face="arial"><b>Password:</b></font></td>

<td><input type="text" name="iName" maxlength="15"><br>
<input name="iAccID" maxlength="15" height="50"></td>
<td><input type="button" value=" Login " onclick="Getstats( );" height="40" width="50"><br>
<input type="reset" value=" Reset " onclick="window.status=('RESET: Please enter your USERNAME and ACCOUNT ID.');" width="50">
</td></tr></table></center>

The Script Tip #74 Script demo page is here.

The script's HTML and its CSS 'reflection'

The Script Tip #74 Script's display is mostly housed in a one-row, three-cell table; commingled with the table is a form named iAccInput. The first table cell holds User Name: and Password: strings that serve as labels for text boxes that are respectively named iName and iAccID and that are held by the second table cell. The third table cell contains self-explanatory Login and Reset buttons.

Above the table, and separated therefrom by a horizontal rule (<hr>), is a large Login heading, which is as good a place as any to begin crafting a style sheet. The Login heading is coded as:

<p align="center"><br><font size="5" face="arial"><u><b>Login</b></u></font></p>

This heading is marked up in six different ways:
(1) The p element parent gives it a block-level display.
(2) The (deprecated) align="center" attribute centers it on the page.
(3-4) Its font size and typeface are set by the size="5" and face="arial" attributes of the (deprecated) font element.
(5) The b element boldens it.
(6) The (deprecated) u element underlines it.
(Curiously enough, this is the first time we've encountered the u element in HTML Goodies' JavaScript materials. Why the W3C decided to retain the b and i elements but deprecate the no-less-wonderfully-intuitive center and u elements surely ranks as one of the great mysteries of the ages.)

Because Login is a heading, perhaps we should code it that way:

<h2>Login</h2>

Style-wise, this kills three birds with one stone:
(1) The h# (%heading;) elements are also block-level elements.
(2) At least on my computer, the size="5" attribute and the h2 element both give a 24pt font size.
(3) The h# elements are boldened.

The align="center" and face="arial" attributes and the u element can be replaced by the following CSS rule set:

h2 {
text-align: center;
font-family: arial, sans-serif;
text-decoration: underline; }

The display table is itself centered on the page by a center element. Also, in the demo page document Joe equips the table element start-tag with six attributes that do not appear on Script Tip #74's "Here's the Code" page:

<table border="3" cellpadding="2" cellspacing="8" bgcolor="#c0c0c0" bordercolor="#808080" bordercolorlight="#808080" bordercolordark="#000000">
<!-- #c0c0c0 and #808080 correspond to the W3C-approved color names silver and gray, respectively. -->

Bordercolor? Bordercolorlight? Bordercolordark? As you might suspect, these attributes are neither "strict" nor "transitional" W3C table element attributes but are - you guessed it - proprietary Microsoft...I would say "attributes" but according to the MSDN Library links above, borderColorLight and borderColorDark are properties of the table scripting object and are not supposed to be HTML attributes at all. The borderColorLight and borderColorDark properties are meant to be used in conjunction with the W3C-kosher border property of the table object; regarding Joe's table element, we would write these properties in a script as follows:

document.getElementById("tableID").border = "3";
document.getElementById("tableID").borderColorLight = "gray";
document.getElementById("tableID").borderColorDark = "black";

<!-- Recast the table element start-tag as: -->
<table id="tableID" cellpadding="2" cellspacing="8" bgcolor="#c0c0c0">

And just what do borderColorLight and borderColorDark do, anyway? The MSDN Library gives each of these properties the same description:
Sets or retrieves the color for one of the two colors used to draw the 3-D border of the object.
Turning to HTML Goodies' highly useful "CSS and Borders" tutorial, we see that there are four values of the CSS border-style property that seem to give rise to "3-D" borders: groove, inset, outset, and ridge. (However, CSS 2.1 specifies context-dependent definitions for the inset and outset values when applied to tables - see here.) In practice at the demo page, inspection of the display table shows that the bordercolor="gray" bordercolorlight="gray" bordercolordark="black" markup gives a border whose appearance is a cross between the solid and outset CSS border styles when using MSIE: the top and left borders are solidly gray and the right and bottom borders are solidly black, even though we would expect all four borders to be solidly gray because only the bordercolor attribute should be operative.

In any case, if your browser is up to date, then you can replace the display table's centering and attributes - all of them - with the following style rules:

table {
margin-left: auto; margin-right: auto;
border: 3px solid;
border-collapse: separate;
border-spacing: 8pt;
background-color: silver;
border-color: gray black black gray; }

td {
padding: 2px;
border: 1px solid;
border-color: gray black black gray; }

Comments
• Tables are normally rendered as block-level elements, which can be centered by setting the CSS margin-left and margin-right properties to auto.
• CSS 2.1 discusses here the use of the border-collapse and border-spacing properties to insert spacing between the table border and adjacent cells and between the table cells themselves, i.e., as a replacement for the table cellspacing attribute.
• The gray black black gray values of the border-color property respectively set colors for the top, right, bottom, and left borders of the table/td elements, as detailed here.
• Table cellpadding can be handled at the td element level via the padding property.

The first table cell is also marked up in several different ways:

<td align="right"><font size="3" color="#ff0000" face="arial"><b>User Name:</b></font><br><br>
<fontsize="3" color="#ff0000" face="arial"><b>Password:</b></font></td>
<!-- In the preceding line, there's supposed to be a space between font and size; as a result of this typo, the font element attributes are not applied to the Password: label on the demo page. -->

The applicable style block would be:

td#cell0 {
text-align: right;
font-size: 16px;
color: red;
font-family: arial, sans-serif;
font-weight: bold; }

Because User Name: and Password: are labels, let's code them that way:

<td id="cell0">
<label for="input0">User Name:</label><br /><br />
<label for="input1">Password:</label></td>
<!--
In order to explicitly associate these labels via their for attributes with the second table cell's input fields, recast the iName and iAccID text boxes as:
<input id="input0" type="text" name="iName" maxlength="15" />
<input id="input1" name="iAccID" maxlength="15" height="50" />
-->

(The first table cell has a header-like quality, and an argument can be made that it should be coded as a th element as opposed to a td element, but I'm going to stick with a td element for this post.)

Moving to the second and third table cells, three of the four iAccInput controls have been equipped with height and/or width attributes, which are not valid for the input element; for a second example, the Login button is coded as:

<input type="button" value=" Login " onclick="Getstats( );" height="40" width="50">

The MSDN Library says that the width attribute (but not the height attribute) should be legit for all of the visible input element types, so one might expect the Login and Reset buttons to have 50px widths when using MSIE, but this isn't what I see on my computer. (In case you were wondering, I don't see a 50px height for the iAccID text box nor a 40px height for the Login button, either.)

Leaving aside for a moment the issue of whether or not you actually want to set non-default dimensions for these controls, the question arises: Can we use CSS to change input element heights and widths? The W3C confesses:
CSS 2.1 does not define which properties apply to form controls and frames, or how CSS can be used to style them. User agents may apply CSS properties to these elements. Authors are recommended to treat such support as experimental. A future level of CSS may specify this further.
In the event, I find that both MSIE 5.1.6 and Netscape 7.02 will apply a

input { height: 50px; width: 50px; }

rule set to the iAccInput controls, although my preference is to go with their default dimensions (and simply remove the input height/width attributes).

Finally, we can set the document background color with:

body { background-color: lime; }

OK, that should take care of the presentational aspects of the Script Tip #74 Script. We'll look at the JavaScript parts of the script in the following entry.

reptile7


Powered by Blogger

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