Wednesday, October 17, 2007

JavaScript, DOM, and the Humble FORM

Use standard DOM methods and properties to interact with HTML forms.

This is a basic tutorial on using JavaScript to interact with forms on a Web page. It covers two methods. The first method is very common, but was first implemented in old browsers. The second method uses standard Document Object Model methods and properties, and is considered best practice.

Using document.forms to Get DOM References to FORMs

Old browsers polluted the document object with multiple, redundant references to FORM tags. The document.forms array actually contains two parts. First, it is an integer-indexed array of references to FORM tags, and secondly it is an associative array. Let's take the following HTML as an example:

Example 1
<form name="establishment_form">
  ...
</form>

<form>
  ...
</form>

In our pretend HTML document, we've got two forms. The first form is given a name and the second form is not given a name. You can access the Document Object Model nodes that represent those FORM tags like this:

Example 2: Getting DOM node references to the two forms in Example 1
var form1 = document.forms["establishment_form"];
var form1 = document.forms.establishment_form;
var form1 = document.forms[0];

var form2 = document.forms[1];

You'll notice that the first form on the page, which was given a name, can be accessed in three ways: document.forms["establishment_form"], document.forms.establishment_form, and document.forms[0]. They all represent the same Document Object Model node and the same HTML tag.

The second form on the page was not given a name, and can only be accessed using document.forms[1]. Also remember that integer-indexed arrays start with zero, so an index of zero is the first form, and an index of 1 is the second form.

One more wrench is thrown into this cog wheel since the first form was given a name: It can also be accessed via document.establishment_form. So a quick recap of how to get a node reference to the first form tag in our HTML example above (example 1).

Example 3: Getting a DOM node reference to the first form in Example 1
var form1 = document.establishment_form;
var form1 = document.forms["establishment_form"];
var form1 = document.forms.establishment_form;
var form1 = document.forms[0];

Yuck. Writing JavaScript for older browsers basically sucks. All of these different methods existed because browser developers couldn't agree on the best way to do things. Now enter Web Standards.

Standard Methods for Getting DOM Node References to FORMs

There are three main ways of getting a reference to a form tag using standard methods. Let's use our HTML code from example 1 again, slightly modified.

Example 4.
<form id="establishment_form">
  ...
</form>

<form>
  ...
</form>

An Id is used instead of the name attribute. Using an Id is best because it identifies one element on the page. Names can be applied to more than one element, thus the document.forms.form_name method might reference a different form than you think. Having more than one form with the same name will cause JavaScript to return a reference to the first instance of a form with the given name. Furthermore, if you use Cascading Style Sheets, giving Ids to all of your FORMs allows you to easily style forms individually.

Example 5: Standard DOM methods to get references to FORM tags
var form1 = document.getElementById("establishment_form");
var form1 = document.getElementsByTagName("form")[0];

var form2 = document.getElementsByTagName("form")[1];

We still have more than one way to grab a reference to the first FORM in Example 4. The advantage of using document.getElementById over document.getElementsByTagName is if you ever add a FORM to that page, the index number at which the establishment_form FORM tag is referenced may change. Using document.getElementById allows you to physically place that FORM tag anywhere in the HTML document and still be able to grab a reference to that FORM tag without changing your JavaScript. The document.getElementById method is recommended for grabbing DOM node references to FORM tags. Now that we have a node reference to our FORM tag, we need node references to the form fields.

Getting Node References To Fields Within A Form

We'll keep using the HTML from Example 4, and yet again we will expand it.

Example 5: Adding some form fields
<form id="establishment_form">
  <input type="text" name="title">
 
   <textarea cols="50" rows="5" name="description"></textarea>
 
   <input type="submit" value="Add Establishment">
</form>

We'll forgo the labels for each form field, as they aren't important for this tutorial. Right now we have two form fields and one button. The form fields are given a name, and the button is given no name. Let's see how we can access those form fields with JavaScript. First, let's get a node reference to our form:

var form = document.getElementById("establishment_form");

Now that we have a node reference to our FORM tag, let's grab references to each form field using the deprecated methods first implemented by older browsers:

Example 6: Get DOM node references to form fields the old way
// Get a reference to the FORM tag
var form = document.getElementById("establishment_form");

// Use old method of getting references to fields
var title = form.title;
var description = form.description;

That wasn't too difficult. When you give a form field a name, the FORM tag gets a property named the same thing as the form field. This is fine and well, but an inherent property of a FORM tag is also called title. This would be the value of the title attribute of the FORM tag. We have a form field named title, which then overwrites the FORM tag's title property with a node reference to the form field. That's not so good. Suddenly we have a problem. The name space of the FORM tag is quickly polluted with references to the form fields inside. There is a better way.

