Chapter 14

Scripting Forms

WHAT’S IN THIS CHAPTER?

  • Understanding form basics
  • Text box validation and interaction
  • Working with other form controls

One of the original uses of JavaScript was to offload some form-processing responsibilities onto the browser instead of relying on the server to do it all. Although the Web and JavaScript have evolved since that time, web forms remain more or less unchanged. The failure of web forms to provide out-of-the-box solutions for common problems led developers to use JavaScript not just for form validation but also to augment the default behavior of standard form controls.

FORM BASICS

Web forms are represented by the <form> element in HTML and by the HTMLFormElement type in JavaScript. The HTMLFormElement type inherits from HTMLElement and therefore has all of the same default properties as other HTML elements. However, HTMLFormElement also has the following additional properties and methods:

  • acceptCharset — The character sets that the server can process; equivalent to the HTML accept-charset attribute.
  • action — The URL to send the request to; equivalent to the HTML action attribute.
  • elements — An HTMLCollection of all controls in the form.
  • enctype — The encoding type of the request; equivalent to the HTML enctype attribute.
  • length — The number of controls in the form.
  • method — The type of HTTP request to send, typically "get" or "post"; equivalent to the HTML method attribute.
  • name — The name of the form; equivalent to the HTML name attribute.
  • reset() — Resets all form fields to their default values.
  • submit() — Submits the form.
  • target — The name of the window to use for sending the request and receiving the response; equivalent to the HTML target attribute.

References to <form> elements can be retrieved in a number of different ways. The most common way is to treat them as any other elements and assign the id attribute, allowing the use of getElementById(), as in the following example:

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

All forms on the page can also be retrieved from document.forms collection. Each form can be accessed in this collection by numeric index and by name, as shown in the following examples:

var firstForm = document.forms[0];      //get the first form in the page
var myForm = document.forms["form2"];   //get the form with a name of "form2"

Older browsers, or those with strict backwards compatibility, also add each form with a name as a property of the document object. For instance, a form named “form2” could be accessed via document.form2. This approach is not recommended, because it is error-prone and may be removed from browsers in the future.

Note that forms can have both an id and a name and that these values need not be the same.

Submitting Forms

Forms are submitted when a user interacts with a submit button or an image button. Submit buttons are defined using either the <input> element or the <button> element with a type attribute of "submit", and image buttons are defined using the <input> element with a type attribute of "image". All of the following, when clicked, will submit a form in which the button resides:

<!-- generic submit button -->
<input type="submit" value="Submit Form">
            
<!-- custom submit button -->
<button type="submit">Submit Form</button>
            
<!-- image button -->
<input type="image" src="graphic.gif">

If any one of these types of buttons is within a form that has a submit button, pressing Enter on the keyboard while a form control has focus will also submit the form. (The one exception is a textarea, within which Enter creates a new line of text.) Note that forms without a submit button will not be submitted when Enter is pressed.

When a form is submitted in this manner, the submit event fires right before the request is sent to the server. This gives you the opportunity to validate the form data and decide whether to allow the form submission to occur. Preventing the event’s default behavior cancels the form submission. For example, the following prevents a form from being submitted:

var form = document.getElementById("myForm");
EventUtil.addHandler(form, "submit", function(event){
            
    //get event object
    event = EventUtil.getEvent(event);
            
    //prevent form submission
    EventUtil.preventDefault(event);
});

This code uses the EventUtil object from the previous chapter to provide cross-browser event handling. The preventDefault() method stops the form from being submitted. Typically, this functionality is used when data in the form is invalid and should not be sent to the server.

It’s possible to submit a form programmatically by calling the submit() method from JavaScript. This method can be called at any time to submit a form and does not require a submit button to be present in the form to function appropriately. Here’s an example:

var form = document.getElementById("myForm");
            
//submit the form
form.submit();

When a form is submitted via submit(), the submit event does not fire, so be sure to do data validation before calling the method.

One of the biggest issues with form submission is the possibility of submitting the form twice. Users sometimes get impatient when it seems like nothing is happening and may click a submit button multiple times. The results can be annoying (because the server processes duplicate requests) or damaging (if the user is attempting a purchase and ends up placing multiple orders). There are essentially two ways to solve this problem: disable the submit button once the form is submitted, or use the onsubmit event handler to cancel any further form submissions.

Resetting Forms

Forms are reset when the user clicks a reset button. Reset buttons are created using either the <input> or the <button> element with a type attribute of "reset", as in these examples:

<!-- generic reset button -->
<input type="reset" value="Reset Form">
            
<!-- custom reset button -->
<button type="reset">Reset Form</button>

Either of these buttons will reset a form. When a form is reset, all of the form fields are set back to the values they had when the page was first rendered. If a field was originally blank, it becomes blank again, whereas a field with a default value reverts to that value.

When a form is reset by the user clicking a reset button, the reset event fires. This event gives you the opportunity to cancel the reset if necessary. For example, the following prevents a form from being reset:

var form = document.getElementById("myForm");
EventUtil.addHandler(form, "reset", function(event){
            
    //get event object
    event = EventUtil.getEvent(event);
            
    //prevent form reset
    EventUtil.preventDefault(event);
});

As with form submission, resetting a form can be accomplished via JavaScript using the reset() method, as in this example:

var form = document.getElementById("myForm");
            
//reset the form
form.reset();

Unlike the submit() method’s functionality, reset() fires the reset event the same as if a reset button were clicked.

image

Form resetting is typically a frowned-upon approach to web form design. It’s often disorienting to the user and, when triggered accidentally, can be quite frustrating. There’s almost never a need to reset a form. It’s often enough to provide a cancel button that takes the user back to the previous page rather than explicitly to revert all values in the form.

Form Fields

Form elements can be accessed in the same ways as any other elements on the page using native DOM methods. Additionally, all form elements are parts of an elements collection that is a property of each form. The elements collection is an ordered list of references to all form fields in the form and includes all <input>, <textarea>, <button>, <select>, and <fieldset> elements. Each form field appears in the elements collection in the order in which it appears in the markup, indexed by both position and name. Here are some examples:

var form = document.getElementById("form1");
            
//get the first field in the form
var field1 = form.elements[0];
            
//get the field named "textbox1"
var field2 = form.elements["textbox1"];
            
//get the number of fields
var fieldCount = form.elements.length;

If a name is in use by multiple form controls, as is the case with radio buttons, then an HTMLCollection is returned containing all of the elements with the name. For example, consider the following HTML snippet:

image
<form method="post" id="myForm">
    <ul>
        <li><input type="radio" name="color" value="red">Red</li>
        <li><input type="radio" name="color" value="green">Green</li>
        <li><input type="radio" name="color" value="blue">Blue</li>
    </ul> 
</form>

FormFieldsExample01.htm

The form in this HTML has three radio controls that have "color" as their name, which ties the fields together. When accessing elements["color"], a NodeList is returned, containing all three elements; when accessing elements[0], however, only the first element is returned. Consider this example:

var form = document.getElementById("myForm");
            
var colorFields = form.elements["color"];
alert(colorFields.length);  //3
            
var firstColorField = colorFields[0];
var firstFormField = form.elements[0];
alert(firstColorField === firstFormField);   //true

FormFieldsExample01.htm

This code shows that the first form field, accessed via form.elements[0], is the same as the first element contained in form.elements["color"].

image

It’s possible to access elements as properties of a form as well, such as form[0] to get the first form field and form["color"] to get a named field. These properties always return the same thing as their equivalent in the elements collection. This approach is provided for backwards compatibility with older browsers and should be avoided when possible in favor of using elements.

Common Form-Field Properties

With the exception of the <fieldset> element, all form fields share a common set of properties. Since the <input> type represents many form fields, some properties are used only with certain field types, whereas others are used regardless of the field type. The common form-field properties and methods are as follows:

  • disabled — A Boolean indicating if the field is disabled.
  • form — A pointer to the form that the field belongs to. This property is read only.
  • name — The name of the field.
  • readOnly — A Boolean indicating if the field is read only.
  • tabIndex — Indicates the tab order for the field.
  • type — The type of the field: "checkbox", "radio", and so on.
  • value — The value of the field that will be submitted to the server. For file-input fields, this property is read only and simply contains the file’s path on the computer.

With the exception of the form property, JavaScript can change all other properties dynamically. Consider this example:

var form = document.getElementById("myForm");
var field = form.elements[0];
            
//change the value
field.value = "Another value";
            
//check the value of form
alert(field.form === form);   //true
            
//set focus to the field
field.focus();
            
//disable the field
field.disabled = true;
            
//change the type of field (not recommended, but possible for <input>)
field.type = "checkbox";

