Chapter 11. HTML Form Enhancements with Plugins

Jörn Zaefferer

Introduction

Forms are a very common interaction for users of web applications; improving this interaction improves the business of the application.

jQuery and various plugins offer out-of-the-box and customizable solutions for better interactions, with progressive enhancement at heart.

Each problem could be solved with a jQuery solution from scratch, but using a plugin yields a lot of benefits:

  • Avoids reinventing the wheel

  • Provides functionality that is well tested among different browsers

  • Saves a lot of work that hides in the details

  • Provides functionality that is tuned to work under extreme conditions

Each recipe will discuss the strengths and weaknesses of the plugin, highlighting where it may make sense to start from scratch instead.

Basic Approach

The basic approach to using jQuery plugins is always the same. First you include jQuery itself, and then you include the plugin file on your page. Some plugins also need a stylesheet. Most plugins require some markup to work with and a line of code that selects this markup element and does something with it. Because of common naming conventions, a plugin “slideshow” would be used like this:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="jquery.slideshow.css"/>
    <script src="assets/jquery-latest.js"></script>
    <script src="assets/jquery.slideshow.js"></script>
    <script type="text/javascript">
    jQuery(document).ready(function($){
        $("#slideshow").slideshow();
    });
    </script>
</head>
<body>
    <div id="slideshow">...</div>
</body>
</html>

The specific markup necessary for a slideshow is quite different for a slider or form validation, so that’s something to look out for in the documentation and examples of each plugin, and that will be covered in the following recipes.

11.1. Validating Forms

Problem

Most registration forms require input of email address, a password (two times), a username, and some other information, like a birth date. This applies to email services, web shops, or forums.

It’s easy to imagine John Doe, who wants to buy a new monitor at some web shop where the registration also requires the input of a captcha (a barely legible group of random characters to tell the difference between a human and a bot). He fills out the complete form, which takes some time, and then submits the form. After about five seconds, the form is displayed again, with some error message at the top: he forgot to fill out the street field. He fixes that and submits again. Another five seconds pass: now he missed the password and captcha field! Huh? He did fill those in, but he had to fill them in again after the failed first attempt.

Such late feedback can be very frustrating and can ruin an experience that was otherwise good, especially when security concerns limit functionality—here causing the empty password and captcha fields.

Solution

One way to improve the situation is to add client-side validation. The basic idea is to give the user feedback as early as possible, without annoying him. It shouldn’t be possible to submit an invalid form, avoiding the issue of filling in passwords or captchas again.

It also makes sense to highlight errors on fields after they are filled in, as in the case of an invalid email address like . Highlighting fields as wrong doesn’t help when it happens before the user even has the chance to fill out a field correctly: to display “too short” on a field that requires at least two characters, after the user types the first character, isn’t helping at all.

A plugin covering these requirements quite well is the validation plugin.

To get started, download the plugin, extract the files, and copy jquery.validate.js to your project. The following example shows a comment form with fields for name, email, URL, and actual comment. A call to the plugin method validate() sets up the validation for the form. Validation rules are specified inline using classes and attributes:

<!DOCTYPE html>
<html>
<head>
  <script src="assets/jquery-latest.js"></script>
  <script src="assets/jquery.validate.js"></script>
  <style type="text/css">
    * { font-family: Verdana; font-size: 96%; }
    label { width: 10em; float: left; }
    label.error { float: none; color: red; padding-left: .5em; vertical-align: top; }
    div { clear: both; }
    input, textarea { width: 15em; }
    .submit { margin-left: 10em; }
  </style>
  <script type="text/javascript">
    jQuery(document).ready(function($){
      $("#commentForm").validate();
    });
  </script>
</head>
<body>
  <form id="commentForm" method="get" action="">
    <fieldset>
      <legend>A simple comment form with submit validation and default messages</legend>
      <div>
        <label for="cname">Name</label>
        <input id="cname" name="name" class="required" minlength="2" />
      </div>
      <div>
        <label for="cemail">E-Mail</label>
        <input id="cemail" name="email" class="required email" />
      </div>
      <div>
        <label for="curl">URL (optional)</label>
        <input id="curl" name="url" class="url" value="" />
      </div>
      <div>
        <label for="ccomment">Your comment</label>
        <textarea id="ccomment" name="comment" class="required"></textarea>
      </div>
      <div>
        <input class="submit" type="submit" value="Submit"/>
      </div>
    </fieldset>
  </form>
</body>
</html>

Any field with the class required is checked to have any content at all. Other methods in this example include the following:

email

Checks that the field contains a valid email address

url

Checks that the field contains a valid URL

minlength

Checks that the field contains at least x characters; here x is specified via an attribute: minlength="2"

Discussion

The validation plugin promotes one specific approach to client-side validation: perform as much work as possible in the browser, and ask the server for help only in special cases, which are covered by the remote method, for example, to check whether a username is still available.