Example 7: Get DOM node references to form fields the standard way
// Get a reference to the FORM tag
var form = document.getElementById("establishment_form");

// Use standard method for getting references to fields
var title = form.elements["title"];
var description = form.elements["description"];

Each FORM tag has a handy property called elements which holds references to all the form fields. This is a two part property. It contains an integer-indexed array of references to all the form fields, and an associative array of references. Using the elements property, you can get a reference to the "title" form field in three ways:

var title = form.elements["title"];
var title = form.elements.title;
var title = form.elements[0];

You can supply the name of the form field or the index at which it appears in the FORM tag HTML source code. Since the "title" INPUT tag is the first field in the source code, it resides at form.elements[0]. Now think about the future. Maybe you rearrange the form somewhere down the line, and the "title" INPUT tag is no longer the first form field. You can no longer access that field in JavaScript with form.elements[0], so it's always safest to use the form.elements["field_name"] syntax for the same reason it's best to use document.getElementById. It is considered best practice to use form.elements["field_name"].

The table below outlines the properties in the form.elements property.

Example 8: Properties of form.elements
Property Type Description
0 object The "title" form field
1 object The "description" form field
2 object The submit button
length number The number of form fields or elements in this form
title object The "title" form field
description object The "description" form field

Now let's add form fields with the same name: a group of radio buttons.

Example 9: Adding more than one field with the same name
<form id="establishment_form">
  <input type="text" name="title">
 
  <textarea cols="50" rows="5" name="description"></textarea>
 
  <input type="radio" name="estab_type" value="General">
  <input type="radio" name="estab_type" value="Restaurant">
  <input type="radio" name="estab_type" value="Government">
 
  <input type="submit" value="Add Establishment">
</form>

We've added a group of radio buttons, all named "estab_type". This changes the object model of the FORM tag.

Example 10: Properties of form.elements
Property Type Description
0 object The "title" form field
1 object The first "estab_type" radio button
2 object The second "estab_type" radio button
3 object The third "estab_type" radio button
4 object The "description" form field
5 object The submit button
length number The number of form fields or elements in this form (includes all non named elements like buttons)
title object The "title" form field
estab_type array[] Integer-indexed array of references to each radio button.
description object The "description" form field

Now you can see why using the form.elements[n] syntax isn't the best. The order in which the form field appears in the HTML source determines the index number in the form.elements array at which a form field resides. The "description" text field went from index 1 to index 4. If you had used form.elements["description"] to get a reference to the "description" text field, this change to the FORM markup wouldn't have affected your JavaScript.

Now we've run into another unique case. The form.elements["estab_type"] property is an array, not a reference to a form field. Remember that we had three radio buttons named "estab_type". The browser accounts for this by mashing references to all three radio buttons in form.elements["estab_type"]. Let's take a look at the contents of this array.

Example 11: Properties of form.elements["estab_type"]
Property Type Description
0 object Reference to first "estab_type" radio button
1 object Reference to second "estab_type" radio button
2 object Reference to third "estab_type" radio button
length number Number of items in this array.

The object model of the form.elements property then looks like this:

Example 12: Breakdown of the form.elements object model
form.elements[0]      // "title" INPUT
form.elements[1]      // "estab_type" radio 1
form.elements[2]      // "estab_type" radio 2
form.elements[3]      // "estab_type" radio 3
form.elements[4]      // "description" TEXTAREA
form.elements[5]      // submit INPUT
form.elements.length  // Number of form elements
form.elements.title   // "title" INPUT

form.elements.estab_type[0]      // "estab_type" radio 1
form.elements.estab_type[1]      // "estab_type" radio 2
form.elements.estab_type[2]      // "estab_type" radio 3
form.elements.estab_type.length  // Number of radio buttons

form.elements.description  // "description" TEXTAREA

// Note: form.elements.estab_type is the same as
// form.elements["estab_type"]

Since we can reference form fields by their name and by an index number, we can do bulk processing on a form using JavaScript. It's easy to loop through all the fields in a form.

Example 13: Looping through all the form fields
// Get a DOM reference to our form
var form = document.getElementById("establishment_form");

// Loop through all the form fields
for (var i = 0; i < form.elements.length; i++) {
  alert(form.elements[i]);
}

Also recall that our "estab_type" radio buttons formed an array in form.elements["estab_type"]. We can also loop through this with JavaScript:

Example 14: Looping through an array of form fields with the same name
// Get a DOM reference to our form
var form = document.getElementById("establishment_form");

// Loop through all the "estab_type" radio buttons
for (var i = 0; i < form.elements["estab_type"].length; i++) {
  alert(form.elements["estab_type"].value);
}

A Quick Recap