The ability to change form-field properties dynamically allows you to change the form at any time and in almost any way. For example, a common problem with web forms is users’ tendency to click the submit button twice. This is a major problem when credit-card orders are involved, because it may result in duplicate charges. A very common solution to this problem is to disable the submit button once it’s been clicked, which is possible by listening for the submit event and disabling the submit button when it occurs. The following code accomplishes this:

image
//Code to prevent multiple form submissions
EventUtil.addHandler(form, "submit", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
            
    //get the submit button
    var btn = target.elements["submit-btn"];
            
    //disable it
    btn.disabled = true;
    
});

FormFieldsExample02.htm

This code attaches an event handler on the form for the submit event. When the event fires, the submit button is retrieved and its disabled property is set to true. Note that you cannot attach an onclick event handler to the submit button to do this because of a timing issue across browsers: some browsers fire the click event before the form’s submit event, some after. For browsers that fire click first, the button will be disabled before the submission occurs, meaning that the form will never be submitted. Therefore it’s better to disable the submit button using the submit event. This approach won’t work if you are submitting the form without using a submit button, because, as stated before, the submit event is fired only by a submit button.

The type property exists for all form fields except <fieldset>. For <input> elements, this value is equal to the HTML type attribute. For other elements, the value of type is set as described in the following table.

DESCRIPTION SAMPLE HTML VALUE OF TYPE
Single-select list <select>...</select> "select-one"
Multi-select list <select multiple>...</select> "select-multiple"
Custom button <button>...</button> "submit"
Custom nonsubmit button <button type="button">...</button> "button"
Custom reset button <button type="reset">...</button> "reset"
Custom submit button <button type="submit">...</button> "submit"

For <input> and <button> elements, the type property can be changed dynamically, whereas the <select> element’s type property is read only.

Common Form-Field Methods

Each form field has two methods in common: focus() and blur(). The focus() method sets the browser’s focus to the form field, meaning that the field becomes active and will respond to keyboard events. For example, a text box that receives focus displays its caret and is ready to accept input. The focus() method is most often employed to call the user’s attention to some part of the page. It’s quite common, for instance, to have the focus moved to the first field in a form when the page is loaded. This can be accomplished by listening for the load event and then calling focus() on the first field, as in the following example:

EventUtil.addHandler(window, "load", function(event){
    document.forms[0].elements[0].focus();
});

Note that this code will cause an error if the first form field is an <input> element with a type of "hidden" or if the field is being hidden using the display or visibility CSS property.

HTML5 introduces an autofocus attribute for form fields that causes supporting browsers to automatically set the focus to that element without the use of JavaScript. For example:

<input type="text" autofocus>

In order for the previous code to work correctly with autofocus, you must first detect if it has been set and, if so, not call focus():

image
EventUtil.addHandler(window, "load", function(event){
    var element = document.forms[0].elements[0];
    
    if (element.autofocus !== true){
        element.focus(); console.log("JS focus");
    }
});

FocusExample01.htm

Because autofocus is a Boolean attribute, the value of the autofocus property will be true in supporting browsers. (It will be the empty string in browsers without support.) So this code calls focus() only if the autofocus property is not equal to true, ensuring forwards compatibility. The autofocus property is supported in Firefox 4+, Safari 5+, Chrome, and Opera 9.6+.

image

By default, only form elements can have focus set to them. It’s possible to allow any element to have focus by setting its tabIndex property to -1 and then calling focus(). The only browser that doesn’t support this technique is Opera.

The opposite of focus() is blur(), which removes focus from the element. When blur() is called, focus isn’t moved to any element in particular; it’s just removed from the field on which it was called. This method was used early in web development to create read-only fields before the readonly attribute was introduced. There’s rarely a need to call blur(), but it’s available if necessary. Here’s an example:

document.forms[0].elements[0].blur();

Common Form-Field Events

All form fields support the following three events in addition to mouse, keyboard, mutation, and HTML events:

  • blur — Fires when the field loses focus.
  • change — Fires when the field loses focus and the value has changed for <input> and <textarea> elements; also fires when the selected option changes for <select> elements.
  • focus — Fires when the field gets focus.

Both the blur and the focus events fire because of users manually changing the field’s focus, as well as by calling the blur() and focus() methods, respectively. These two events work the same way for all form fields. The change event, however, fires at different times for different controls. For <input> and <textarea> elements, the change event fires when the field loses focus and the value has changed since the time the control got focus. For <select> elements, however, the change event fires whenever the user changes the selected option; the control need not lose focus for change to fire.

The focus and blur events are typically used to change the user interface in some way, to provide either visual cues or additional functionality (such as showing a drop-down menu of options for a text box). The change event is typically used to validate data that was entered into a field. For example, consider a text box that expects only numbers to be entered. The focus event may be used to change the background color to more clearly indicate that the field has focus, the blur event can be used to remove that background color, and the change event can change the background color to red if nonnumeric characters are entered. The following code accomplishes this:

image
var textbox = document.forms[0].elements[0];
            
EventUtil.addHandler(textbox, "focus", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    
    if (target.style.backgroundColor != "red"){
        target.style.backgroundColor = "yellow";
    }
});
            
EventUtil.addHandler(textbox, "blur", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    
    if (/[^d]/.test(target.value)){
        target.style.backgroundColor = "red";
    } else {
        target.style.backgroundColor = "";
    }
});
            
EventUtil.addHandler(textbox, "change", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    
    if (/[^d]/.test(target.value)){
        target.style.backgroundColor = "red";
    } else {
        target.style.backgroundColor = "";
    }
});

FormFieldEventsExample01.htm

The onfocus event handler simply changes the background color of the text box to yellow, more clearly indicating that it’s the active field. The onblur and onchange event handlers turn the background color red if any nonnumeric character is found. To test for a nonnumeric character, use a simple regular expression against the text box’s value. This functionality has to be in both the onblur and onchange event handlers to ensure that the behavior remains consistent regardless of text box changes.

image

The relationship between the blur and the change events is not strictly defined. In some browsers, the blur event fires before change; in others, it’s the opposite. You can’t depend on the order in which these events fire, so use care whenever they are required.

SCRIPTING TEXT BOXES

There are two ways to represent text boxes in HTML: a single-line version using the <input> element and a multiline version using <textarea>. These two controls are very similar and behave in similar ways most of the time. There are, however, some important differences.

By default, the <input> element displays a text box, even when the type attribute is omitted (the default value is "text"). The size attribute can then be used to specify how wide the text box should be in terms of visible characters. The value attribute specifies the initial value of the text box, and the maxlength attribute specifies the maximum number of characters allowed in the text box. So to create a text box that can display 25 characters at a time but has a maximum length of 50, you can use the following code:

<input type="text" size="25" maxlength="50" value="initial value">

The <textarea> element always renders a multiline text box. To specify how large the text box should be, you can use the rows attribute, which specifies the height of the text box in number of characters, and the cols attribute, which specifies the width in number of characters, similar to size for an <input> element. Unlike <input>, the initial value of a <textarea> must be enclosed between <textarea> and </textarea>, as shown here:

<textarea rows="25" cols="5">initial value</textarea>

Also unlike the <input> element, a <textarea> cannot specify the maximum number of characters allowed using HTML.

Despite the differences in markup, both types of text boxes store their contents in the value property. The value can be used to read the text box value and to set the text box value, as in this example:

var textbox = document.forms[0].elements["textbox1"];
alert(textbox.value);
            
textbox.value = "Some new value";

It’s recommended to use the value property to read or write text box values rather than to use standard DOM methods. For instance, don’t use setAttribute() to set the value attribute on an <input> element, and don’t try to modify the first child node of a <textarea> element. Changes to the value property aren’t always reflected in the DOM either, so it’s best to avoid using DOM methods when dealing with text box values.

Text Selection

Both types of text boxes support a method called select(), which selects all of the text in a text box. Most browsers automatically set focus to the text box when the select() method is called (Opera does not). The method accepts no arguments and can be called at any time. Here’s an example:

var textbox = document.forms[0].elements["textbox1"];
textbox.select();

It’s quite common to select all of the text in a text box when it gets focus, especially if the text box has a default value. The thinking is that it makes life easier for users when they don’t have to delete text separately. This pattern is accomplished with the following code:

image
EventUtil.addHandler(textbox, "focus", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    
    target.select();
});

TextboxSelectExample01.htm

With this code applied to a text box, all of the text will be selected as soon as the text box gets focus. This can greatly aid the usability of forms.

The select Event