A different approach would avoid replicating validation rules and methods on both the client and server sides, instead sending the whole form via Ajax to the server, usually on submit of the form. It could then use the same logic on the server side that is in place already. The drawback is that user feedback is slower, because it is impractical to send a request for every keypress. It’s also not very likely that the server validation was written with Ajax validation in mind, making it impractical to reuse it. In that case, you’d have to plan up front to use it that way.

The validation plugin can be added to a form later, and apart from remote validation, there is no need to adapt the application in any way. This makes it useful for a simple comment form on a blog, as well as more complex forms on some intranet application and anything in between.

The most important building blocks for the plugin are rules and methods. Methods contain validation logic, like the email method that uses a regular expression to determine whether a value is a valid email address. Rules wire input fields together with methods, where a single rule is a pair of an input field and a method. The email field then has one rule for making it required and one for making it an email address.

Methods

The plugin has about 19 built-in methods. The essential method is required—when specified, the field has to be filled out. When left out, most other methods will be ignored on an empty field. The only exception to that is the equalTo method, which checks that the content of a field is exactly the same as some other field, which even applies for an empty field. The rule itself is most commonly used for “Confirm password” fields.

The email, url, date, dateISO, dateDE, number, numberDE, digits, and creditcard methods all check for certain data types, with simple variations for different locales. For example, number requires a U.S. number format like 1,000.00, and numberDE requires the German number format 1.000,00.

The min and max and range methods check the value of a number, while minlength, maxlength, and rangelength check the number of characters.

In case of a select input or checkboxes, min, max, and range validate the number of selected options or checked checkboxes.

In case of file inputs, the accept method comes in handy and checks the file extension, by default looking for .gif, .png, .jpg, or .jpeg.

The remote method is the only method that delegates the actual validation logic elsewhere, to the server side. It gets a URL as the parameter, pointing at some server-side resource. This could be a script that does a database query, for example, for checking if a username is already taken or if a specified email address is already registered. An example of a registration form using the remote method for both username and email fields can be found at http://jquery-cookbook.com/go/plugin-validation-remote-demo.

Custom methods

Custom methods are a good way to extend the plugin with application-specific requirements. You may have a form where users enter URLs that have to start with a certain corporate domain. A custom method could encapsulate the necessary validation:

jQuery.validator.addMethod("domain", function(value, element) {
    return this.optional(element) || /^http://mycorporatedomain.com/.test(value);
}, "Please specify the correct domain for your documents");

The first argument to jQuery.validator.addMethod is the name of the custom method, and it must be a valid JavaScript identifier. The second argument is a function that implements the actual validation. If it returns true, the input is considered valid. It uses this.optional(element) to determine whether that input has no value and should therefore be skipped—all default methods use the same call. The third argument specifies the default message for the new method.

Writing methods that accept a parameter works very similarly:

jQuery.validator.addMethod("math", function(value, element, params) {
    return this.optional(element) || value == params[0] + params[1];
}, jQuery.format("Please enter the correct value for {0} + {1}"));

In this case, the default message is specified with the help of jQuery.format, a templating helper the plugin provides. The indexed curly-braced placeholders are replaced with the actual parameters when the validation is run.

Custom methods can also reuse existing methods, which is useful to specify different default messages for a single method. In this example, the required method is aliased to customerRequired with a different default message:

$.validator.addMethod("customerRequired", $.validator.methods.required, 
"Customer name required");

A collection of ready-to-use custom methods are bundled with the plugin in additionalMethods.js.

Rules

There are four distinct ways to specify rules: two in code and two inline as metadata. The previous example uses classes and attributes as metadata, which the plugin supports by default. When the metadata plugin is available, rules can be embedded in various ways, for example, inside the class attribute:

<input type="text" name="email" class="{required:true, email:true}" />

Here the class contains JavaScript literals inside curly braces, which is very similar in syntax to specifying rules in code via the rules option:

$("#myform").validate({
    rules: {
        name: {
            required: true,
            minlength: 2
        },
        email: {
            required: true,
            email: true
        },
        url: "url",
        comment: "required"
    }
});

The object keys like name, email, url, and comment always refer to the name of the element, not the ID.

Note the shortcuts used for url and comment, where only a single rule is necessary. This isn’t available when specifying rules with parameters, like minlength.

Some rules need to be added later, which is possible using the fourth way, the rules plugin method:

// initialize the validation first
$("#myform").validate();
// some time later, add more rules
$("#username").rules("add", { minlength: 2});

Rules can also be removed that way:

$("#username").rules("remove", "required");

This can come in handy when implementing a “Forgot password” link on a login form:

$("#loginform").validate({
    username: "required",
    password: "required"
});
$("a#forgotPassword").click(function(e) {
    $("#password").rules("remove", "required");
    $("#loginform").submit();
    $("#password").rules("add", "required");
    return false;
});

That click event code removes the required rule from the password, tries to submit the form (triggering the validation), and adds the rule back. That way, the username field is still being validated, and if the validation fails, the password field will be required again (in case of a normal form submit).