We've covered how to get a DOM node reference to a FORM tag, and also how to get references to the form fields inside of it. There were two basic methods. One used the API of older browsers, which included document.forms, and form.field_name, and the other utilized standard Document Object Model functions and properties like document.getElementById and form.elements. Using the standard DOM properties allows you to change and rearrange your FORM markup down the road with little fear of breaking your JavaScript, and since you are using standard functions and properties, your script will be supported for many years to come.

Methods of getting DOM references to FORMs and form fields
Standard Method (Best Practice) Old Method (Deprecated)
document.getElementById("form_id") document.forms.form_name
document.getElementById("form_id") document.form_name
document.getElementsByTagName("form")[n] document.forms[n]
form.elements["description"] form.description
form.elements.description form.description

Additional References

Monday, October 8, 2007

iPhone: Killing or Redefining Web Standards?

The iPhone will be looked upon as the epoch of the truly mobile web. It doesn't support handheld media style sheets and tries to display web sites normally, as well as tailoring them to its lower resolution. The iPhone may destroy a Web standard or cause it to be redefined.

The World Wide Web Consortium has set up a number of different types of media on which a Web page can be displayed. Chief among them are the "screen" and "handheld" media. Desktop web browsers fall into the "screen" category and things like cellphones and PDAs fall into the "handheld" category. Handheld devices must adapt content designed for the screen media to their less capable displays.

Apple's Safari Web browser on iPhone isn't the first browser that attempts to display Web pages normally. Opera's flavors of mobile browsers do something similar. A while back, I believe around version 7 or 8 of its desktop browser, Opera in mobile mode would try to display a Web page how you'd see it on a desktop computer unless you imported a style sheet for the handheld media. This continues on today as "Small Screen Rendering" (SSR) and attempts to crunch a web page down in size to fit on a smaller screen.

This seems like a fairly sensible approach. The Opera browser is aware it's on a mobile or handheld device and supports hand held style sheets, but in the absence of hand held style sheets it does it's best to display a Web page not designed for a handheld device. Safari on iPhone basically claims it's a desktop browser. With a little additional markup you can change its behavior, and with a CSS3 media query you can feed that particular flavor of Safari a different style sheet. The common theme here is that mobile browsers, despite what the W3C might have imagined, try to be desktop web browsers first unless an alternative method of styling exists.

The emergence of a two-tiered Mobile Web

The World Wide Web Consortium envisioned the Web as requiring different styles based on the display capabilities of a device, but getting the same markup. Reality is panning out differently. Web page HTML has been a bitter soup of presentational markup garnished with sparse portions of meaningful tags for many years, and web browsers must display these pages. To make matters worse, most pages are designed for the screen media, or desktop Web browsers as they are more commonly known as. Browser developers seem to be taking one of two paths:

  • Attempt to display the Web page like a normal desktop browser would, and in the presence of some browser-specific markup, use styles catoring to the device. (Safari on iPhone).
  • If no handheld media style sheets are found, crunch, chew, and rearrange the page to fit the display of the device (Opera Mini and Mobile).

The mobile web is becoming two-tiered. This doesn't seem like something the W3C imagined would happen.

Upon reading that Safari on iPhone wouldn't support handheld media style sheets, I had to wonder if we'd be stuck in a mobile browser war similar to Netscape vs. Internet Explorer at the close of the last century. Looking into it further made me realize that both Opera's and Safari's compromises were out of necessity and shed light on how to realistically create a mobile Web. I'm not sure where the Web Standards explain how handheld browser developers should handle legacy Web pages or pages not designed for handheld devices, but the method for implementing handheld styles is starting to diverge. This could destroy cross-browser functionality, and presents a challenge to the industry on how to handle the non-mobile Web on mobile devices in a standardized fashion.

Even though Safari on iPhone is on a hand held device, there are many reasons that it shouldn't be thrown into the same lot as cell phone browsers. Screen resolution, processor speed, battery life and memory capacity are greater on iPhone, and therefore Safari is more capable. Opera has also divied up its handheld browser into Mini and Mobile versions assuming Mini will be on cell phones and Mobile will be on technologically beefier personal digital assistants and smart phones. Suddenly the notion of a "screen" and "handheld" web is blurred.

The multi-tiered mobile Web

In the coming months we may see a plethora of mobile devices with many levels of capabilities — screen resolutions, color depth, processor speed, memory capacity, plug-in support, battery life — all these things will improve, but at different rates and on different devices. How can we possibly design for a "mobile web" when we don't know what will be accessing it? Opera's implementation of mobile technology is closest to the spirit of the Web Standards when compared with Safari on iPhone, but design itself is resolution dependent. You need physical limits to implement good design. This is where iPhone's implementation makes the most sense.