To accompany the select() method, there is a select event. The select event fires when text is selected in the text box. Exactly when the event fires differs from browser to browser. In Internet Explorer 9+, Opera, Firefox, Chrome, and Safari, the select event fires once the user has finished selecting text, whereas in Internet Explorer 8 and earlier it fires as soon as one letter is selected. The select event also fires when the select() method is called. Here’s a simple example:

image
var textbox = document.forms[0].elements["textbox1"];
EventUtil.addHandler(textbox, "select", function(event){
    var     alert("Text selected: " + textbox.value);
});

SelectEventExample01.htm

Retrieving Selected Text

Although useful for understanding when text is selected, the select event provides no information about what text has been selected. HTML5 solved this issue by introducing some extensions to allow for better retrieval of selected text. The specification approach adds two properties to text boxes: selectionStart and selectionEnd. These properties contain zero-based numbers indicating the text-selection boundaries (the offset of the beginning of text selection and the offset of end of text selection, respectively). So, to get the selected text in a text box, you can use the following code:

function getSelectedText(textbox){
    return textbox.value.substring(textbox.selectionStart, textbox.selectionEnd);
}

Since the substring() method works on string offsets, the values from selectionStart and selectionEnd can be passed in directly to retrieve the selected text.

This solution works for Internet Explorer 9+, Firefox, Safari, Chrome, and Opera. Internet Explorer 8 and earlier don’t support these properties, so a different approach is necessary.

Older versions of Internet Explorer have a document.selection object that contains text-selection information for the entire document, which means you can’t be sure where the selected text is on the page. When used in conjunction with the select event, however, you can be assured that the selection is inside the text box that fired the event. To get the selected text, you must first create a range (discussed in Chapter 12) and then extract the text from it, as in the following:

function getSelectedText(textbox){
    if (typeof textbox.selectionStart == "number"){
        return textbox.value.substring(textbox.selectionStart, 
                                       textbox.selectionEnd);                
    } else if (document.selection){
        return document.selection.createRange().text;
    }
}

TextboxGetSelectedTextExample01.htm

This function has been modified to determine whether to use the Internet Explorer approach to selected text. Note that document.selection doesn’t need the textbox argument at all.

Partial Text Selection

HTML5 also specifies an addition to aid in partially selecting text in a text box. The setSelectionRange() method, originally implemented by Firefox, is now available on all text boxes in addition to the select() method. This method takes two arguments: the index of the first character to select and the index at which to stop the selection (the same as the string’s substring() method). Here are some examples:

textbox.value = "Hello world!"
            
//select all text
textbox.setSelectionRange(0, textbox.value.length);    //"Hello world!"
            
//select first three characters
textbox.setSelectionRange(0, 3);    //"Hel"
            
//select characters 4 through 6
textbox.setSelectionRange(4, 7);    //"o w"

To see the selection, you must set focus to the text box either immediately before or after a call to setSelectionRange(). This approach works for Internet Explorer 9, Firefox, Safari, Chrome, and Opera.

Internet Explorer 8 and earlier allow partial text selection through the use of ranges (discussed in Chapter 12). To select part of the text in a text box, you must first create a range and place it in the correct position by using the createTextRange() method that Internet Explorer provides on text boxes and using the moveStart() and moveEnd() range methods to move the range into position. Before calling these methods, however, you need to collapse the range to the start of the text box using collapse(). After that, moveStart() moves both the starting and the end points of the range to the same position. You can then pass in the total number of characters to select as the argument to moveEnd(). The last step is to use the range’s select() method to select the text, as shown in these examples:

textbox.value = "Hello world!";
            
var range = textbox.createTextRange();
            
//select all text
range.collapse(true);
range.moveStart("character", 0);
range.moveEnd("character", textbox.value.length);   //"Hello world!"
range.select();
            
//select first three characters
range.collapse(true);
range.moveStart("character", 0);
range.moveEnd("character", 3);
range.select();                        //"Hel"
            
//select characters 4 through 6
range.collapse(true);
range.moveStart("character", 4);
range.moveEnd("character", 3);
range.select();                        //"o w"

As with the other browsers, the text box must have focus in order for the selection to be visible.

These two techniques can be combined into a single function for cross-browser usage, as in the following example:

image
function selectText(textbox, startIndex, stopIndex){
    if (textbox.setSelectionRange){
        textbox.setSelectionRange(startIndex, stopIndex);
    } else if (textbox.createTextRange){
        var range = textbox.createTextRange();
        range.collapse(true);
        range.moveStart("character", startIndex);
        range.moveEnd("character", stopIndex - startIndex);
        range.select();                    
    }
    textbox.focus();
}

TextboxPartialSelectionExample01.htm

The selectText() function accepts three arguments: the text box to act on, the index at which to begin the selection, and the index before which to end the selection. First, the text box is tested to determine if it has the setSelectionRange() method. If so, that method is used. If setSelectionRange() is not available, then the text box is checked to see if it supports createTextRange(). If createTextRange() is supported, then a range is created to accomplish the text selection. The last step in the method is to set the focus to the text box so that the selection will be visible. The selectText() method can be used as follows:

textbox.value = "Hello world!"
            
//select all text
selectText(textbox, 0, textbox.value.length);    //"Hello world!"
            
//select first three characters
selectText(textbox, 0, 3);    //"Hel"
            
//select characters 4 through 6
selectText(textbox, 4, 7);    //"o w"

Partial text selection is useful for implementing advanced text input boxes such as those that provide autocomplete suggestions.

Input Filtering

It’s common for text boxes to expect a certain type of data or data format. Perhaps the data needs to contain certain characters or must match a particular pattern. Since text boxes don’t offer much in the way of validation by default, JavaScript must be used to accomplish such input filtering. Using a combination of events and other DOM capabilities, you can turn a regular text box into one that understands the data it is dealing with.

Blocking Characters

Certain types of input require that specific characters be present or absent. For example, a text box for the user’s phone number should not allow non-numeric values to be inserted. The keypress event is responsible for inserting characters into a text box. Characters can be blocked by preventing this event’s default behavior. For example, the following code blocks all key presses:

EventUtil.addHandler(textbox, "keypress", function(event){
    event = EventUtil.getEvent(event);
    EventUtil.preventDefault(event);
});

Running this code causes the text box to effectively become read only, because all key presses are blocked. To block only specific characters, you need to inspect the character code for the event and determine the correct response. For example, the following code allows only numbers:

EventUtil.addHandler(textbox, "keypress", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    var charCode = EventUtil.getCharCode(event);
    
    if (!/d/.test(String.fromCharCode(charCode))){
        EventUtil.preventDefault(event);
    }
});

In this example, the character code is retrieved using EventUtil.getCharCode() for cross-browser compatibility. The character code is converted to a string using String.fromCharCode(), and the result is tested against the regular expression /d/, which matches all numeric characters. If that test fails, then the event is blocked using EventUtil.preventDefault(). This ensures that the text box ignores nonnumeric keys.

Even though the keypress event should be fired only when a character key is pressed, some browsers fire it for other keys as well. Firefox and Safari (versions prior to 3.1) fire keypress for keys like up, down, Backspace, and Delete; Safari versions 3.1 and later do not fire keypress events for these keys. This means that simply blocking all characters that aren’t numbers isn’t good enough, because you’ll also be blocking these very useful and necessary keys. Fortunately, you can easily detect when one of these keys is pressed. In Firefox, all noncharacter keys that fire the keypress event have a character code of 0, whereas Safari versions prior to 3 give them all a character code of 8. To generalize the case, you don’t want to block any character codes lower than 10. The function can then be updated as follows:

EventUtil.addHandler(textbox, "keypress", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    var charCode = EventUtil.getCharCode(event);
    
    if (!/d/.test(String.fromCharCode(charCode)) && charCode > 9){
        EventUtil.preventDefault(event);
    }
});

The event handler now behaves appropriately in all browsers, blocking nonnumeric characters but allowing all basic keys that also fire keypress.

There is still one more issue to handle: copying, pasting, and any other functions that involve the Ctrl key. In all browsers but Internet Explorer, the preceding code disallows the shortcut keystrokes of Ctrl+C, Ctrl+V, and any other combinations using the Ctrl key. The last check, therefore, is to make sure the Ctrl key is not pressed, as shown in the following example:

image
EventUtil.addHandler(textbox, "keypress", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    var charCode = EventUtil.getCharCode(event);
    
    if (!/d/.test(String.fromCharCode(charCode)) && charCode > 9 && 
            !event.ctrlKey){
        EventUtil.preventDefault(event);
    }
});

TextboxInputFilteringExample01.htm

This final change ensures that all of the default text box behaviors work. This technique can be customized to allow or disallow any characters in a text box.

