Showing posts with label dom. Show all posts
Showing posts with label dom. Show all posts

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

Friday, September 28, 2007

TextEditor: Free Unobtrusive JavaScript Code Editor Supporting Web Standards

Easily create a JavaScript enhanced code editor with any TEXTAREA that includes auto-indent, tab stops, auto-complete, XHTML compatibility and more. Did I mention it's free?

I've worked with several older web-based content management systems, that store template code in a database. That means writing HTML in a TEXTAREA. Yuck! Many of them have been updated to include a WYSIWYG editor, be it Java or JavaScript-based. As good as they are, I still yearn for code. Pure, unadulterated, geeky, I'm-in-total-control ... code.

Even with a fancy WYSIWYG editor at my disposal, I turn it off. A plain old TEXTAREA will suffice, but I miss the coding assistance of a real text editor like HTML Kit. I want complete control of my code, and I want a smart code editor all on a Web page. So I built one.

Features of the JavaScript TextEditor:
  • Unobtrusive JavaScript. No need to clutter up your HTML with event handlers.
  • Object-oriented JavaScript. Helps keep the global JavaScript namespace clean and tidy, and simplifies interacting with the editor using JavaScript.
  • Create as many code editors as you want. Want more than one code editor on a page? No problem. Have as many as you want.
  • Create HTML tags in upper or lower case. The HTML 4.01 doctype declares HTML tags in upper case even though they aren't case sensitive. You can have it either way.
  • Set tab stops. When you press the TAB key, it inserts enough spaces to get to the next tab stop. Nice for indenting code.
  • Block Indent. Select several lines of code and hit the TAB key to indent all the lines, or hold the Shift key and hit TAB to unindent each line by one tab stop.
  • Auto-Indent. When you press the ENTER or RETURN keys, the cursor and code is indented automatically to where you were on the previous line.
  • Auto-Complete. Greatly speed up your coding by typing an HTML tag name, highlighting it, then holding the Ctrl and Shift keys down and pressing the "<" key. BAM! Automatic opening and closing tags are created, plus commonly used attributes, and applicable child tags.
  • XHTML compatible. The auto complete feature is XHTML aware.
  • Tabify and Detabify. Did someone actually use real TAB characters to indent the code!? *gasp* Functions are included to convert those pesky tabs to tab stops, and then back again. You'll have to provide the button and the onclick, but the function is available.
  • Escape and Unescape HTML. Does just what it says: Converts "&", "<" and ">" characters to HTML entities, and you can change them back again. Of course you'll need to supply your own button and onclick, but the function is there.
  • Convert newlines to <br> and <p>. Select a bunch of text, and a function can convert all newline characters to page break and paragraph tags. You'll need to create the button and onclick.

The JavaScript TextEditor is completely customizable. When creating it, you can pass several options on a case-by-case basis so every editor can be different.

Browser support for version 0.1

It's an early version and works only in Opera and Firefox on Windows. I tested Safari 3.03 on Windows and found a bug . The Control, Shift and Alt keys do not generate keydown, keypress or keyup events on TEXTAREA's, so the auto complete feature doesn't work, but tab stops and auto indent do. I've reported the bug to Apple.

Mac OS support is upcoming. I need to turn off my PC and fire up my Mac. TextEditor might work on Mac OS right now, but I'll have to double check the keyCodes generated by the event object so the Control, Shift and Tab keys are captured properly.

Internet Explorer users? Well. Your browser sucks and doesn't support the standard methods used to manipulate text in the TEXTAREA, but fear not, Internet Explorer support is pending too. But enough yackin', let's see the code:

Source code for the JavaScript TextEditor

Just click on the box below to select it, then copy.

Implementing the TextEditor

Save the JavaScript code above as TextEditor.js and include it in the HEAD of any page you want JavaScript enhanced TEXTAREA's.

<script type="text/javascript"
src="TextEditor.js"></script>

Next, you will need a TEXTAREA:

<textarea name="body" cols="65" rows="20"
id="body_source"></textarea>

Lastly, you'll need a bit of JavaScript in the HEAD of your HTML document to get things rolling:

<script type="text/javascript">
window.onload = function() {
window.CodeEditor = new TextEditor("body_source");
};
</script>

You can then access that instance of the TextEditor with the global JavaScript variable CodeEditor.

alert(CodeEditor.version);
Future development of TextEditor

In stock for the first full release, version 1.0, is support for Mac OS and Linux. Next up for version 2.0 is support for Internet Explorer 5.5 and newer. I will also be writing an add-on class to create a user interface for TextEditor, which will create a semi-WYSIWYG interface. Think the point and click BB Code editors you see in popular forum software, meets Dreamweaver — and all in your Web browser. Since the basic functionality of the code editor is separate from the user interface, you can have just the code editor, or a full-blown point and click code editor. Upcoming features are:

  • Mac OS and Linux browser support
  • Internet Explorer support
  • Point and click user interface to assist coding
  • Full documentation
  • Downloadable JavaScript files: Uncompressed with documentation, uncompressed without documentation, packed, obfuscated, and both packed and obfuscated.
  • Live preview. Will not require AJAX.
Who can use TextEditor

This script is released under the LGPL license and is free for commercial and non-commercial use. This is still in development so check back again for updates. Any updates to this script will be posted on this blog post.