Dependencies

Often the validation behavior of a field depends on some more factors than just a link being clicked. Those can be handled using parameters for the required method. The parameter can be a selector or a callback. The selector is useful when the dependency can be written in a simple expression. An email field may be required only when the newsletter checkbox is selected:

email: {
    required: "#newsletter:checked"
}

A callback can be used for expressions of any complexity, for example, when the field depends on the state of multiple other fields:

email: {
    required: function(element) {
        return $("#newsletter:checked").length && $("#telephone:blank");
    }
}
Custom expressions

The previous example used the :blank expression to select an element only when it has no value at all or only whitespace. The plugin also provides the :filled expression, the inversion of :blank. jQuery itself provides :checked, and the validation plugin adds the inversion :unchecked. Both are useful when specifying dependencies on radio buttons or checkboxes.

While you could use the :not expression to inverse :filled or :checked, :blank and :unchecked make the selector more readable and therefore easier to understand at a glance.

Error messages

Similar to rules, there are a few ways to specify messages, both in code and inline. Inline messages are read from the title attribute:

<input name="email" class="required email" title="A valid email address is 
required" />

That will produce a single error message for each rule. An alternative inline approach is to use the metadata plugin (see Rules):

<input name="email" class="{required:true, email:true, messages:{required:"Required", 
email: "Not a valid email address"}}"/>

With this approach, you can specify a message for each rule, which is also possible when using the messages option:

$("#myform").validate({
    messages: {
        email: {
            required: "Required",
            email: "Not a valid email address"
        }
    }
});

Again, the keys—here, email—refer to the name of the input, not the ID, just the same as specifying rules.

For more dynamic scenarios, the rules plugin method can be used:

$("#myform").validate();
// sometime later
$("#email").rules("add", {
    messages: {
        email: "A valid email address, please!"
    }
});

If you use some of the alternatives to the title attribute while using a regular title, you can suppress the plugin from checking the attribute for messages:

$("#myform").validate({
    ignoreTitle: true
});
Localization

The default messages are in English (with the exception of dateDE and numberDE). In addition, the plugin provides (at the time of this writing) 17 localizations. Usage is plain and simple: just copy the messages_xx.js file you need to your project, and include it after the validation plugin. For example, here’s the code for the Swedish localization:

<script src="assets/jquery-latest.js"></script>
<script src="assets/jquery.validate.js"></script>
<script src="assets/messages_se.js.js"></script>

With that in place, instead of “Please enter a valid email address.” you’ll get “Ange en korrekt e-postadress.”

Error element

By default error messages are inserted into the DOM next to the element that they are referring to. An error message is inserted as a label element, with the for attribute set to the id of the validated element. Using a label with the for attribute leverages the browser feature where a click on the label gives focus to the input field. So by default, the user can click the error message to give the invalid field focus.

If you need a different element type, use the errorElement option:

$("#myform").validate({
    errorElement: "em"
});

The plugin will still use the for attribute then, but the auto linking the browser provides won’t work.

Layout

If you want to customize the position where error messages are inserted, the errorPlacement option is useful. We may have a form that uses a table for layout, where the first column contains the regular label, the second the input, and the third the messages:

<form id="signupform" method="get" action="">
    <table>
        <tr>
            <td class="label">
                <label id="lfirstname" for="firstname">First Name</label>
            </td>
            <td class="field">
                <input id="firstname" name="firstname" type="text" value="" 
maxlength="100" />
            </td>
            <td class="status"></td>
        </tr>
        <!-- more fields -->
    </table>
</form>

$("#signupform").validate({
    errorPlacement: function(error, element) {
        error.appendTo( element.parent("td").next("td") );
    }
});

Another common requirement is to display a general message above the form. The errorContainer option helps with that:

$("#myform").validate({
    errorContainer: "#messageBox1"
});

In this example, an element with the ID messageBox1 would be shown when the form is invalid and would be hidden when valid.

This can also be combined with the errorLabelContainer option. When specified, error labels aren’t placed next to their input elements but instead added to a single element above or below the form. Combined with the errorContainer and wrapper options, messages are added to a list of errors above the form:

<div class="container">
        <h4>There are a few problems, please see below for details.</h4>
        <ul></ul>
</div>
<form id="myform" action="">
<!-- form content -->
</form>

var container = $('div.container'),
// validate the form when it is submitted
$("#myform").validate({
    errorContainer: container,
    errorLabelContainer: $("ul", container),
    wrapper: 'li'
});

Handling the submit

Once the form is valid, it has to be submitted. By default that just works as any other form submit. To submit the form via Ajax, the submitHandler option can be used, together with the form plugin (see Recipe 11.6 for more details):

$(".selector").validate({
    submitHandler: function(form) {
        $(form).ajaxSubmit();
    }
});

The invalidHandler callback is useful for running code on an invalid submit. The following example displays a summary of the missing fields:

$("#myform").validate({
    invalidHandler: function(e, validator) {
        var errors = validator.numberOfInvalids();
        if (errors) {
            var message = errors == 1
                ? 'You missed 1 field. It has been highlighted below'
                : 'You missed ' + errors + ' fields. They have been highlighted below';
            $("div.error span").html(message);
            $("div.error").show();
        } else {
            $("div.error").hide();
        }
    }
});

The Marketo demo shows this behavior in action (http://jquery-cookbook.com/go/plugin-validation-marketo-demo).

Limitations

So, when does it make sense to not use the plugin and write a validation solution from scratch? There are certain limitations: forms where groups of inputs, like checkboxes, have different name attributes are hard to validate as a group. Lists of inputs that all have the same name can’t be validated, because each individual input needs its own unique name. If you stick with the naming convention of unique names for individual inputs and one name for groups of checkboxes or radio buttons, the plugin works fine.

If your application has only a login form, the plugin is probably overkill, and it would be difficult to justify the file size; however, if you use the plugin somewhere else on a site, it can be used for the login form as well.

11.2. Creating Masked Input Fields

Problem

There are certain input types that are quite error prone, like a credit card number. A simple typo that goes unnoticed at first can cause weird errors much later. That also applies to dates or phone numbers. These have a few features in common:

  • A fixed length

  • Mostly numbers

  • Delimiting characters at certain positions

Solution

A jQuery plugin that can improve the feedback is the masked input plugin. It is applied to one or more inputs to restrict what can be entered while inserting delimiters automatically.

In this example, a phone input is masked:

<!DOCTYPE html>
<html>
<head>
    <script src="assets/jquery-latest.js"></script>
    <script src="assets/jquery.maskedinput.js"></script>
    <script>
    jQuery(document).ready(function($) {
        $("#phone").mask("(999) 999-9999");
    });
    </script>
</head>
<body>
    <form>
        <label for="phone">Phone</label>
        <input type="text" name="phone" id="phone" />
    </form>
</body>
</html>

The plugin file is included in addition to jQuery itself. In the document-ready callback, the input with ID phone is selected, and the mask method is called. The only argument specifies the mask to use, here describing the format of a U.S. phone number.

Discussion

There are four characters with a special meaning available when specifying the mask:

a

Any alpha character from a−z and A−Z

9

Any digit from 0–9

*

Any alphanumeric character, that is, a–z, A–Z, and 0–9

?

Anything after this is optional

Any other character, like the parentheses or hyphen in the phone mask, are considered literals, which the plugin automatically inserts into the input and which the user can’t remove.

By default, the plugin inserts an underscore (_) for each variable character. For the phone example, the input would display the following value once focused:

(___) ___-____

When the user starts typing, the first underscore gets replaced if it is a valid character, here a digit. The other literals are skipped as well.

The underscore placeholder can be customized by passing an additional argument:

$("#phone").mask("(999) 999-9999", {placeholder: " "});

In this case, whitespace would be displayed instead of the underscore.

It’s also possible to define new mask characters:

$.mask.definitions['~'] = '[+−]';
$("#eyescript").mask("~9.99 ~9.99 999");

Here the new mask character is a tilde, and allowed values for it are + and −, specified as a regular expression character class. The tilde can then be used in a mask.

The quotation mark enables masks with a fixed part and an optional part. A phone number with an optional extension could be defined like this:

$("#phone").mask("(999) 999-9999? x99999");

When a masked input is combined with the validation plugin (Recipe 11.1), it’s important the field proper rules are defined for it. Otherwise, the validation plugin may accept the placeholder characters of the mask plugin as valid input, irritating the user when an invalid field is marked as valid while he just inserted the first character.

Limitations

The significant limitation of the plugin is the fixed-length requirement. It can’t be used for anything with a variable length, like currency value. For example, “$ 999,999.99” would require a value between 100,000.00 and 999,999.99 and can’t accept anything above or below.

11.3. Autocompleting Text Fields

Problem

There are two HTML input types that allow a user to select one value out of a list of existing values: radio buttons and selects. Radio buttons work well for lists with up to eight items, and selects work well with up to 30 to 150, depending on the type of data. Both fall short when the user can enter a new value as well—in this case they are usually accompanied by an “Other” field. Both become useless when the list is big, maybe 500 or 500,000 items.

Solution

The jQuery UI autocomplete widget can solve the various situations where a select isn’t enough. In the simplest case, the data to display is available in a JavaScript array:

<label for="month">Select a month:</label>
<input id="month" name="month" />

var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 
'August', 'September', 'October', 'November', 'December'];
$("#month").autocomplete({
    source: months
});

Here we apply the autocomplete plugin to a month input, with the data being a plain JavaScript array.

When the data isn’t already available on the client side, the plugin can get it from a server-side resource:

$("#month").autocomplete({
    source: "addresses.php"
});