Dealing with the Clipboard

Internet Explorer was the first browser to support events related to the clipboard and access to clipboard data from JavaScript. The Internet Explorer implementation became a de facto standard as Safari 2, Chrome, and Firefox 3 implemented similar events and clipboard access (Opera as of version 11 still doesn’t have JavaScript clipboard support), and clipboard events were later added to HTML5. The following six events are related to the clipboard:

  • beforecopy — Fires just before the copy operation takes place.
  • copy — Fires when the copy operation takes place.
  • beforecut — Fires just before the cut operation takes place.
  • cut — Fires when the cut operation takes place.
  • beforepaste — Fires just before the paste operation takes place.
  • paste — Fires when the paste operation takes place.

Since this is a fairly new standard governing clipboard access, the behavior of the events and related objects differs from browser to browser. In Safari, Chrome, and Firefox, the beforecopy, beforecut, and beforepaste events fire only when the context menu for the text box is displayed (in anticipation of a clipboard event), but Internet Explorer fires them in that case and immediately before firing the copy, cut, and paste events. The copy, cut, and paste events all fire when you would expect them to in all browsers, both when the selection is made from a context menu and when using keyboard shortcuts.

The beforecopy, beforecut, and beforepaste events give you the opportunity to change the data being sent to or retrieved from the clipboard before the actual event occurs. However, canceling these events does not cancel the clipboard operation — you must cancel the copy, cut, or paste event to prevent the operation from occurring.

Clipboard data is accessible via the clipboardData object that exists either on the window object (in Internet Explorer) or on the event object (in Firefox 4+, Safari, and Chrome). In Firefox, Safari, and Chrome, the clipboardData object is available only during clipboard events to prevent unauthorized clipboard access; Internet Explorer exposes the clipboardData object all the time. For cross-browser compatibility, it’s best to use this object only during clipboard events.

There are three methods on the clipboardData object: getData(), setData(), and clearData(). The getData() method retrieves string data from the clipboard and accepts a single argument, which is the format for the data to retrieve. Internet Explorer specifies two options: "text" and "URL". Firefox, Safari, and Chrome expect a MIME type but will accept "text" as equivalent to "text/plain".

The setData() method is similar: its first argument is the data type, and its second argument is the text to place on the clipboard. Once again, Internet Explorer supports "text" and "URL" whereas Safari and Chrome expect a MIME type. Unlike getData(), however, Safari and Chrome won’t recognize the "text" type. Only Internet Explorer 8 and earlier allow honors calling setData(); other browsers simply ignore the call. To even out the differences, you can add the following cross-browser methods to EventUtil:

image
var EventUtil = {
            
    //more code here
    
    getClipboardText: function(event){
        var clipboardData =  (event.clipboardData || window.clipboardData);
        return clipboardData.getData("text");
    },
    
    //more code here
    
    setClipboardText: function(event, value){
        if (event.clipboardData){
            return event.clipboardData.setData("text/plain", value);
        } else if (window.clipboardData){
            return window.clipboardData.setData("text", value);
        }
    },
    
    //more code here
            
};

EventUtil.js

The getClipboardText() method is relatively simple. It needs only to identify the location of the clipboardData object and then call getData() with a type of "text". Its companion method, setClipboardText(), is slightly more involved. Once the clipboardData object is located, setData() is called with the appropriate type for each implementation ("text/plain" for Firefox, Safari, and Chrome; "text" for Internet Explorer).

Reading text from the clipboard is helpful when you have a text box that expects only certain characters or a certain format of text. For example, if a text box allows only numbers, then pasted values must also be inspected to ensure that the value is valid. In the paste event, you can determine if the text on the clipboard is invalid and, if so, cancel the default behavior, as shown in the following example:

image
EventUtil.addHandler(textbox, "paste", function(event){
    event = EventUtil.getEvent(event);
    var text = EventUtil.getClipboardText(event);
            
    if (!/^d*$/.test(text)){
        EventUtil.preventDefault(event);
    }
});

TextboxClipboardExample01.htm

This onpaste handler ensures that only numeric values can be pasted into the text box. If the clipboard value doesn’t match the pattern, then the paste is canceled. Firefox, Safari, and Chrome allow access to the getData() method only in an onpaste event handler.

Since not all browsers support clipboard access, it’s often easier to block one or more of the clipboard operations. In browsers that support the copy, cut, and paste events (Internet Explorer, Safari, Chrome, and Firefox 3+), it’s easy to prevent the events’ default behavior. For Opera, you need to block the keystrokes that cause the events and block the context menu from being displayed.

Automatic Tab Forward

JavaScript can be used to increase the usability of form fields in a number of ways. One of the most common is to automatically move the focus to the next field when the current field is complete. This is frequently done when entering data whose appropriate length is already known, such as for telephone numbers. In the United States, telephone numbers are typically split into three parts: the area code, the exchange, and then four more digits. It’s quite common for web pages to represent this as three text boxes, such as the following:

<input type="text" name="tel1" id="txtTel1" maxlength="3">
<input type="text" name="tel2" id="txtTel2" maxlength="3">
<input type="text" name="tel3" id="txtTel3" maxlength="4">

TextboxTabForwardExample01.htm

To aid in usability and speed up the data-entry process, you can automatically move focus to the next element as soon as the maximum number of characters has been entered. So once the user types three characters in the first text box, the focus moves to the second, and once the user types three characters in the second text box, the focus moves to the third. This “tab forward” behavior can be accomplished using the following code:

image
(function(){
            
    function tabForward(event){            
        event = EventUtil.getEvent(event);
        var target = EventUtil.getTarget(event);
        
        if (target.value.length == target.maxLength){
            var form = target.form;
            
            for (var i=0, len=form.elements.length; i < len; i++) {
                if (form.elements[i] == target) {
                    if (form.elements[i+1]){
                        form.elements[i+1].focus();
                    }
                    return;
                }
            }
        }
    }
                
    var textbox1 = document.getElementById("txtTel1");
    var textbox2 = document.getElementById("txtTel2");
    var textbox3 = document.getElementById("txtTel3");
    
    EventUtil.addHandler(textbox1, "keyup", tabForward);        
    EventUtil.addHandler(textbox2, "keyup", tabForward);        
    EventUtil.addHandler(textbox3, "keyup", tabForward);        
        
})();

TextboxTabForwardExample01.htm

The tabForward() function is the key to this functionality. It checks to see if the text box’s maximum length has been reached by comparing the value to the maxlength attribute. If they’re equal (since the browser enforces the maximum, there’s no way it could be more), then the next form element needs to be found by looping through the elements collection until the text box is found and then setting focus to the element in the next position. This function is then assigned as the onkeyup handler for each text box. Since the keyup event fires after a new character has been inserted into the text box, this is the ideal time to check the length of the text box contents. When filling out this simple form, the user will never have to press the Tab key to move between fields and submit the form.

Keep in mind that this code is specific to the markup mentioned previously and doesn’t take into account possible hidden fields.

HTML5 Constraint Validation API

HTML5 introduces the ability for browsers to validate data in forms before submitting to the server. This capability enables basic validation even when JavaScript is unavailable or fails to load. The browser itself handles performing the validation based on rules in the code and then displays appropriate error messages on its own (without needing additional JavaScript). Of course, this functionality works only in browsers that support this part of HTML5, including Firefox 4+, Safari 5+, Chrome, and Opera 10+.

Validation is applied to a form field only under certain conditions. You can use HTML markup to specify constraints on a particular field that will result in the browser automatically performing form validation.

Required Fields

The first condition is when a form field has a required attribute, as in this example:

<input type="text" name="username" required>

Any field marked as required must have a value in order for the form to be submitted. This attribute applies to <input>, <textarea>, and <select> fields (Opera through version 11 doesn’t support required on <select>). You can check to see if a form field is required in JavaScript by using the corresponding required property on the element:

var isUsernameRequired = document.forms[0].elements["username"].required;

You can also test to see if the browser supports the required attribute using this code snippet:

var isRequiredSupported = "required" in document.createElement("input");

This code uses simple feature detection to determine if the property required exists on a newly created <input> element.

Keep in mind that different browsers behave differently when a form field is required. Firefox 4 and Opera 11 prevent the form from submitting and pop up a help box beneath the field, while Safari (as of version 5) and Chrome (as of version 9) do nothing and don’t prevent the form from submitting.

Alternate Input Types

HTML5 specifies several additional values for the type attribute on an <input> element. These type attributes not only provide additional information about the type of data expected but also provide some default validation. The two new input types that are mostly widely supported are "email" and "url", and each comes with a custom validation that the browser applies. For example:

