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.
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.
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.
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 john.doe@gmail,com. 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"
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.
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 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
.
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).
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"); } }
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.
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 });
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.”
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.
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' });
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).
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.
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
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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!"); } });
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.
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.
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();
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 });
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.
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");
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.
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.
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();
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; }
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.
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.
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.
The jQuery UI datepicker supports 41 locales, provided
as ui.datepicker-
files, where xx
.jsxx
is the locale. Each file
adds a property to $.datepicker.regional
. The ui.datepicker-ar.js
file adds
these:
$.datepicker.regional['ar'] = { closeText: 'إغلاق', prevText: '<السابق', nextText: 'التالي>', 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.
18.118.31.67