The plugin then sends a GET request to that resource, with the user-entered value appended as the q parameter, e.g., addresses.php?q=ma. As a response, the plugin expects a list of newline separated values:

Mainstreet
Mallstreet
Marketstreet

Discussion

The first decision to make when using the plugin is deciding on local or remote data.

With local data, the complete data set is already present in the browser’s memory. It could have been loaded as part of the page or via a separate Ajax request. In any case, it’s loaded just once. This mode is practical when the data is small and static—less than 500 rows of data—and doesn’t change while selecting a value. The big advantage of local data is that it’s extremely fast to find matching values.

Remote data is loaded from the server in small chunks (up to 100 rows per chunk makes sense). This works with both small data sets as well as very big data sets (say, more than half a million rows). As the data is loaded from the server, finding matching values is slower when compared to local data. This is mitigated by loading big enough chunks, which can then be filtered down on the client side without additional requests.

11.4. Selecting a Range of Values

Problem

Imagine a car search interface: the user inputs the price range that’s acceptable for him, and while changing the value, the list of available cars in that range is updated. The HTML form elements for that type of input—plain text input, radio buttons, selects—aren’t good enough. On the one hand, each requires an exact value. On the other, they fail to visualize the price range. It’s also not possible to move the entire range; instead, the user has to update both the start and end values, one by one.

Solution

The jQuery UI slider widget can transform two text inputs into a range slider. The start and end values of the range can be dragged using the mouse or using the cursor keys.

The default slider is applied to a simple <div>, with no options necessary:

<div id="slider"></div>

$("#slider").slider();

For that to work, jQuery, jQuery UI core, and the slider .js files must be included, in addition to a UI theme:

<link rel="stylesheet" href="ui.core.css" />
<link rel="stylesheet" href="ui.slider.css" />
<link rel="stylesheet" href="ui.theme.css" />
<script type="text/javascript" src="jquery-1.3.2.js"></script>
<script type="text/javascript" src="ui.core.js"></script>
<script type="text/javascript" src="ui.slider.js"></script>

While this adds a nice-looking slider to the page, it doesn’t yet really do anything useful.

In the case of the car search, we want to put the selected values into an input field and display them to the user:

<p>
    <label for="amount">Price range:</label>
    <input type="text" id="amount" style="border:0; color:#f6931f; 
font-weight:bold;" />
</p>

<div id="slider-range"></div>

Based on that markup, we can create a range slider:

var slider = $("#slider-range").slider({
    range: true,
    min: 0,
    max: 500,
    values: [75, 300],
    slide: function(event, ui) {
        $("#amount").val('$' + ui.values[0] + ' − $' + ui.values[1]);
    }
});
$("#amount").val('$' + slider.slider("values", 0) + ' − $' + slider.slider("values", 
1));

Setting the range option to true instructs the plugin to create two handles instead of just one. The min and max options specify the total range available; the values option the starting positions.

The slide callback is triggered when a handle is moved. Here it updates the amount input to display the selected price range.

Discussion

Binding a slider to a text input is one option; binding it to a select, and using the options of the select as values, is another.

Let’s take the example of a room reservation form where the user enters the minimum number of beds. The maximum number of beds is six; therefore, a slider isn’t a bad choice to start with. Using progressive enhancement, we can enhance the select with a slider and feed changes to the slider back to the <select> element:

<select name="minbeds" id="minbeds">
    <option>1</option>
    <option>2</option>
    <option>3</option>
    <option>4</option>
    <option>5</option>
    <option>6</option>
</select>

var select = $("#minbeds");
var slider = $('<div id="slider"></div>').insertAfter(select).slider({
    min: 1,
    max: 6,
    range: "min",
    value: select[0].selectedIndex + 1,
    slide: function(event, ui) {
        select[0].selectedIndex = ui.value − 1;
    }
});
$("#minbeds").click(function() {
    slider.slider("value", this.selectedIndex + 1);
});

Instead of using existing markup, which doesn’t have any semantic meaning, we generate the <div> on the fly and insert it into the DOM, right after the <select>.

We have a single value, so we use the value option instead of the values option. We initialize it with the selectedIndex of the select, using the DOM property directly. The property starts at zero, so we add one.

When the slider is updated, on keypresses or while dragging the handle with the mouse, the select is updated by setting its selectedIndex to the value read from the ui object passed to every ui event. The offset of one, which we added during initialization, is now subtracted.

We also set the range option, even though we have only one handle. It accepts a string parameter in addition to the Boolean: setting it to min displays the range from the start of the slider to the handle; setting it to max displays it from the end of the slider to the handle. This helps to visualize the minimum number of beds the hotel room should have.

Finally, we bind a click event to the select to update the slider when the select itself is changed directly by the user. We could also hide the select but would then need to add another form of label to display the selected numerical value.

The plugin also supports two more options, which weren’t covered in the example:

  • Setting animate: true animates the handle to move to the destination when clicking somewhere on the slider.

  • Setting orientation: vertical displays a vertical slider, instead of the horizontal default.