<input type="email" name="email">
<input type="url" name="homepage">

The "email" type ensures that the input text matches the pattern for an e-mail address, while the "url" type ensures that the input text matches the pattern for a URL. Note that the browsers mentioned earlier in this section all have some issues with proper pattern matching. Most notably, the text "-@-" is considered a valid e-mail address. Such issues are still being addressed with browser vendors.

You can detect if a browser supports these new types by creating an element in JavaScript and setting the type property to "email" or "url" and then reading the value back. Older browsers automatically set unknown values back to "text", while supporting browsers echo the correct value back. For example:

var input = document.createElement("input");
input.type = "email";
 
var isEmailSupported = (input.type == "email");

Keep in mind that an empty field is also considered valid unless the required attribute is applied. Also, specifying a special input type doesn’t prevent the user from entering an invalid value; it only applies some default validation.

Numeric Ranges

In addition to "email" and "url", there are several other new input element types defined in HTML5. These are all numeric types that expect some sort of numbers-based input: "number", "range", "datetime", "datetime-local", "date", "month", "week", and "time". These types are not well supported in browsers and as such should be used carefully, if at all. Browser vendors are working toward better cross-compatibility and more logical functionality at this time. Therefore, the information in this section is more forward looking rather than explanatory of existing functionality.

For each of these numeric types, you can specify a min attribute (the smallest possible value), a max attribute (the largest possible value), and a step attribute (the difference between individual steps along the scale from min to max). For instance, to allow only multiples of 5 between 0 and 100, you could use:

<input type="number" min="0" max="100" step="5" name="count">

Depending on the browser, you may or may not see a spin control (up and down buttons) to automatically increment or decrement the browser.

Each of the attributes have corresponding properties on the element that are accessible (and changeable) using JavaScript. Additionally, there are two methods: stepUp() and stepDown(). These methods each accept an optional argument: the number to either subtract or add from the current value. (By default, they increment or decrement by one.) The methods have not yet been implemented by browsers but will be usable as in this example:

input.stepUp();      //increment by one
input.stepUp(5);     //increment by five
input.stepDown();    //decrement by one
input.stepDown(10);  //decrement by ten

Input Patterns

The pattern attribute was introduced for text fields in HTML5. This attribute specifies a regular expression with which the input value must match. For example, to allow only numbers in a text field, the following code applies this constraint:

<input type="text" pattern="d+" name="count">

Note that ^ and $ are assumed at the beginning and end of the pattern, respectively. That means the input must exactly match the pattern from beginning to end.

As with the alternate input types, specifying a pattern does not prevent the user from entering invalid text. The pattern is applied to the value, and the browser then knows if the value is valid or not. You can read the pattern by accessing the pattern property:

var pattern = document.forms[0].elements["count"].pattern;

You can also test to see if the browser supports the pattern attribute using this code snippet:

var isPatternSupported = "pattern" in document.createElement("input");

Checking Validity

You can check if any given field on the form is valid by using the checkValidity() method. This method is provided on all elements and returns true if the field’s value is valid or false if not. Whether or not a field is valid is based on the conditions previously mentioned in this section, so a required field without a value is considered invalid, and a field whose value does not match the pattern attribute is considered invalid. For example:

if (document.forms[0].elements[0].checkValidity()){
    //field is valid, proceed
} else {
    //field is invalid
}

To check if the entire form is valid, you can use the checkValidity() method on the form itself. This method returns true if all form fields are valid and false if even one is not:

if(document.forms[0].checkValidity()){
    //form is valid, proceed
} else {
    //form field is invalid
}

While checkValidity() simply tells you if a field is valid or not, the validity property indicates exactly why the field is valid or invalid. This object has a series of properties that return a Boolean value:

  • customErrortrue if setCustomValidity() was set, false if not.
  • patternMismatchtrue if the value doesn’t match the specified pattern attribute.
  • rangeOverflowtrue if the value is larger than the max value.
  • rangeUnderflowtrue if the value is smaller than the min value.
  • stepMisMatchtrue if the value isn’t correct given the step attribute in combination with min and max.
  • tooLongtrue if the value has more characters than allowed by the maxlength property. Some browsers, such as Firefox 4, automatically constrain the character count, and so this value may always be false.
  • typeMismatchvalue is not in the required format of either "email" or "url".
  • validtrue if every other property is false. Same value that is required by checkValidity().
  • valueMissingtrue if the field is marked as required and there is no value.

Therefore, you may wish to check the validity of a form field using validity to get more specific information, as in the following code:

if (input.validity && !input.validity.valid){
    if (input.validity.valueMissing){
        alert("Please specify a value.")
    } else if (input.validity.typeMismatch){
        alert("Please enter an email address.");
    } else {
        alert("Value is invalid.");
    }
}

Disabling Validation

You can instruct a form not to apply any validation to a form by specifying the novalidate attribute:

<form method="post" action="signup.php" novalidate>
    <!-- form elements here -->
</form>

This value can also be retrieved or set by using the JavaScript property noValidate, which is set to true if the attribute is present and false if the attribute is omitted:

document.forms[0].noValidate = true;   //turn off validation

If there are multiple submit buttons in a form, you can specify that the form not validate when a particular submit button is used by adding the formnovalidate attribute to the button itself:

<form method="post" action="foo.php">
    <!-- form elements here -->
    <input type="submit" value="Regular Submit">
    <input type="submit" formnovalidate name="btnNoValidate" 
           value="Non-validating Submit">
</form>

In this example, the first submit button will cause the form to validate as usual while the second disables validation when submitting. You can also set this property using JavaScript:

//turn off validation
document.forms[0].elements["btnNoValidate"].formNoValidate = true;

SCRIPTING SELECT BOXES

Select boxes are created using the <select> and <option> elements. To allow for easier interaction with the control, the HTMLSelectElement type provides the following properties and methods in addition to those that are available on all form fields:

  • add(newOption, relOption) — Adds a new <option> element to the control before the related option.
  • multiple — A Boolean value indicating if multiple selections are allowed; equivalent to the HTML multiple attribute.
  • options — An HTMLCollection of <option> elements in the control.
  • remove(index) — Removes the option in the given position.
  • selectedIndex — The zero-based index of the selected option or -1 if no options are selected. For select boxes that allow multiple selections, this is always the first option in the selection.
  • size — The number of rows visible in the select box; equivalent to the HTML size attribute.

The type property for a select box is either "select-one" or "select-multiple", depending on the absence or presence of the multiple attribute. The option that is currently selected determines a select box’s value property according to the following rules:

  • If there is no option selected, the value of a select box is an empty string.
  • If an option is selected and it has a value attribute specified, then the select box’s value is the value attribute of the selected option. This is true even if the value attribute is an empty string.
  • If an option is selected and it doesn’t have a value attribute specified, then the select box’s value is the text of the option.
  • If multiple options are selected, then the select box’s value is taken from the first selected option according to the previous two rules.

Consider the following select box:

<select name="location" id="selLocation">
    <option value="Sunnyvale, CA">Sunnyvale</option>
    <option value="Los Angeles, CA">Los Angeles</option>
    <option value="Mountain View, CA">Mountain View</option>
    <option value="">China</option>
    <option>Australia</option>
</select>

If the first option in this select box is selected, the value of the field is "Sunnyvale, CA". If the option with the text "China" is selected, then the field’s value is an empty string because the value attribute is empty. If the last option is selected, then the value is "Australia" because there is no value attribute specified on the <option>.

Each <option> element is represented in the DOM by an HTMLOptionElement object. The HTMLOptionElement type adds the following properties for easier data access:

  • index — The option’s index inside the options collection.
  • label — The option’s label; equivalent to the HTML label attribute.
  • selected — A Boolean value used to indicate if the option is selected. Set this property to true to select an option.
  • text — The option’s text.
  • value — The option’s value (equivalent to the HTML value attribute).

Most of the <option> properties are used for faster access to the option data. Normal DOM functionality can be used to access this information, but it’s quite inefficient, as this example shows:

var selectbox = document.forms[0].elements["location"];
            
//not recommended
var text = selectbox.options[0].firstChild.nodeValue;     //option text
var value = selectbox.options[0].getAttribute("value");   //option value

This code gets the text and value of the first option in the select box using standard DOM techniques. Compare this to using the special option properties:

var selectbox = document.forms[0].elements["location"];
            
//preferred
var text = selectbox.options[0].text;     //option text
var value = selectbox.options[0].value;   //option value

When dealing with options, it’s best to use the option-specific properties because they are well supported across all browsers. The exact interactions of form controls may vary from browser to browser when manipulating DOM nodes. It is not recommended to change the text or values of <option> elements by using standard DOM techniques.

