Chapter 10. HTML Form Enhancements from Scratch

Brian Cherne

Introduction

Whether you’re trying to learn more about JavaScript and jQuery or you just want to code the most direct solution for your immediate problem, sometimes writing code from scratch is the best approach. This chapter aims to provide you with simple, generic solutions that will help you get started writing your own code.

It is important to note that while there are great benefits to starting from scratch, some of the more common problems you’ll encounter, such as remaining character count, autoresizing textareas, and form validation (just to name a few), have already been addressed by others. Please see Chapter 11 or visit the jQuery forums and blogs online for more information on how community-critiqued and community-tested plugins can help you. Sometimes it helps to look at someone else’s code to find out what you could do better with yours.

When necessary, I provide some sample HTML under the “Problem” heading of each recipe. This isn’t a philosophical statement—there isn’t anything wrong or problematic with naked HTML. I am, however, trying to reinforce the mind-set that JavaScript should be used for enhancing existing HTML and improving user interaction. JavaScript is, and should be considered, completely separate from your HTML.

Note

In the following recipes I am only showing XHTML code snippets relevant to the problem. Please make sure that your code is a complete XHTML document and that it passes validation.

Also, I am only including $(document).ready(function(){...}) in the recipes where that is part of the discussion. All other solutions assume you will place the JavaScript in the correct location for your code structure—either in the .ready() handler or at the bottom of your file after the XHTML code in question. Please see Chapter 1 for more information.

10.1. Focusing a Text Input on Page Load

Problem

You have a login form on your home page, and you’d like the username text input to be focused on page load.

Solution

Use the jQuery $(selector).focus() method:

// when the HTML DOM is ready
$(document).ready(function(){
    // focus the <input id="username" type="text" ...>
    $('#username').focus();
});

Discussion

Using $(document).ready() should be fast enough. However, in situations like retrieving a huge HTML file over a slow connection, the cursor might focus later than desired—the user could have already entered her username and could be in the process of typing her password when $(document).ready() executes and puts her cursor back in the username text input. How annoying! In this case you could use inline JavaScript after the <input> tag to make focus immediate:

<input name="username" id="username" type="text" />

<script type="text/javascript">
    $('#username').focus();
</script>

Or, if you prefer to keep your code together in the $(document).ready() block, you can check to see whether the text input has any text in it before giving it focus:

// when the HTML DOM is ready
$(document).ready(function(){
    var $inputTxt = $('#username'),
    if( $inputTxt.val() == '' ) {
        // focus the username input by default
        $inputTxt.focus();
    }
});

What will happen when JavaScript is disabled? The user will have to manually click into the text input to start typing.

10.2. Disabling and Enabling Form Elements

Problem

Your order form has fields for both shipping and billing contact information. You’ve decided to be nice and supply the user with a checkbox that indicates the user’s shipping information and billing information are the same. When checked, the billing text fields should be disabled:

<fieldset id="shippingInfo">
    <legend>Shipping Address</legend>

    <label for="shipName">Name</label>
    <input name="shipName" id="shipName" type="text" />

    <label for="shipAddress">Address</label>
    <input name="shipAddress" id="shipAddress" type="text" />
</fieldset>

<fieldset id="billingInfo">
    <legend>Billing Address</legend>

    <label for="sameAsShipping">Same as Shipping</label>
    <input name="sameAsShipping" id="sameAsShipping" type="checkbox" 
value="sameAsShipping" />

    <label for="billName">Name</label>
    <input name="billName" id="billName" type="text" />

    <label for="billAddress">Address</label>
    <input name="billAddress" id="billAddress" type="text" />
</fieldset>

Solution 1

If all you want to do is disable the billing fields, it’s as simple as using the jQuery .attr() and .removeAttr() methods when the change event is triggered:

// find the "sameAsShipping" checkbox and listen for the change event
$('#sameAsShipping').change(function(){

    if( this.checked ){
        // find all text inputs inside billingInfo and disable them
        $('#billingInfo input:text').attr('disabled','disabled'),

    } else {
        // find all text inputs inside billingInfo and enable them
        $('#billingInfo input:text').removeAttr('disabled'),
    }

}).trigger('change'), // close change() then trigger it once

Solution 2

While selecting a checkbox and disabling the form fields might be enough to get the point across to the user, you could go the extra mile and prepopulate the billing text fields with data from shipping information.

The first part of this solution is the same in structure as the solution shown previously. However, in addition to disabling the billing fields, we are also prepopulating them with data from the shipping fields. The following code assumes the shipping and billing <fieldset> elements contain the same number of text inputs and that they are in the same order:

// find the "sameAsShipping" checkbox and listen for the change event
$('#sameAsShipping').change(function(){
    if( this.checked ){
        // find all text inputs inside billingInfo, disable them, then cycle 
through each one
        $('#billingInfo input:text').attr('disabled',
'disabled').each(function(i){

                // find the shipping input that corresponds to this billing input
                var valueFromShippingInput = 
$('#shippingInfo input:text:eq('+i+')').val();
                // set the billing value with the shipping text value
                $(this).val( valueFromShippingInput );

        }); // close each()

    } else {
        // find all text inputs inside billingInfo and enable them
        $('#billingInfo input:text').removeAttr('disabled');
    }
}).trigger('change'), // close change() then trigger it