There are also more events with more fine-grained control:

  • start is called whenever sliding begins.

  • stop is called when sliding stops.

  • change is called when sliding stops and the slider value changes; this is especially useful when a change to the slider triggers an expensive operation, such as sending a request to the server, or updating a graph. Of course, it makes the slider behavior less obvious, because there isn’t instant feedback while sliding.

11.5. Entering a Range-Constrained Value

Problem

A slider is good at handling rough inputs and visualizing them but bad for gathering exact values. An example would be a pixel value in a layout component, where the value has to be tuned in very small increments, pixel by pixel. With a standard input, the keyboard has to be used: click the field, remove the current value and enter a new value, repeat for each increment.

Solution

The jQuery UI spinner widget can solve this problem by adding up and down buttons to the input to enable mouse interaction as well as handle keyboard events like cursor up and down.

All you need is a regular text input:

<input id="value" name="value" />

to which you then apply the spinner plugin:

$("#value").spinner();

This will create and position the up/down buttons and add the necessary keyboard handling events.

Use the spinner plugin to add buttons to in- and decrement the value, either by clicking the buttons or giving the input focus and using the cursor keys.

It also restricts the input to numeric values—when entering abc into the spinner, it’ll get replaced with the default value on blur. Unless specified otherwise, it’s a zero.

Discussion

The plugin offers a few options to restrict the input further:

  • min sets the lower limit, e.g., −10 or 100.

  • max sets the upper limit, e.g., 10 or 200.

  • stepping restricts the value to certain increments, e.g., 5; the default is 1.

When the spinner is used to input a currency value, the currency option can be used to display the appropriate symbol inside the input.

The following example puts these all together and creates a form for donating money:

<label for="currency">Currency</label>
<select id="currency" name="currency">
    <option value="$">US $</option>
    <option value="€">EUR €</option>
    <option value="¥">YEN ¥</option>
</select>
<br/>
<label for="amount">Select the amount to donate:</label>
<input id="amount" name="amount" value="5" />

We have a select for the currency and a text input for the amount:

var currency = $("#currency").change(function() {
    $("#amount").spinner("option", "currency", $(this).val()).blur();
});
$("#amount").spinner({
    currency: currency.val(),
    min: 5,
    max: 1000,
    step: 5
});

We bind a change event to the currency select to update the currency option of the spinner whenever the selection changes.

The spinner itself is initialized with the current value, as well as limits for min, max, and step, restricting the value somewhere between 5 and 1,000, with increments of 5, e.g., 10, 15, 20, and so on.

Google Maps integration

The value may also be a decimal number; in that case, the decimal option can be used to specify the number of allowed digits after the decimal point. In the following example, we display a Google map and use spinners to specify the latitude and longitude values.

To start with, we include the Google Maps API scripts:

<script type="text/javascript" src="http://maps.google.com/maps/api/js?
sensor=false"></script>

With that in place, we can add markup for the spinners and the actual map, along with some minimal styles:

<style>
    #map { width:500px; height:500px; }
</style>

<label for="lat">Latitude</label>
<input id="lat" name="lat" value="44.797916" />
<br/>
<label for="lng">Longitude</label>
<input id="lng" name="lng" value="-93.278046" />

<div id="map"></div>

Based on that, we can initialize the map and link it with the spinners:

function latlong() {
    return new google.maps.LatLng($("#lat").val(),$("#lng").val());
}
function position() {
    map.set_center(latlong());
}
$("#lat, #lng").spinner({
    precision: 6,
    change: position
});

var map = new google.maps.Map($("#map")[0], {
    zoom: 8,
    center: latlong(),
    mapTypeId: google.maps.MapTypeId.ROADMAP
});

The position function sets the center of the map to the latitude and longitude values obtained from the spinners. They are initialized with the decimal option set to 6, and passing the position function for the change option. With that, the map is updated whenever one of the spinners changes. Then the map itself is initialized, using the Google Maps API.

The drawback of the spinner in this case is that increments and decrements affect only the digits before the decimal point, so scrolling is rather rough. The increment option rounds any value below one up to one, so it can’t help here.

11.6. Uploading Files in the Background

Problem

File upload is part of many web applications but badly supported by browsers. The biggest problem is the lack of feedback of the upload status, while any action of the users disrupts the upload. A simple progress bar could improve the feedback but requires quite some work on the server side, while the problem of disruptive actions remains.

Solution

To improve the situation, file uploads should be performed in the background. This allows the application to continue accepting other user input.

The jQuery form plugin makes it trivial to switch from the native browser upload to Ajax background uploading. With this form:

<form id="uploadform">
    <input type="file" id="fileupload" name="fileupload" />
    <input type="submit" value="Upload!" />
</form>

all you need to add is a call to ajaxForm:

$("#uploadform").ajaxForm();

However, just doing the upload in the background without any feedback of the completed upload isn’t enough, so we use the success option to display an alert about the successful upload:

$("#uploadform").ajaxForm({
    success: function() {
        alert("Upload completed!");
    }
});

Discussion

The ajaxForm method binds itself to the submit event of the form, which allows it to also include the button used to submit the form in the Ajax request. The latter isn’t available when using ajaxSubmit. The ajaxSubmit method is useful on its own when the form submit is handled elsewhere, for example, by the validation plugin. To integrate validation and Ajax submit, ajaxSubmit should be used in the submitHandler option:

$("#commentform").validate({
    submitHandler: function(form) {
        $(form).ajaxSubmit({
            success: function() {
                $(form).clearForm();
                alert("Thanks for your comment!");
            }
        });
    }
});

In addition to the alert, the clearForm method, also provided by the form plugin, removes all values from the form. This makes it easy for the user to upload another file.

11.7. Limiting the Length of Text Inputs

Problem

It is common to limit the amount of characters in a textarea, like the 140 characters for Twitter or the 500 characters for a YouTube comment. Informing the user that he entered too much, after he submitted a form, is frustrating, so it makes sense to display an indicator of the available characters left.

Solution

The maxlength plugin solves this by adding a “Characters left: x” indicator in front or after the textarea. The plugin, after being applied on a text input or textarea, looks for an element with the class charsLeft to update with the count:

<form action="/comment">
  <p>Characters left: <span class="charsLeft">10</span></p>
  <textarea name="commentbody" maxlength="10"></textarea>
</form>

$('textarea').maxlength();

To make this less intrusive, we can create the necessary elements with jQuery, resulting in a simpler form markup:

<form action="/comment">
  <textarea name="commentbody" maxlength="10"></textarea>
</form>

var textarea = $('textarea'),
$('<p>Characters left: <span class="charsLeft">10</span></p>').insertBefore(textarea);
textarea.maxlength();

Discussion

In the case of Twitter, the textarea allows you to go over the 140-character limit, but you can’t submit. This helps a lot when pasting longer text that wouldn’t fit into the 140-character limit and editing it afterward. To get a similar effect with the maxlength plugin, we can set the hardLimit option to false. However, that doesn’t affect the actual submit but could be handled elsewhere, e.g., by the validation plugin (see Recipe 11.1).

The plugin also supports counting words instead of characters, by setting the words option to true.

Instead of having the plugin look for the default .charsLeft selector, we can also set the feedback option.

Here is another example using all three of these options:

<form action="/comment">
    <textarea name="commentbody" maxlength="10"></textarea>
    <p><span>x</span> characters left</p>
</form>

$('textarea').maxlength({
    feedback: "p>span",
    hardLimit: false,
    words: true
});

11.8. Displaying Labels Above Input Fields

Problem

A page layout doesn’t have enough space in front of an input element to display a label, the function of the input is obscured, and a title alone isn’t visible enough.

Search and login forms are often subject to space constraints. There just isn’t enough visual space to display a label in front of the input field. Though without the label, the function of the input is obscured. A title attribute isn’t enough to fix the problem, because it’s rather hard to spot, requiring the user to mouse over the input and rest there.

Solution

The most common example, the search field, can be solved by displaying “search” inside the field with a light gray to emphasize that it’s just a label, not the actual text to search for. When focusing the field, the text is removed. When blurring the field, the text is returned, unless something else was entered.

The less common example is a space-constrained login form, consisting of username and password fields. The password field needs to display the watermark as plain text, while the password to be entered (or prefilled by the browser) must still be obfuscated.

In both cases, the watermark shouldn’t be submitted as a value.

The watermark plugin solves this problem by displaying a label element above the actual input, hiding the label when the input gets focus, and displaying it again when the empty field is blurred.

Using a label above the field, instead of modifying the text inside the field, makes this solution also work with password fields and avoids having to clear watermark values on submit.

The default usage calls the watermark plugin method and passes the value to display:

$("#search").watermark("Search");

Discussion

Instead of passing the value to the plugin, it can also be specified as metadata, using the metadata plugin, in the markup, which is more practical when several watermarks are used or when those are generated on the server side:

<form id="loginform">
    <input type="text" id="email" name="email" 
class="{watermark:'E-Mail Address'}" />
    <input type="password" id="password" name="password" 
class="{watermark:'Your password'}" />
</form>

$("#loginform input").watermark();

Metadata has the drawback that it doesn’t build on progressive enhancement. To improve that, label elements should be used as for a normal form, with the plugin positioning the labels at the right position:

<form id="loginform">
    <div>
        <label for="email">E-Mail Address</label>
        <input type="text" id="email" name="email" />
    </div>
    <div>
        <label for="password">Your password</label>
        <input type="password" id="password" name="password" />
    </div>
</form>

In this case, the plugin is applied to the labels instead of the inputs:

$("#loginform label").watermark();

The plugin then uses the for attribute of each label to find the associated input and position it above the input.

11.9. Growing an Input with Its Content

Problem