As a final note, there is a difference in the way the change event is used for select boxes. As opposed to other form fields, which fire the change event after the value has changed and the field loses focus, the change event fires on select boxes as soon as an option is selected.

image

There are differences in what the value property returns across browsers. The value property is always equal to the value attribute in all browsers. When the value attribute is not specified, Internet Explorer 8 and earlier versions return an empty string, whereas Internet Explorer 9+, Safari, Firefox, Chrome, and Opera return the same value as text.

Options Selection

For a select box that allows only one option to be selected, the easiest way to access the selected option is by using the select box’s selectedIndex property to retrieve the option, as shown in the following example:

var selectedOption = selectbox.options[selectbox.selectedIndex];

This can be used to display all of the information about the selected option, as in this example:

image
var selectedIndex = selectbox.selectedIndex;
var selectedOption = selectbox.options[selectedIndex];
alert("Selected index: " + selectedIndex + "
Selected text: " + 
      selectedOption.text + "
Selected value: " + selectedOption.value);

SelectboxExample01.htm

Here, an alert is displayed showing the selected index along with the text and value of the selected option.

When used in a select box that allows multiple selections, the selectedIndex property acts as if only one selection was allowed. Setting selectedIndex removes all selections and selects just the single option specified, whereas getting selectedIndex returns only the index of the first option that was selected.

Options can also be selected by getting a reference to the option and setting its selected property to true. For example, the following selects the first option in a select box:

selectbox.options[0].selected = true;

Unlike selectedIndex, setting the option’s selected property does not remove other selections when used in a multiselect select box, allowing you to dynamically select any number of options. If an option’s selected property is changed in a single-select select box, then all other selections are removed. It’s worth noting that setting the selected property to false has no effect in a single-select select box.

The selected property is helpful in determining which options in a select box are selected. To get all of the selected options, you can loop over the options collection and test the selected property. Consider this example:

function getSelectedOptions(selectbox){
    var result = new Array();
    var option = null;
    
    for (var i=0, len=selectbox.options.length; i < len; i++){
        option = selectbox.options[i];
        if (option.selected){
            result.push(option);
        }
    }
    
    return result;            
} 

SelectboxExample03.htm

This function returns an array of options that are selected in a given select box. First an array to contain the results is created. Then a for loop iterates over the options, checking each option’s selected property. If the option is selected, it is added to the result array. The last step is to return the array of selected options. The getSelectedOptions() function can then be used to get information about the selected options, like this:

image
var selectbox = document.getElementById("selLocation");
var selectedOptions = getSelectedOptions(selectbox);
var message = "";
            
for (var i=0, len=selectedOptions.length; i < len; i++){
    message += "Selected index: " + selectedOptions[i].index + 
 "
Selected text: " + selectedOptions[i].text + 
 "
Selected value: " + selectedOptions[i].value + "

";
}
            
alert(message);

SelectboxExample03.htm

In this example, the selected options are retrieved from a select box. A for loop is used to construct a message containing information about all of the selected options, including each option’s index, text, and value. This can be used for select boxes that allow single or multiple selection.

Adding Options

There are several ways to create options dynamically and add them to select boxes using JavaScript. The first way is to the use the DOM as follows:

var newOption = document.createElement("option");
newOption.appendChild(document.createTextNode("Option text"));
newOption.setAttribute("value", "Option value");
            
selectbox.appendChild(newOption);

SelectboxExample04.htm

This code creates a new <option> element, adds some text using a text node, sets its value attribute, and then adds it to a select box. The new option shows up immediately after being created.

New options can also be created using the Option constructor, which is a holdover from pre-DOM browsers. The Option constructor accepts two arguments, the text and the value, though the second argument is optional. Even though this constructor is used to create an instance of Object, DOM-compliant browsers return an <option> element. This means you can still use appendChild() to add the option to the select box. Consider the following:

var newOption = new Option("Option text", "Option value");
selectbox.appendChild(newOption);    //problems in IE <= 8

SelectboxExample04.htm

This approach works as expected in all browsers except Internet Explorer 8 and earlier. Because of a bug, the browser doesn’t correctly set the text of the new option when using this approach.

Another way to add a new option is to use the select box’s add() method. The DOM specifies that this method accepts two arguments: the new option to add and the option before which the new option should be inserted. To add an option at the end of the list, the second argument should be null. The Internet Explorer 8 and earlier implementation of add() is slightly different in that the second argument is optional, and it must be the index of the option before which to insert the new option. DOM-compliant browsers require the second argument, so you can’t use just one argument for a cross-browser approach (Internet Explorer 9 is DOM-compliant). Instead, passing undefined as the second argument ensures that the option is added at the end of the list in all browsers. Here’s an example:

image
var newOption = new Option("Option text", "Option value");
selectbox.add(newOption, undefined);    //best solution

SelectboxExample04.htm

This code works appropriately in all versions of Internet Explorer and DOM-compliant browsers. If you need to insert a new option into a position other than last, you should use the DOM technique and insertBefore().

image

As in HTML, you are not required to assign a value for an option. The Option constructor works with just one argument (the option text).

Removing Options

As with adding options, there are multiple ways to remove options. You can use the DOM removeChild() method and pass in the option to remove, as shown here:

selectbox.removeChild(selectbox.options[0]);   //remove first option 

The second way is to use the select box’s remove() method. This method accepts a single argument, the index of the option to remove, as shown here:

selectbox.remove(0);   //remove first option 

The last way is to simply set the option equal to null. This is also a holdover from pre-DOM browsers. Here’s an example:

selectbox.options[0] = null;     //remove first option

To clear a select box of all options, you need to iterate over the options and remove each one, as in this example:

function clearSelectbox(selectbox){
    for(var i=0, len=selectbox.options.length; i < len; i++){
        selectbox.remove(0);
    }
}

This function simply removes the first option in a select box repeatedly. Since removing the first option automatically moves all of the options up one spot, this removes all options.

Moving and Reordering Options

Before the DOM, moving options from one select box to another was a rather arduous process that involved removing the option from the first select box, creating a new option with the same name and value, and then adding that new option to the second select box. Using DOM methods, it’s possible to literally move an option from the first select box into the second select box by using the appendChild() method. If you pass an element that is already in the document into this method, the element is removed from its parent and put into the position specified. For example, the following code moves the first option from one select box into another select box.

image
var selectbox1 = document.getElementById("selLocations1");
var selectbox2 = document.getElementById("selLocations2");
            
selectbox2.appendChild(selectbox1.options[0]);

SelectboxExample05.htm

Moving options is the same as removing them in that the index property of each option is reset.

Reordering options is very similar, and DOM methods are the best way to accomplish this. To move an option to a particular location in the select box, the insertBefore() method is most appropriate, though the appendChild() method can be used to move any option to the last position. To move an option up one spot in the select box, you can use the following code:

var optionToMove = selectbox.options[1];
selectbox.insertBefore(optionToMove, selectbox.options[optionToMove.index-1]);

SelectboxExample06.htm

In this code, an option is selected to move and then inserted before the option that is in the previous index. The second line of code is generic enough to work with any option in the select box except the first. The following similar code can be used to move an option down one spot:

var optionToMove = selectbox.options[1];
selectbox.insertBefore(optionToMove, selectbox.options[optionToMove.index+2]);

SelectboxExample06.htm

This code works for all options in a select box, including the last one.

image

There is a repainting issue in Internet Explorer 7 that sometimes causes options that are reordered using DOM methods to take a few seconds to display correctly.

FORM SERIALIZATION

With the emergence of Ajax (discussed further in Chapter 21), form serialization has become a common requirement. A form can be serialized in JavaScript using the type property of form fields in conjunction with the name and value properties. Before writing the code, you need to understand how the browser determines what gets sent to the server during a form submission:

  • Field names and values are URL-encoded and delimited using an ampersand.
  • Disabled fields aren’t sent at all.
  • A check box or radio field is sent only if it is checked.
  • Buttons of type "reset" or "button" are never sent.
  • Multiselect fields have an entry for each value selected.
  • When the form is submitted by clicking a submit button, that submit button is sent; otherwise no submit buttons are sent. Any <input> elements with a type of "image" are treated the same as submit buttons.
  • The value of a <select> element is the value attribute of the selected <option> element. If the <option> element doesn’t have a value attribute, then the value is the text of the <option> element.

Form serialization typically doesn’t include any button fields, because the resulting string will most likely be submitted in another way. All of the other rules should be followed. The code to accomplish form serialization is as follows:

image
function serialize(form){        
    var parts = [],
        field = null,
        i,
        len,
        j,
        optLen,
        option,
        optValue;
    
    for (i=0, len=form.elements.length; i < len; i++){
        field = form.elements[i];
    
        switch(field.type){
            case "select-one":
            case "select-multiple":
            
                if (field.name.length){
                    for (j=0, optLen = field.options.length; j < optLen; j++){
                        option = field.options[j];
                        if (option.selected){
                            optValue = "";
                            if (option.hasAttribute){
                                optValue = (option.hasAttribute("value") ? 
                                            option.value : option.text);
                            } else {
                                optValue = (option.attributes["value"].specified ? 
                                            option.value : option.text);
                            }
                            parts.push(encodeURIComponent(field.name) + "=" + 
                                       encodeURIComponent(optValue));
                        }
                    }
                }
                break;
                
            case undefined:     //fieldset
            case "file":        //file input
            case "submit":      //submit button
            case "reset":       //reset button
            case "button":      //custom button
                break;
                
            case "radio":       //radio button
            case "checkbox":    //checkbox
                if (!field.checked){
                    break;
                }
                /* falls through */
                            
            default:
                //don't include form fields without names
                if (field.name.length){
                    parts.push(encodeURIComponent(field.name) + "=" + 
                               encodeURIComponent(field.value));
                }
        }
    }        
    return parts.join("&");
}

FormSerializationExample01.htm

The serialize() function begins by defining an array called parts to hold the parts of the string that will be created. Next, a for loop iterates over each form field, storing it in the field variable. Once a field reference is obtained, its type is checked using a switch statement. The most involved field to serialize is the <select> element, in either single-select or multiselect mode. Serialization is done by looping over all of the options in the control and adding a value if the option is selected. For single-select controls, there will be only one option selected, whereas multiselect controls may have zero or more options selected. The same code can be used for both select types, because the restriction on the number of selections is enforced by the browser. When an option is selected, you need to determine which value to use. If the value attribute is not present, the text should be used instead, although a value attribute with an empty string is completely valid. To check this, you’ll need to use hasAttribute() in DOM-compliant browsers and the attribute’s specified property in Internet Explorer 8 and earlier.

If a <fieldset> element is in the form, it appears in the elements collection but has no type property. So if type is undefined, no serialization is necessary. The same is true for all types of buttons and file input fields. (File input fields contain the content of the file in form submissions; however, these fields can’t be mimicked, so they are typically omitted in serialization.) For radio and check box controls, the checked property is inspected and if it is set to false, the switch statement is exited. If checked is true, then the code continues executing in the default statement, which encodes the name and value of the field and adds it to the parts array. Note that in all cases form fields without names are not included as part of the serialization to mimic browser form submission behavior. The last part of the function uses join() to format the string correctly with ampersands between fields.

The serialize() function outputs the string in query string format, though it can easily be adapted to serialize the form into another format.

RICH TEXT EDITING

One of the most requested features for web applications was the ability to edit rich text on a web page (also called what you see is what you get, or WYSIWYG, editing). Though no specification covers this, a de facto standard has emerged from functionality originally introduced by Internet Explorer and now supported by Opera, Safari, Chrome, and Firefox. The basic technique is to embed an iframe containing a blank HTML file in the page. Through the designMode property, this blank document can be made editable, at which point you’re editing the HTML of the page’s <body> element. The designMode property has two possible values: "off" (the default) and "on". When set to "on", an entire document becomes editable (showing a caret), allowing you to edit text as if you were using a word processor complete with keystrokes for making text bold, italic, and so forth.

A very simple, blank HTML page is used as the source of the iframe. Here’s an example:

<!DOCTYPE html>
<html>
    <head>
        <title>Blank Page for Rich Text Editing</title>
    </head>
    <body>
    </body>
</html>

This page is loaded inside an iframe as any other page would be. To allow it to be edited, you must set designMode to "on", but this can happen only after the document is fully loaded. In the containing page, you’ll need to use the onload event handler to indicate the appropriate time to set designMode, as shown in the following example:

<iframe name="richedit" style="height: 100px; width: 100px" src="blank.htm">
</iframe>
            
<script type="text/javascript">
EventUtil.addHandler(window, "load", function(){
    frames["richedit"].document.designMode = "on";
});
</script>

Once this code is loaded, you’ll see what looks like a text box on the page. The box has the same default styling as any web page, though this can be adjusted by applying CSS to the blank page.

Using contenteditable

Another way to interact with rich text, also first implemented by Internet Explorer, is through the use of a special attribute called contenteditable. The contenteditable attribute can be applied to any element on a page and instantly makes that element editable by the user. This approach has gained favor because it doesn’t require the overhead of an iframe, blank page, and JavaScript. Instead, you can just add the attribute to an element:

<div class="editable" id="richedit" contenteditable></div>

Any text already contained within the element is automatically made editable by the user, making it behave similarly to the <textarea> element. You can also toggle the editing mode on or off by setting the contentEditable property on an element:

var div = document.getElementById("richedit");
richedit.contentEditable = "true";

There are three possible values for contentEditable: "true" to turn on, "false" to turn off, or "inherit" to inherit the setting from a parent (required since elements can be created/destroyed inside of a contenteditable element). The contentEditable attribute is supported in Internet Explorer, Firefox, Chrome, Safari, and Opera. For mobile devices, contenteditable is supported on Safari for iOS 5+ and WebKit for Android 3+.

Interacting with Rich Text

The primary method of interacting with a rich text editor is through the use of document.execCommand(). This method executes named commands on the document and can be used to apply most formatting changes. There are three possible arguments for document.execCommand(): the name of the command to execute, a Boolean value indicating if the browser should provide a user interface for the command, and a value necessary for the command to work (or null if none is necessary). The second argument should always be false for cross-browser compatibility, because Firefox throws an error when true is passed in.

Each browser supports a different set of commands. The most commonly supported commands are listed in the following table.

COMMAND VALUE (THIRD ARGUMENT) DESCRIPTION
backcolor A color string Sets the background color of the document.
bold null Toggles bold text for the text selection.
copy null Executes a clipboard copy on the text selection.
createlink A URL string Turns the current text selection into a link that goes to the given URL.
cut null Executes a clipboard cut on the text selection.
delete null Deletes the currently selected text.
fontname The font name Changes the text selection to use the given font name.
fontsize 1 through 7 Changes the font size for the text selection.
forecolor A color string Changes the text color for the text selection.
formatblock The HTML tag to surround the block with; for example, <h1> Formats the entire text box around the selection with a particular HTML tag.
indent null Indents the text.
inserthorizontalrule null Inserts an <hr> element at the caret location.
insertimage The image URL Inserts an image at the caret location.
insertorderedlist null Inserts an <ol> element at the caret location.
insertparagraph null Inserts a <p> element at the caret location.
insertunorderedlist null Inserts a <ul> element at the caret location.
italic null Toggles italic text for the text selection.
justifycenter null Centers the block of text in which the caret is positioned.
justifyleft null Left-aligns the block of text in which the caret is positioned.
outdent null Outdents the text.
paste null Executes a clipboard paste on the text selection.
removeformat null Removes block formatting from the block in which the caret is positioned. This is the opposite of formatblock.
selectall null Selects all of the text in the document.
underline null Toggles underlined text for the text selection.
unlink null Removes a text link. This is the opposite of createlink.

The clipboard commands are very browser-dependent. Opera doesn’t implement any of the clipboard commands, and Firefox has them disabled by default. (You must change a user preference to enable them.) Safari and Chrome implement cut and copy but not paste. Note that even though these commands aren’t available via document.execCommand(), they still work with the appropriate keyboard shortcuts.

These commands can be used at any time to modify the appearance of the iframe rich text area, as in this example:

image
//toggle bold text in an iframe
frames["richedit"].document.execCommand("bold", false, null);
            
//toggle italic text in an iframe
frames["richedit"].document.execCommand("italic", false, null);
            
//create link to www.wrox.com in an iframe
frames["richedit"].document.execCommand("createlink", false, 
                                        "http://www.wrox.com");
            
//format as first-level heading in an iframe
frames["richedit"].document.execCommand("formatblock", false, "<h1>");

RichTextEditingExample01.htm

You can use the same methods to act on a contenteditable section of the page, just use the document object of the current window instead of referencing the iframe:

//toggle bold text
document.execCommand("bold", false, null);
            
//toggle italic text
document.execCommand("italic", false, null);
            
//create link to www.wrox.com
document.execCommand("createlink", false, 
                                        "http://www.wrox.com");
            
//format as first-level heading
document.execCommand("formatblock", false, "<h1>");

RichTextEditingExample01.htm

Note that even when commands are supported across all browsers, the HTML that the commands produce is often very different. For instance, applying the bold command surrounds text with <strong> in Internet Explorer and Opera, with <b> in Safari and Chrome, and with a <span> in Firefox. You cannot rely on consistency in the HTML produced from a rich text editor, because of both command implementation and the transformations done by innerHTML.

There are some other methods related to commands. The first is queryCommandEnabled(), which determines if a command can be executed given the current text selection or caret position. This method accepts a single argument, the command name to check, and returns true if the command is allowed given the state of the editable area or false if not. Consider this example:

var result = frames["richedit"].document.queryCommandEnabled("bold");

This code returns true if the "bold" command can be executed on the current selection. It’s worth noting that queryCommandEnabled() indicates not if you are allowed to execute the command but only if the current selection is appropriate for use with the command. In Firefox, queryCommandEnabled("cut") returns true even though it isn’t allowed by default.

The queryCommandState() method lets you determine if a given command has been applied to the current text selection. For example, to determine if the text in the current selection is bold, you can use the following:

image
var isBold = frames["richedit"].document.queryCommandState("bold");

RichTextEditingExample01.htm

If the "bold" command was previously applied to the text selection, then this code returns true. This is the method by which full-featured rich text editors are able to update buttons for bold, italic, and so on.

The last method is queryCommandValue(), which is intended to return the value with which a command was executed. (The third argument in execCommand is in the earlier example.) For instance, a range of text that has the "fontsize" command applied with a value of 7 returns "7" from the following:

var fontSize = frames["richedit"].document.queryCommandValue("fontsize");

RichTextEditingExample01.htm

This method can be used to determine how a command was applied to the text selection, allowing you to determine whether the next command is appropriate to be executed.

Rich Text Selections

You can determine the exact selection in a rich text editor by using the getSelection() method of the iframe. This method is available on both the document object and the window object and returns a Selection object representing the currently selected text. Each Selection object has the following properties:

  • anchorNode — The node in which the selection begins.
  • anchorOffset — The number of characters within the anchorNode that are skipped before the selection begins.
  • focusNode — The node in which the selection ends.
  • focusOffset — The number of characters within the focusNode that are included in the selection.
  • isCollapsed — Boolean value indicating if the start and end of the selection are the same.
  • rangeCount — The number of DOM ranges in the selection.

The properties for a Selection don’t contain a lot of useful information. Fortunately, the following methods provide more information and allow manipulation of the selection:

  • addRange(range) — Adds the given DOM range to the selection.
  • collapse(node, offset) — Collapses the selection to the given text offset within the given node.
  • collapseToEnd() — Collapses the selection to its end.
  • collapseToStart() — Collapses the selection to its start.
  • containsNode(node) — Determines if the given node is contained in the selection.
  • deleteFromDocument() — Deletes the selection text from the document. This is the same as execCommand("delete", false, null).
  • extend(node, offset) — Extends the selection by moving the focusNode and focusOffset to the values specified.
  • getRangeAt(index) — Returns the DOM range at the given index in the selection.
  • removeAllRanges() — Removes all DOM ranges from the selection. This effectively removes the selection, because there must be at least one range in a selection.
  • removeRange(range) — Removes the specified DOM range from the selection.
  • selectAllChildren(node) — Clears the selection and then selects all child nodes of the given node.
  • toString() — Returns the text content of the selection.

The methods of a Selection object are extremely powerful and make extensive use of DOM ranges (discussed in Chapter 12) to manage the selection. Access to DOM ranges allows you to modify the contents of the rich text editor in even finer-grain detail than is available using execCommand(), because you can directly manipulate the DOM of the selected text. Consider the following example:

image
var selection = frames["richedit"].getSelection();
            
//get selected text
var selectedText = selection.toString();
            
//get the range representing the selection
var range = selection.getRangeAt(0);
            
//highlight the selected text
var span = frames["richedit"].document.createElement("span");
span.style.backgroundColor = "yellow";
range.surroundContents(span);

RichTextEditingExample01.htm

This code places a yellow highlight around the selected text in a rich text editor. Using the DOM range in the default selection, the surroundContents() method surrounds the selection with a <span> element whose background color is yellow.

The getSelection() method was standardized in HTML5 and is implemented in Internet Explorer 9, Firefox, Safari, Chrome, and Opera 8. Firefox 3.6+ incorrectly returns a string from document.getSelection() because of legacy support issues. You can retrieve a Selection object in Firefox 3.6+ by using the window.getSelection() method instead. Firefox 8 fixed document.getSelection() to return the same value as window.getSelection().

Internet Explorer 8 and earlier versions don’t support DOM ranges, but they do allow interaction with the selected text via the proprietary selection object. The selection object is a property of document, as discussed earlier in this chapter. To get the selected text in a rich text editor, you must first create a text range (discussed in Chapter 12) and then use the text property as follows:

var range = frames["richedit"].document.selection.createRange();
var selectedText = range.text;

Performing HTML manipulations using Internet Explorer text ranges is not as safe as using DOM ranges, but it is possible. To achieve the same highlighting effect as described using DOM ranges, you can use a combination of the htmlText property and the pasteHTML() method:

var range = frames["richedit"].document.selection.createRange();
range.pasteHTML("<span style="background-color:yellow">" + range.htmlText +  
                "</span>");

This code retrieves the HTML of the current selection using htmlText and then surrounds it with a <span> and inserts it back into the selection using pasteHTML().

Rich Text in Forms

Since rich text editing is implemented using an iframe or a contenteditable element instead of a form control, a rich text editor is technically not part of a form. That means the HTML will not be submitted to the server unless you extract the HTML manually and submit it yourself. This is typically done by having a hidden form field that is updated with the HTML from the iframe or the contenteditable element. Just before the form is submitted, the HTML is extracted from the iframe or element and inserted into the hidden field. For example, the following may be done in the form’s onsubmit event handler when using an iframe:

image
EventUtil.addHandler(form, "submit", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
            
    target.elements["comments"].value = frames["richedit"].document.body.innerHTML;
});