The second part of this solution updates the billing fields automatically when the user enters information into the shipping fields, but only if the billing fields are otherwise disabled:

// find the shippingInfo text inputs and listen for the keyup and change event
$('#shippingInfo input:text').bind('keyup change',function(){

    // if "sameAsShipping" checkbox is checked
    if ( $('#sameAsShipping:checked').length ){

        // find out what text input this is
        var i = $('#shippingInfo input:text').index( this );
        var valueFromShippingInput = $(this).val();

        $('#billingInfo input:text:eq('+i+')').val( valueFromShippingInput );
    }

}); // close bind()

Discussion

In the preceding solution I’m using the input:text selector to avoid disabling the checkbox itself.

Using .trigger('change') immediately executes the .change() event. This will check the state of the checkbox initially, in case it is checked by default. Also, this protects against Firefox and other browsers that hold on to radio button and checkbox states when the page is refreshed.

What will happen when JavaScript is disabled? You should hide the checkbox by default in CSS. Then use JavaScript to add a class name to a parent element that would override the previous CSS rule. In the following example code I’ve added an extra <div> surrounding the checkbox and label so they can be easily hidden:

<style type="text/css" title="text/css">
 #sameAsShippingWrapper { display:none; }
 .jsEnabled #sameAsShippingWrapper { display:block }
</style>

...

// when the HTML DOM is ready
$(document).ready(function(){
    $('form').addClass('jsEnabled'),
});

...

<form>
 ...
 <div id="sameAsShippingWrapper">
   <label for="sameAsShipping">Same as Shipping</label>
   <input name="sameAsShipping" id="sameAsShipping" type="checkbox" ... />
 </div>
 ....
</form>

As an alternative to hiding the checkbox in CSS and showing it using JavaScript, you could add the checkbox to the DOM using JavaScript. I prefer to keep my HTML, CSS, and JavaScript separate, but sometimes this is the better solution:

var html_label = '<label for="sameAsShipping">Same as Shipping</label>';
var html_input = '<input name="sameAsShipping" id="sameAsShipping" type="checkbox" 
value="sameAsShipping" />';