A textarea is part of an interface and is often too large or too small, depending on the user’s input. Either it’s too big and other important elements get out of sight, or it’s too small and the user has to scroll too much.

Solution

Use the elastic plugin to start with a small default height and have the height autogrow when the user enters a certain amount of text.

Usage is plain and simple. Start with a textarea:

<textarea id="commentbody"></textarea>

And apply the plugin to it:

$("#commentbody").elastic();

Discussion

The plugin binds both a timer and a blur event to the textarea to look for changes. When the content changes, it copies the content into a hidden textarea with the same styles applied to it as the original, calculates the new height for that, and if it exceeds the current height of the original, starts an animation to adapt. This allows the textarea to both grow and shrink as content is added or removed.

An alternative is to let the user resize the textarea. Safari offers that by default for any textarea. The jQuery UI resizable plugin can add that to other browsers as well. Starting with the same textarea, we apply the resizable plugin, customizing the handle option to display only one handle on the bottom right:

$("#resizable").resizable({
    handles: "se"
});

With that and the jQuery UI base theme included, the handle gets displayed below the textarea. To move it into the bottom-right corner of the textarea, we have to add some CSS:

.ui-resizable-handle {
    bottom: 17px;
}

11.10. Choosing a Date

Problem

Date inputs are necessary for searching for events, flights, or hotels, or entering a birth date in a registration form. A common solution is to use three selects, for the day, month, and year components. While that works OK for a date of birth, it can get very cumbersome when searching for a flight in a certain time period.

Solution

The jQuery UI datepicker can solve the problem by offering a calendar together with a lot of customization options to optimize for various applications.

The default datepicker works by simply applying it to an input:

<label for="startAt">Start at:</label>
<input type="text" name="startAt" id="startAt" />

$("#startAt").datepicker();

This will bind the events necessary to show the datepicker when the input gets focused, starting with the current date. Next and previous buttons can be used to select the next or previous month, and a calendar can be used to select a day.

To make the datepicker more useful, we need to adapt it to the application where it’s used. For the flight-search example, we can assume that the user looks for a flight sometime in the next three months, and therefore it displays three months at once, starting with the next week from the current date:

<label for="from">From</label>
<input type="text" id="from" name="from"/>
<label for="to">to</label>
<input type="text" id="to" name="to"/>

We start with two inputs, each associated with an appropriate label, and then apply the datepicker to both:

var dates = $('#from, #to').datepicker({
    defaultDate: "+1w",
    changeMonth: true,
    numberOfMonths: 3,
    onSelect: function(selectedDate) {
        var option = this.id == "from" ? "minDate" : "maxDate";
        dates.not(this).datepicker("option", option, new Date(selectedDate));
    }
});

The default date for the datepicker is the current date plus one week, specified using the defaultDate option. A select for changing the months is displayed as well, via changeMonth: true. The option numberOfMonths: 3 indicates that three calendars should be displayed at once.

The onSelect option is an event triggered whenever the user selects a date. When the from date is selected, the minDate option for the to date is set to the from date, and when the to date is selected, the maxDate option for the from date is set.

With that in place, the user can start selecting any of the two dates, and when he continues to select the other, the input is restricted to a positive range already.

Discussion

By default, the datepicker is shown when the input field receives focus. Using the showOn option, we can configure the calendar to appear only when clicking a calendar icon next to the input:

$("#datepicker").datepicker({
    showOn: 'button',
    buttonImage: 'images/calendar.gif',
    buttonImageOnly: true
});

The buttonImage option specifies the path to an image to use as the button, where buttonImageOnly specifies to use only that image, instead of a button element with an embedded image.

The showOn option also supports both as a value, displaying the datepicker on focus of the input and on clicks on the button.

Localization

The jQuery UI datepicker supports 41 locales, provided as ui.datepicker-xx.js files, where xx is the locale. Each file adds a property to $.datepicker.regional. The ui.datepicker-ar.js file adds these:

$.datepicker.regional['ar'] = {
    closeText: 'إغلاق',
    prevText: '&#x3c;السابق',
    nextText: 'التالي&#x3e;',
    currentText: 'اليوم',
    dayNames: ['السبت', 'الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخم
يس', 'الجمعة'],
    dayNamesShort: ['سبت', 'أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خم
يس', 'جمعة'],
    dayNamesMin: ['سبت', 'أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خم
يس', 'جمعة'],
    dateFormat: 'dd/mm/yy',
    firstDay: 0,
    isRTL: true
};

To initialize a datepicker with the Arabic locale, we refer to that property:

$("#datepicker").datepicker($.datepicker.regional.ar);

To mix in other options as well, we use $.extend:

$("#datepicker").datepicker($.extend({}, $.datepicker.regional.ar, {
    changeMonth: true,
    changeYear: true
});

We create an empty object literal via {} and then use $.extend to copy the regional options as well as values for changeMonth and changeYear into the empty object, which is then used to initialize the datepicker.

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

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