RichTextEditingExample01.htm

Here, the HTML is retrieved from the iframe using the innerHTML property of the document’s body and inserted into a form field named "comments". Doing so ensures that the "comments" field is filled in just before the form is submitted. If you are submitting the form manually using the submit() method, take care to perform this operation beforehand. You can perform a similar operation with a contenteditable element:

EventUtil.addHandler(form, "submit", function(event){
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
            
    target.elements["comments"].value = 
        document.getElementById("richedit").innerHTML;
});

SUMMARY

Even though HTML and web applications have changed dramatically since their inception, web forms have remained mostly unchanged. JavaScript can be used to augment existing form fields to provide new functionality and usability enhancements. To aid in this, forms and form fields have properties, methods, and events for JavaScript usage. Here are some of the concepts introduced in this chapter:

  • It’s possible to select all of the text in a text box or just part of the text using a variety of standard and nonstandard methods.
  • All browsers have adopted Firefox’s way of interacting with text selection, making it a true standard.
  • Text boxes can be changed to allow or disallow certain characters by listening for keyboard events and inspecting the characters being inserted.

All browsers except Opera support events for the clipboard, including copy, cut, and paste. Clipboard event implementations across the other browsers vary in the following ways:

  • Internet Explorer, Firefox, Chrome, and Safari allow access to clipboard data from JavaScript, whereas Opera doesn’t allow such access.
  • Even amongst Internet Explorer, Chrome, and Safari, there are differences in implementation.
  • Firefox, Safari, and Chrome allow reading of clipboard data only during the paste event, whereas Internet Explorer has no such restrictions.
  • Firefox, Safari, and Chrome limit the availability of clipboard information to clipboard-related events, whereas Internet Explorer allows access to the data at any time.

Hooking into clipboard events is useful for blocking paste events when the contents of a text box must be limited to certain characters.

Select boxes are also frequently controlled using JavaScript. Thanks to the DOM, manipulating select boxes is much easier than it was previously. Options can be added, removed, moved from one select box to another, or reordered using standard DOM techniques.

Rich text editing is handled by using an iframe containing a blank HTML document. By setting the document’s designMode property to "on", you make the page editable and it acts like a word processor. You can also use an element set as contenteditable. By default, you can toggle font styles such as bold and italic and use clipboard actions. JavaScript can access some of this functionality by using the execCommand() method and can get information about the text selection by using the queryCommandEnabled(), queryCommandState(), and queryCommandValue() methods. Since building a rich text editor in this manner does not create a form field, it’s necessary to copy the HTML from the iframe or contenteditable element into a form field if it is to be submitted to the server.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.26.108