$( html_label + html_input ).prependTo('#billingInfo").change( ... ).trigger( ... );

10.3. Selecting Radio Buttons Automatically

Problem

You have a series of radio buttons. The last radio button is labeled “Other” and has a text input field associated with it. Naturally you’d want that radio button to be selected if the user has entered text in the Other field:

<p>How did you hear about us?</p>
<ul id="chooseSource">
    <li>
        <input name="source" id="source1" type="radio" value="www" />
        <label for="source1">Website or Blog</label>
    </li>
    <li>
        <input name="source" id="source2" type="radio" value="mag" />
        <label for="source2">Magazine</label>
    </li>
    <li>
        <input name="source" id="source3" type="radio" value="per" />
        <label for="source3">Friend</label>
    </li>
    <li>
        <input name="source" id="source4" type="radio" value="oth" />
        <label for="source4">Other</label>
        <input name="source4txt" id="source4txt" type="text" />
    </li>
</ul>

Solution 1

In the HTML code you’ll notice the radio button, label, and associated text input elements are wrapped in an <li> tag. You don’t necessarily need this structure, but it makes finding the correct radio button much easier—you’re guaranteed there’s only one radio button sibling:

// find any text input in chooseSource list, and listen for blur
$('#chooseSource input:text').blur(function(){

    // if text input has text
    if ( $(this).val() != '' ) {
        // find the radio button sibling and set it be selected
        $(this).siblings('input:radio').attr('checked',true);
    }

});

Solution 2

To take the concept one step further, when the radio button is selected, we can .focus() the text field. It’s important to note that the following code completely replaces the previous solution. Instead of using the .blur() method and then chaining a .each() method, just use the .each() method since that gives us access to all the objects we need:

$('#chooseSource input:text').each(function(){

    // these are both used twice, let's store them to be more efficient
    // the text input
    var $inputTxt = $(this);
    // the associated radio button
    var $radioBtn = $inputTxt.siblings('input:radio'),

    // listen for the blur event on the text input
    $inputTxt.blur(function(){
        // if text input has text
        if ( $inputTxt.val() != '' ) {
            // select radio button
            $radioBtn.attr('checked',true);
        }
    });

    // listen for the change event on the radio button
    $radioBtn.change(function(){
        // if it is checked, focus on text input
        if ( this.checked ) { $inputTxt.focus(); }
    });

}); // close each()

Discussion

The jQuery .sibling() method only returns siblings, not the HTML element you’re attempting to find siblings of. So, the code $(this).siblings('input:radio') could be rewritten $(this).siblings('input') because there is only one other input that is a sibling. I prefer including the :radio selector because it is more explicit and creates self-commenting code.

It would have been very easy to target the Other text input directly using $('#source5txt').focus(...) and have it directly target the radio button using its id attribute. While that’s a perfectly functional approach, the code as shown previously is more flexible. What if someone decided to change the id of the Other radio button? What if each radio button had a text input? The abstract solution handles these cases without additional work.

Why use .blur() instead of .focus() on the text input? While .focus() would be more immediate for selecting the associated radio button, if the user were simply tabbing through the form elements, .focus() would accidentally select the radio button. Using .blur() and then checking for a value avoids this problem.

What will happen when JavaScript is disabled? The user will have to manually click into the text input to start typing and manually select the radio button. You are left to decide how to validate and process submitted data should the user enter text and select a different radio button.

10.4. (De)selecting All Checkboxes Using Dedicated Links

Problem

You need to select all checkboxes and deselect all checkboxes using dedicated Select All and Deselect All links:

<fieldset>

    <legend>Reasons to be happy</legend>

    <a class="selectAll" href="#">Select All</a>
    <a class="deselectAll" href="#">Deselect All</a>

    <input name="reasons" id="iwokeup" type="checkbox" value="iwokeup" />
    <label for="iwokeup">I woke up</label>

    <input name="reasons" id="health" type="checkbox" value="health" />
    <label for="health">My health</label>

    <input name="reasons" id="family" type="checkbox" value="family" />
    <label for="family">My family</label>

    <input name="reasons" id="sunshine" type="checkbox" value="sunshine" />
    <label for="sunshine">The sun is shining</label>

</fieldset>

Solution

Target the Select All and Deselect All links directly using their class attributes. Then attach the appropriate .click() handler:

// find the "Select All" link in a fieldset and list for the click event
$('fieldset .selectAll').click(function(event){
    event.preventDefault();
    // find all the checkboxes and select them
    $(this).siblings('input:checkbox').attr('checked','checked'),
});

// find the "Deselect All" link in a fieldset and list for the click event
$('fieldset .deselectAll').click(function(event){
    event.preventDefault();
    // find all the checkboxes and deselect them
    $(this).siblings('input:checkbox').removeAttr('checked'),
});

Discussion

If you are interested in activating and deactivating the dedicated links, you should see Recipe 10.5 in this chapter. In that solution, the individual checkboxes update the toggle state, and you will need this logic to activate and deactivate the dedicated links appropriately.

What will happen when JavaScript is disabled? You should hide the links by default in CSS. Then use JavaScript to add a class name to a parent element that will override the previous CSS rule:

<style type="text/css" title="text/css">
 .selectAll, .deselectAll { display:none; }
 .jsEnabled .selectAll, .jsEnabled .deselectAll { display:inline; }
</style>

...

// when the HTML DOM is ready
$(document).ready(function(){
    $('form').addClass('jsEnabled'),
});

10.5. (De)selecting All Checkboxes Using a Single Toggle

Problem

You need to select and deselect all checkboxes using a single toggle, in this case another checkbox. Additionally, that toggle should automatically switch states if some (or all) of the checkboxes are selected individually:

<fieldset>

    <legend>Reasons to be happy</legend>

    <input name="reasons" id="toggleAllReasons" type="checkbox" class="toggle" />
    <label for="toggleAllReasons" class="toggle">Select All</label>

    <input name="reasons" id="iwokeup" type="checkbox" value="iwokeup" />
    <label for="iwokeup">I woke up</label>

    <input name="reasons" id="health" type="checkbox" value="health" />
    <label for="health">My health</label>

    <input name="reasons" id="family" type="checkbox" value="family" />
    <label for="family">My family</label>

    <input name="reasons" id="sunshine" type="checkbox" value="sunshine" />
    <label for="sunshine">The sun is shining</label>

</fieldset>

Solution

Target the toggle directly using its class attribute and the :checkbox selector. Then cycle through each toggle found, determine the associated checkboxes using .siblings(), and attach the change event listeners:

// find the "Select All" toggle in a fieldset, cycle through each one you find
$('fieldset .toggle:checkbox').each(function(){

    // these are used more than once, let's store them to be more efficient
    // the toggle checkbox
    var $toggle = $(this);
    // the other checkboxes
    var $checkboxes = $toggle.siblings('input:checkbox'),

    // listen for the change event on the toggle
    $toggle.change(function(){
        if ( this.checked ) {
            // if checked, select all the checkboxes
            $checkboxes.attr('checked','checked'),
        } else {
            // if not checked, deselect all the checkboxes
            $checkboxes.removeAttr('checked'),
        }
    });

    // listen for the change event on each individual checkbox (not toggle)
    $checkboxes.change(function(){
        if ( this.checked ) {
            // if this is checked and all others are checked, select the toggle
            if ( $checkboxes.length == $checkboxes.filter(':checked').length ) {
                $toggle.attr('checked','checked'),
            }
        } else {
            // if not checked, deselect the toggle
            $toggle.removeAttr('checked'),
        }
    }).eq(0).trigger('change'), // close change() then trigger change on first 
checkbox only
}); // close each()

Discussion

Using .eq(0).trigger('change') immediately executes the .change() event for the first checkbox. This sets the state of the toggle and protects against Firefox and other browsers that hold on to radio and checkbox states when the page is refreshed. The .eq(0) is used to only trigger the first checkbox’s change event. Without .eq(0), the .trigger('change') would be executed for every checkbox, but since they all share the same toggle, you only need to run it once.

What will happen when JavaScript is disabled? You should hide the toggle checkbox and label by default in CSS. Then use JavaScript to add a class name to a parent element that would override the previous CSS rule:

<style type="text/css" title="text/css">
 .toggle { visibility:hidden; }
 .jsEnabled .toggle { visibility:visible; }
</style>

...

// when the HTML DOM is ready
$(document).ready(function(){
    $('form').addClass('jsEnabled'),
});

10.6. Adding and Removing Select Options

Problem

You have a drop-down box for colors and want to add new colors to it, as well as remove options from it.

<label for="colors">Colors</label>
<select id="colors" multiple="multiple">
    <option>Black</options>
    <option>Blue</options>
    <option>Brown</options>
</select>

<button id="remove">Remove Selected Color(s)</button>


<label for="newColorName">New Color Name</label>
<input id="newColorName" type="text" />

<label for="newColorValue">New Color Value</label>
<input id="newColorValue" type="text" />

<button id="add">Add New Color</button>

Solution

To add a new option to the drop-down box, use the .appendTo() method:

// find the "Add New Color" button
$('#add').click(function(event){
    event.preventDefault();

    var optionName = $('#newColorName').val();
    var optionValue = $('#newColorValue').val();

    $('<option/>').attr('value',optionValue).text(optionName).appendTo('#colors'),
});

To remove an option, use the .remove() method:

// find the "Remove Selected Color(s)" button
$('#remove').click(function(event){
    event.preventDefault();

    var $select = $('#colors'),

    $('option:selected',$select).remove();
});

Discussion

I use the .attr() and .text() methods to populate the <option> element:

$('<option/>').attr("value",optionValue).text(optionName).appendTo('#colors'),

However, the same line could be rewritten so that the <option> element is built in one step, without using the methods:

$('<option value="'+optionValue+'">'+optionName+'</option>').appendTo('#colors'),

Concatenating all the <option> data like that would be a fraction of a millisecond faster, but not in any way noticeable by a human. I prefer using the .attr() and .text() methods to populate the <option> element because I think that it is more readable and easier to debug and maintain. With the performance issue being negligible, using one approach or the other is the developer’s preference.

What would happen with JavaScript disabled? You would need to provide a server-side alternative that processes the button clicks, and the user would have to wait for the resulting page reloads.

10.7. Autotabbing Based on Character Count

Problem

You have a form for allowing users to register a product online, and you require the user to enter a serial number printed on the installation discs. This number is 16 digits long and separated across four input fields. Ideally, to speed the user along in their data entry, as each input field is filled up, you’d like to automatically focus the next input field until they’re finished typing the number:

<fieldset class="autotab">
    <legend>Product Serial Number</legend>
    <input type="text" maxlength="4" />
    <input type="text" maxlength="4" />
    <input type="text" maxlength="4" />
    <input type="text" maxlength="4" />
</fieldset>

Solution

Inside <fieldset class="autotab">, find all the <input> elements. Use jQuery’s .bind() method to listen for the keydown and keyup events. We exit the bound function for a handful of keys that we want to ignore, because they aren’t meaningful for automatically tabbing forward or backward. When an <input> element is full, based on the maxlength attribute, we .focus() the next <input> element. Conversely, when using the Backspace key, if an <input> element is made empty, we .focus() the previous <input> element:

$('fieldset.autotab input').bind('keydown keyup',function(event){

    // the keycode for the key evoking the event
    var keyCode = event.which;

    // we want to ignore the following keys:
    // 9 Tab, 16 Shift, 17 Ctrl, 18 Alt, 19 Pause Break, 20 Caps Lock
    // 27 Esc, 33 Page Up, 34 Page Down, 35 End, 36 Home
    // 37 Left Arrow, 38 Up Arrow, 39 Right Arrow, 40 Down Arrow
    // 45 Insert, 46 Forward Delete, 144 Num Lock, 145 Scroll Lock
    var ignoreKeyCodes = 
',9,16,17,18,19,20,27,33,34,35,36,37,38,39,40,45,46,144,145,';
    if ( ignoreKeyCodes.indexOf(',' + keyCode + ',') > −1 ) { return; }

    // we want to ignore the backspace on keydown only
    // let it do its job, but otherwise don't change focus
    if ( keyCode == 8 && event.type == 'keydown' ) { return; }

    var $this = $(this);
    var currentLength = $this.val().length;
    var maximumLength = $this.attr('maxlength'),

    // if backspace key and there are no more characters, go back
    if ( keyCode == 8 && currentLength == 0 ) {
        $this.prev().focus();
    }

    // if we've filled up this input, go to the next
    if ( currentLength == maximumLength ) {
        $this.next().focus();
    }
});

Discussion

Why do we bind both keydown and keyup events?

You could use just the keydown event. However, when the user is done filling out the first input, there would be no visual indication that their next keystroke would focus the second input. By using the keyup event, after the first input is filled, the second input gains focus, the cursor is placed at the beginning of the input, and most browsers indicate that focus with a border or some other highlight state. Also, the keyup event is required for the Backspace key to focus the previous input after the current input is empty.

You could use just the keyup event. However, if your cursor was in the second input and you were using the Backspace key to clear it, once you removed all characters, the focus would be shifted into the first input. Unfortunately, the first is already full, so the next keystroke would be lost, because of the maxlength attribute, and then the keyup event would focus the second input. Losing a keystroke is a bad thing, so we perform the same check on keydown, which moves the cursor to the next input before the character is lost.

Because the logic isn’t CPU intensive, we can get away with binding both the keydown and keyup events. In another situation, you may want to be more selective.

You’ll notice that the ignoreKeyCodes variable is a string. If we were building it dynamically, it would be faster to create an array and then use .join(',') or .toString() JavaScript methods. But since the value is always the same, it’s easier to simply code it as a string from the very beginning. I also start and end the ignoreKeyCodes variable with commas, because I am paranoid about false positives. This way, when searching for a keyCode flanked by commas, you are guaranteed to find only the number you’re looking for—if you look for 9, it won’t find 19, or 39.

Notice there is no code to prevent $this.next().focus() from executing when on the last <input> element. I’m taking advantage of the jQuery chain here. If $this.next() finds nothing, then the chain stops—it can’t .focus() what it can’t find. In a different scenario, it might make sense to precache any known .prev() and .next() elements.

What will happen when JavaScript is disabled? Nothing. The user will have to manually click from one text input field to the next.

10.8. Displaying Remaining Character Count

Problem

Your company has a contact form on its website. This form has a <textarea> element to allow users to express themselves freely. However, you know time is money, and you don’t want your staff reading short novels, so you would like to limit the length of the messages they have to read. In the process, you’d also like to show the end user how many characters are remaining:

<textarea></textarea>
<div class="remaining">Characters remaining: <span class="count">300</span></div>

Solution

Target all .remaining messages, and for each find the associated <textarea> element and the maximum number of characters as listed in the .count child element. Bind an update function to the <textarea> to capture when the user enters text:

// for each "Characters remaining: ###" element found
$('.remaining').each(function(){

    // find and store the count readout and the related textarea/input field
    var $count = $('.count',this);
    var $input = $(this).prev();

    // .text() returns a string, multiply by 1 to make it a number (for math)
    var maximumCount = $count.text()*1;

    // update function is called on keyup, paste and input events
    var update = function(){

        var before = $count.text()*1;
        var now = maximumCount - $input.val().length;

        // check to make sure users haven't exceeded their limit
        if ( now < 0 ){
            var str = $input.val();
            $input.val( str.substr(0,maximumCount) );
            now = 0;
        }

        // only alter the DOM if necessary
        if ( before != now ){
            $count.text( now );
        }
    };

    // listen for change (see discussion below)
    $input.bind('input keyup paste', function(){setTimeout(update,0)} );

    // call update initially, in case input is pre-filled
    update();

}); // close .each()

Discussion

The preceding code is generic enough to allow for any number of “Character remaining” messages and <textarea> elements on a given page. This could be useful if you were building a content management or data entry system.

To protect against when the user attempts to copy and paste data into the <textarea> using a mouse, we need to bind both the input and paste events. The mouseup event cannot be used because it is not triggered when selecting an item from the browser’s contextual menu. The input event is part of HTML5 (Working Draft) and already implemented by Firefox, Opera, and Safari. It fires on user input, regardless of input device (mouse or keyboard). Safari, at the time of this writing, has a bug and does not fire the input event on <textarea> elements. Both Safari and Internet Explorer understand the paste event on <textarea> elements and understand keyup to capture keystrokes. Attaching keyup, input, and paste is redundant but, in this case, benign. The update function is simple enough that there aren’t any performance issues, and it only manipulates the DOM when needed, so any redundant update calls after the first would do nothing.

An alternative to redundant events would be to use setInterval when the <textarea> element has focus. The same update function could be called from the interval, and if it is paired with the keyup event, you’d get the immediate updating on key presses and an arbitrary update interval, say 300 milliseconds, for when information is pasted into the <textarea> element. If the update function were more complex or costly, this might be a better alternative.

When binding events to form elements, it is sometimes important to use a timeout to slightly delay a function call. In the previous example, Internet Explorer triggers the paste event before the text from the clipboard is actually added to the <textarea> element. Thus, the calculation for characters remaining would be incorrect until the user clicks or presses another key. By using setTimeout(update,0), the update function is placed at the end of the call stack and will fire after that browser has added the text:

$input.bind('input keyup paste', function(){setTimeout(update,0)} );

What will happen when JavaScript is disabled? You should hide the “Characters remaining” message by default in CSS. Then use JavaScript to add a class name to a parent element that would override the previous CSS rule. Also, it’s important to check the length of the message again on the server side:

<style type="text/css" title="text/css">
 .remaining { display:none; }
 .jsEnabled .remaining { display:block; }
</style>

...

// when the HTML DOM is ready
$(document).ready(function(){
    $('form').addClass('jsEnabled'),
});

10.9. Constraining Text Input to Specific Characters

Problem

Your shopping cart page has a quantity field, and you want to make sure users can only enter numbers into that field:

<input type="text" class="onlyNumbers" />

Solution

Find all elements with the onlyNumbers class, and listen for keydown and blur events. The keydown event handler will prevent users from typing non-numeric characters into the field. The blur event handler is a precautionary measure that cleans any data entered via Paste from the contextual menu or the browser’s Edit menu:

$('.onlyNumbers').bind('keydown',function(event){

    // the keycode for the key pressed
    var keyCode = event.which;

    // 48-57 Standard Keyboard Numbers
    var isStandard = (keyCode > 47 && keyCode < 58);

    // 96-105 Extended Keyboard Numbers (aka Keypad)
    var isExtended = (keyCode > 95 && keyCode < 106);

    // 8 Backspace, 46 Forward Delete
    // 37 Left Arrow, 38 Up Arrow, 39 Right Arrow, 40 Down Arrow
    var validKeyCodes = ',8,37,38,39,40,46,';
    var isOther = ( validKeyCodes.indexOf(',' + keyCode + ',') > −1 );

    if ( isStandard || isExtended || isOther ){
        return true;
    } else {
        return false;
    }

}).bind('blur',function(){

    // regular expression that matches everything that is not a number
    var pattern = new RegExp('[^0-9]+', 'g'),

    var $input = $(this);
    var value = $input.val();

    // clean the value using the regular expression
    value = value.replace(pattern, ''),
    $input.val( value )
});

Discussion

The keydown event is immediate and prevents users from typing non-numeric characters into the field. This could be replaced with a keyup event that shares the same handler as the blur event. However, users would see a non-numeral appear and then quickly disappear. I prefer just to prevent them from entering the character in the first place and avoid the flickering.

The blur event protects against copying and pasting non-numeric characters into the text field. In the previous scenario, I’m assuming the user is either trying to test the limits of the JavaScript (something that I would do) or trying to copy and paste data from a spreadsheet. Neither situation requires immediate correction in my opinion. However, if your situation requires more immediate correction, please see the “Discussion” section of Recipe 10.8 for more information about capturing changes from the paste event.

If your situation is different and you expect users to be copying and pasting data from a spreadsheet, keep in mind that the regular expression I use does not account for a decimal point. So, a number like “1,000” would be cleaned to “1000” and the number “10.00” would also be cleaned to “1000” as well.

You’ll notice that the validKeyCodes variable is a string that starts and ends with commas. As I mentioned in Recipe 10.7, I did this because I am paranoid about false positives—when searching for a keyCode flanked by commas, you are guaranteed to find only the number you’re looking for.

What will happen when JavaScript is disabled? The user will be able to enter any characters they please. Always be sure to validate code on the server. Don’t rely on JavaScript to provide clean data.

10.10. Submitting a Form Using Ajax

Problem

You have a form that you would like to submit using Ajax:

<form action="process.php">

    <!-- value changed via JavaScript -->
    <input type="hidden" name="usingAJAX" value="false" />

    <label for="favoriteFood">What is your favorite food?</label>
    <input type="text" name="favoriteFood" id="favoriteFood" />

    <input type="submit" value="Tell Us" />

</form>

Solution

Find the <form> element, and hijack the submit event:

$('form').submit(function(event){

    // we want to submit the form using Ajax (prevent page refresh)
    event.preventDefault();

    // this is where your validation code (if any) would go
    // ...

    // this tells the server-side process that Ajax was used
    $('input[name="usingAJAX"]',this).val( 'true' );

    // store reference to the form
    var $this = $(this);

    // grab the url from the form element
    var url = $this.attr('action'),

    // prepare the form data to send
    var dataToSend = $this.serialize();

    // the callback function that tells us what the server-side process had to say
    var callback = function(dataReceived){

        // hide the form (thankfully we stored a reference to it)
        $this.hide();

        // in our case the server returned an HTML snippet so just append it to 
        // the DOM
        // expecting: <div id="result">Your favorite food is pizza! Thanks for 
        // telling us!</div>
        $('body').append(dataReceived)
    };

    // type of data to receive (in our case we're expecting an HTML snippet)
    var typeOfDataToReceive = 'html';

    // now send the form and wait to hear back
    $.get( url, dataToSend, callback, typeOfDataToReceive )

}); // close .submit()

Discussion

What will happen when JavaScript is disabled? The form will be submitted, and the entire page will refresh with the results from the server-side script. I use JavaScript to alter the value of the <input type="hidden" name="usingAJAX" /> element from false to true. This allows the server-side script to know what to send back as a response—either a full HTML page or whatever data is expected for the Ajax response.

10.11. Validating Forms

Problem

You have a form that you would like to validate. To get started, you’ll want to set up some basic CSS. The only styles that are really important for this enhancement are the display:none declaration of the div.errorMessage selector and the display:block declaration of the div.showErrorMessage selector. The rest are just to make things look better:

<style type="text/css" title="text/css">
    div.question {
        padding: 1em;
    }
    div.errorMessage {
        display: none;
    }
    div.showErrorMessage {
        display: block;
        color: #f00;
        font-weight: bold;
        font-style: italic;
    }
    label.error {
        color: #f00;
        font-style: italic;
    }
</style>

The following HTML snippet is one example of how you might structure this form. The <div class="question> element is purely for layout and not important for the validation code. Each <label> element’s for attribute associates it with the form element with that identical id attribute. That is standard HTML, but I wanted to call it out because the JavaScript will also be using that (in reverse) to find the correct <label> for a given form element. Similarly, you’ll notice the error messages have an id attribute of errorMessage_ plus the name attribute of the associated form element. This structure may seem redundant, but radio buttons and checkboxes are grouped by the name attribute and you’d only want to have one error message per such group:

<form action="process.php">

<!-- TEXT -->
<div class="question">
    <label for="t">Username</label>
    <input id="t" name="user" type="text" class="required" />
    <div id="errorMessage_user" class="errorMessage">Please enter your username.</div>
</div>

<!-- PASSWORD -->
<div class="question">
    <label for="p">Password</label>
    <input id="p" name="pass" type="password" class="required" />
    <div id="errorMessage_pass" class="errorMessage">Please enter your password.</div>
</div>

<!-- SELECT ONE -->
<div class="question">
    <label for="so">Favorite Color</label>
    <select id="so" name="color" class="required">
        <option value="">Select a Color</option>
        <option value="ff0000">Red</option>
        <option value="00ff00">Green</option>
        <option value="0000ff">Blue</option>
    </select>
    <div id="errorMessage_color" class="errorMessage">Please select your favorite 
color.</div>
</div>

<!-- SELECT MULTIPLE -->
<div class="question">
    <label for="sm">Favorite Foods</label>
    <select id="sm" size="3" name="foods" multiple="multiple" class="required">
        <option value="pizza">Pizza</option>
        <option value="burger">Burger</option>
        <option value="salad">Salad</option>
    </select>
    <div id="errorMessage_foods" class="errorMessage">Please choose your favorite 
foods.</div>
</div>

<!-- RADIO BUTTONS -->
<div class="question">
    <span>Writing Hand:</span>
    <input id="r1" type="radio" name="hand" class="required"/>
    <label for="r1">Left</label>
    <input id="r2" type="radio" name="hand" class="required" />
    <label for="r2">Right</label>
    <div id="errorMessage_hand" class="errorMessage">Please select what hand you 
write with.</div>
</div>

<!-- TEXTAREA -->
<div class="question">
    <label for="tt">Comments</label>
    <textarea id="tt" name="comments" class="required"></textarea>
    <div id="errorMessage_comments" class="errorMessage">Please tell us what you 
think.</div>
</div>

<!-- CHECKBOX -->
<div class="question">
    <input id="c" type="checkbox" name="legal" class="required" />
    <label for="c">I agree with the terms and conditions</label>
    <div id="errorMessage_legal" class="errorMessage">Please check the box!</div>
</div>

<input type="submit" value="Continue" />

</form>

Solution

The first part of the solution is fairly straightforward. Find the <form> element, and hijack the submit event. When the form is submitted, iterate through the required form elements, and check to see whether the required elements are valid. If the form is error free, then (and only then) trigger the submit event:

$('form').submit(function(event){

    var isErrorFree = true;

    // iterate through required form elements and check to see if they are valid
    $('input.required, select.required, textarea.required',this).each(function(){
        if ( validateElement.isValid(this) == false ){
            isErrorFree = false;
        };
    });

    // Ajax alternatives:
    // event.preventDefault();
    // if (isErrorFree){ $.get( url, data, callback, type ) }
    // if (isErrorFree){ $.post( url, data, callback, type ) }
    // if (isErrorFree){ $.ajax( options ) }

    return isErrorFree;

}); // close .submit()

The second part of this solution is where all the real validation happens. The isValid() method starts by storing frequently used data from the element we’re validating. Then, in the switch() statement, the element is validated. Finally, class names are added to or removed from the <label> and div.errorMessage elements.

var validateElement = {

    isValid:function(element){

        var isValid = true;
        var $element = $(element);
        var id = $element.attr('id'),
        var name = $element.attr('name'),
        var value = $element.val();

        // <input> uses type attribute as written in tag
        // <textarea> has intrinsic type of 'textarea'
        // <select> has intrinsic type of 'select-one' or 'select-multiple'
        var type = $element[0].type.toLowerCase();

        switch(type){
            case 'text':
            case 'textarea':
            case 'password':
                if ( value.length == 0 || 
value.replace(/s/g,'').length == 0 ){ isValid = false; }
                break;
            case 'select-one':
            case 'select-multiple':
                if( !value ){ isValid = false; }
                break;
            case 'checkbox':
            case 'radio':
                if( $('input[name="' + name + 
'"]:checked').length == 0 ){ isValid = false; };
                break;
        } // close switch()

        // instead of $(selector).method we are going to use $(selector)[method]
        // choose the right method, but choose wisely
        var method = isValid ? 'removeClass' : 'addClass';

        // show error message [addClass]
        // hide error message [removeClass]
        $('#errorMessage_' + name)[method]('showErrorMessage'),
        $('label[for="' + id + '"]')[method]('error'),

        return isValid;

    } // close validateElement.isValid()
}; // close validateElement object

Discussion

The validation in this solution is quite simple. It checks the following:

  • <input type="text">, <input type="password">, and <textarea> elements have some data other than whitespace.

  • <select> elements have something other than the default option selected. Please note that there are two types of <select> element: “select-one” and “select-multiple” (see the second code snippet in this section for HTML code and the previous code snippet for JavaScript validation). The first <option> element of the “select-one” <select> must have a value="" in order for validation to work. The “select-multiple” <select> is immune from this requirement because its <option> elements can be deselected.

  • <input type="radio"> and <input type="checkbox"> elements have at least one element checked in their respective name groups.

The switch(){} statement is used because it is more efficient than multiple if(){}else if(){} statements. It also allows for elements with shared validation to be grouped together, letting the break; statement separate these groups.

The validateElement object is in the global scope with the intention that it might be reused on other forms. It also keeps the global scope less cluttered by containing the validation methods—in the future, helper methods could be added to the validateElement object without worrying about global naming collisions. For instance, a stripWhitespace() method could be implemented like this:

var validateElement = {

    stripWhitespace : function(str){
        return str.replace(/s/g,''),
    },
    isValid : function(element){

        //... snipped code ...//

            case 'text':
            case 'textarea':
            case 'password':
                // if text length is zero after stripping whitespace, it's not valid
                if ( this.stripWhitespace(value).length == 0 ){ isValid = false; }
                break;

        //... snipped code ...//

    } // close validateElement.isValid()
}; // close validateElement object

When showing and hiding error messages, I used the bracket notation for calling the .addClass() and .removeClass() jQuery methods:

// instead of $(selector).method we are going to use $(selector)[method]
// choose the right method, but choose wisely
var method = isValid ? 'removeClass' : 'addClass';

// show error message [addClass]
// hide error message [removeClass]
$('#errorMessage_' + name)[method]('showErrorMessage'),
$('label[for="' + id + '"]')[method]('error'),

The previous code in bracket notation is functionally identical to the dot notation:

if (isValid) {
    $('#errorMessage_' + name).removeClass('showErrorMessage'),
    $('label[for="' + id + '"]').removeClass('error'),
} else {
    $('#errorMessage_' + name).addClass('showErrorMessage'),
    $('label[for="' + id + '"]').addClass('error'),
}

When we validate on submit, the dot notation is cleaner and more readable. However, let’s extend the bracket-notation solution to allow elements to revalidate (after an initial validation) using the change event. This would give the user immediate feedback that their new answers are in fact valid, without requiring them to click the submit button. The following code does not work as expected (see the next paragraph for the real solution), but it illustrates where to .unbind() and .bind() the change event:

// instead of $(selector).method we are going to use $(selector)[method]
// choose the right method, but choose wisely
var method = isValid ? 'removeClass' : 'addClass';

// show error message [addClass]
// hide error message [removeClass]
$('#errorMessage_' + name)[method]('showErrorMessage'),
$('label[for="' + id + '"]')[method]('error'),

// after initial validation, allow elements to re-validate on change
$element
    .unbind('change.isValid')
    .bind('change.isValid',function(){ validateElement.isValid(this); });

Note

Because we are unbinding and binding the change event with each validation, I added the .isValid event namespace to target it more directly. This way, if a form element has other change events bound, they will remain.

The problem with the previous code isn’t syntax but logic. You’ll note that the radio buttons in the HTML have the class="required" attribute. This means that when the entire form is validated, each radio button is validated, and (more importantly) each radio button’s <label> has a class added or removed to indicate the error. However, if we allow for a revalidation to occur using the element-specific change event, only that particular radio button’s <label> will be updated—the others would remain in an error state. To account for this, a single change event would have to look at all radio buttons and checkboxes in that name group to affect all the <label> classes simultaneously:

// instead of $(selector).method we are going to use $(selector)[method]
// choose the right method, but choose wisely
var method = isValid ? 'removeClass' : 'addClass';

// show error message [addClass]
// hide error message [removeClass]
$('#errorMessage_' + name)[method]('showErrorMessage'),

if ( type == 'checkbox' || type == 'radio' ) {

    // if radio button or checkbox, find all inputs with the same name
    $('input[name="' + name + '"]').each(function(){
        // update each input elements <label> tag, (this==<input>)
        $('label[for="' + this.id + '"]')[method]('error'),
    });

} else {

    // all other elements just update one <label>
    $('label[for="' + id + '"]')[method]('error'),

}

// after initial validation, allow elements to re-validate on change
$element
    .unbind('change.isValid')
    .bind('change.isValid',function(){ validateElement.isValid(this); });

If the preceding code were to be rewritten using dot-notation syntax, it would have twice again as many lines. And on a separate note, with this new logic in place, only one radio button (or checkbox) in a name group would need to have the class="required" in order for all the other elements in that group to be adjusted correctly.

What will happen when JavaScript is disabled? The form will be submitted without client-side validation. Always be sure to validate code on the server. Don’t rely on JavaScript to provide clean data. If the server-side code returns the form with errors, it can use the same classes, on the same elements, in the same way. There is no need to use inline style tags or write custom code to handle the server-side errors differently.

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

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