On the iPhone, you use a CSS3 media query in which you specify the maximum resolution for the device, and you add a few META tags to your HTML. I don't believe this meshes well with Web Standards, but makes the most sense design-wise. The iPhone isn't "handheld" or "screen" or "print." It's got a maximum width of 480 pixels in landscape mode. We have 480 pixels of width to design with. Now we have a physical limitation. Now we can truly design effectively. Now there are no standards and we are open to conflicts between different devices and software platforms— precisely the situation Web Standards have sought to avoid. However, this makes the most sense for design.

There are too many levels of "hand held" or "mobile" devices to realistically mesh them together, and yet they aren't truly "screen" or "desktop" devices. They can't handle reams of JavaScript and dozens of images, but they can handle some. The W3C's media types have gotten us started. Now that we are seeing how the mobile web is developing, it's time to look back and choose a different metric on which to decide how to display content. Pixels count. The W3C has tried hard to make them count less, but in the end that's what really counts. We need a standard on how to decide which resolutions get which styles and functionality. I still don't want to write multiple versions of HTML on one Web site for God knows how many different types of devices when one version should suffice.

I've only just started down the path of designing and coding for the "mobile web," so I've got oodles more questions than answers.

Additional Resources

Wednesday, October 3, 2007

New Design Is Live!

Well, the headline pretty much says it all. I've got the new design done.

"Wait a tick!" you say. "It doesn't work in Internet Explorer. It looks like rubbish."

Yes. The design doesn't work in Internet Explorer. Truth be told, it won't ever work in Internet Explorer. That browser is old and outdated. I decided to not support old buggy browsers at all. This design uses CSS 3 styles, and some "real" web browsers don't support that yet (c'mon Opera), but that's only for rounded corners on the boxes in the layout.

So, this is my new design. This is my playground. I don't want to play with Internet Explorer. Too bad so sad.

Download a real web browser:

Monday, October 1, 2007

Pardon My Dust

I'm finally putting a real design on this. In the mean time, I call this design "An Ode To Netscape 2." Enjoy. :)

iPhoneUI: Standards-based iPhone CSS styles

Download free, quick and easy CSS styles for creating iPhone applications using Apple's design specifications.

A coworker of mine created his first iPhone web app, iPhone Lease Calculator, and needed a visual skin on it. I set out to create simple styles so you can easily implement web apps using Apple's recommended iPhone user interface. These styles are used for laying out forms, and limited text styles.

There are two main styles, the Edge to Edge design, and the Rounded Corners design.

Edge-To-Edge Design: Forms
<dl class="edgeToEdge formFields">
  <dt>
    <label for="txt_name">Name:</label>
  </dt>
  
  <dd>
    <input type="text" name="full_name" value="">
  </dd>
</dl>

Pretty simple. The edge to edge design is recommended for most forms. It's not too attractive looking and provides maximum readability. I decided that a definition list provides the greatest flexibility in design and the most meaning for the markup.

A gray border is placed below each row. The last row should not have a border, so add the "last" className to the last DD tag in the definition list.

<dl class="edgeToEdge formFields">
  <dt>
    <label for="txt_name">Name:</label>
  </dt>
  
  <dd class="last">
    <input type="text" name="full_name" value="">
  </dd>
</dl>
Rounded Corners: Forms
<dl class="roundedRect formFields">
  <dt>
    <label for="txt_name">Name:</label>
  </dt>
  
  <dd>
    <input type="text" name="full_name" value="">
  </dd>
</dl>

So really not much different here. The rounded corners design uses the same basic markup with the exception of a different className. This will get the white, rounded corner box. The only thing that's missing is a label:

<h1 class="roundedRectHead">
  Enter your name
</h1>

<dl class="roundedRect formFields">
  ...
</dl>

While that may work for most uses, FIELDSETs and LEGENDs are also very useful on forms.

<fieldset class="roundedRect">
  
  <legend>Enter your name</legend>
  
  <dl class="formFields">
    ...
  </dl>
  
</fieldset>

This requires just a slightly different markup structure. The "roundedRect" className is moved to the FIELDSET tag, and the DL tag only has the "formFields" className.

At some point you'll want buttons on your form.

Edge-To-Edge: Buttons
<p class="edgeToEdge formButtons">
  <input type="submit" value="Submit Name">
</p>
Rounded Corners: Buttons
<p class="roundedRect formButtons">
  <input type="submit" value="Submit Name">
</p>
Showing Form Submission Results

Once the user has submitted the form, you'll want to display some sort of message or result.

<h1 class="roundedRectHead">
  Your name is:
</h1>
<p class="roundedRect formResults">
  John Doe
</p>

These styles aren't anything special and just seek to implement Apple's iPhone user interface on a web application viewed in iPhone's Safari web browser. This will integrate your Web application seamlessly into the iPhone's inherent user interface. And as usual, if I change anything, I'll be sure to post it.