CHAPTER 13

image

Working with Forms

In this chapter, I will show you the jQuery support for working with HTML forms. In part, I will recap the form-related events and the jQuery methods you can use to manage them, but most of this chapter is dedicated to a plug-in that provides a great mechanism for validating the values that users enter into a form before it is submitted to a server. If you have written any kind of form-based web application, you will have realized that users will enter all sorts of data into a form, so validation is an important process.

I begin this chapter by introducing the Node.js server script that you will use in this part of the book. For this chapter, the script doesn’t do a great deal other than show you the data values that were entered into the form, but in later chapters I’ll start to rely on Node.js a little more. Table 13-1 provides the summary for this chapter.

Table 13-1. Chapter Summary

Problem Solution Listing
Set up the Node.js server. Use the script listed in this chapter (and included in the source code that accompanies this book). 1, 2
Respond to the focus being gained or lost by a form element. Use the focus and blur methods. 3
Respond to changes in the value that the user has entered into a form element. Use the change method. 4
Respond to (and interrupt) the user submitting the form. Use the submit method. 5, 6
Validate the values in a form. Use the validation plug-in. 7
Configure the validation plug-in. Pass a map object to the validate method. 8
Define and apply validation rules using a class. Use the addClassRules and addClass methods. 9–12
Apply validation rules directly to elements. Use the rules method. 13, 14
Apply validation rules using element names. Add a rules property to the options object. 15
Apply validation rules using element attributes. Define attributes that correspond to individual validation checks. 16
Define custom messages for rules applied via element names and attributes. Add a message property to the options object, set to a map object that defines the custom messages. 17, 18
Define custom messages for rules applied directly to elements. Include a map object defining the messages as an argument to the rules method. 19
Create a custom validation check. Use the addMethod method. 20, 21
Format the validation messages. Use the highlight, unhighlight, errorElement, and errorClass properties of the options object. 22–26
Use a validation summary. Use the errorContainer and errorLabelContainer properties. 27
Compose error messages using templates. Use the $.validator.format method. 28

Preparing the Node.js Server

In this chapter, I will be using Node.js to receive and process form data from the browser. I don’t want to get drawn into the details of how Node.js functions, but one of the reasons that I selected it for this book was because Node.js is built around JavaScript, which means you can use the same skills for server-side programming as you do for client-side programming.

image Tip  If you want to re-create the example in this chapter, see Chapter 1 for details of how to obtain Node.js. You can download the formserver.js server-side script along with all of the examples for this chapter from Apress.com.

Listing 13-1 shows the server-side script that you will use in this chapter, which I have saved in a file called formserver.js. I present this as a black box and explain only the inputs and output.

Listing 13-1.  The formserver.js Node.js Script

var http = require("http");
var querystring = require("querystring");
  
var port = 80;
  
http.createServer(function (req, res) {
    console.log("[200 OK] " + req.method + " to " + req.url);
  
    if (req.method == "POST") {
        var dataObj = new Object();
        var cType = req.headers["content-type"];
        var fullBody = "";
          
        if (cType && cType.indexOf("application/x-www-form-urlencoded") > -1) {
            req.on("data", function(chunk) { fullBody += chunk.toString();});
            req.on("end", function() {
                res.writeHead(200, "OK", {"Content-Type": "text/html"});
                res.write("<html><head><title>Post data</title></head><body>");
                res.write("<style>th, td {text-align:left; padding:5px; color:black} ");
                res.write("th {background-color:grey; color:white; min-width:10em} ");
                res.write("td {background-color:lightgrey} ");
                res.write("caption {font-weight:bold}</style>");
                res.write("<table border='1'><caption>Form Data</caption>");
                res.write("<tr><th>Name</th><th>Value</th>");
                var dBody = querystring.parse(fullBody);
                for (var prop in dBody) {
                    res.write("<tr><td>" + prop + "</td><td>"
                        + dBody[prop] + "</td></tr>");
                }
                res.write("</table></body></html>");
                res.end();
            });
        }
    }
  
}).listen(port);
console.log("Ready on port " + port);

To run this script, I enter the following at the command line:

node.exe formserver.js

The command will be different if you are using another operating system. See the Node.js documentation for details. To demonstrate the Node.js functionality I will use the example document shown in Listing 13-2, which I saved as example.html.

Listing 13-2.  The Example Document for This Chapter

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="handlebars.js" type="text/javascript"></script>
    <script src="handlebars-jquery.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <script id="flowerTmpl" type="text/x-handlebars-template">
        {{#each flowers}}
        <div class="dcell">
            <img src="{{product}}.png"/>
            <label for="{{product}}">{{name}}: </label>
            <input name="{{product}}" value="0" required />
        </div>
        {{/each}}
    </script>
    <script type="text/javascript">
        $(document).ready(function () {
  
            var data = { flowers: [
               { name: "Aster", product: "aster", stock: "10", price: "2.99" },
               { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
               { name: "Rose", product: "rose", stock: "2", price: "4.99" },
               { name: "Peony", product: "peony", stock: "0", price: "1.50" },
               { name: "Primula", product: "primula", stock: "1", price: "3.12" },
               { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
            };
  
            var templResult = $("#flowerTmpl").template(data).filter("*");
            templResult.slice(0, 3).appendTo("#row1");
            templResult.slice(3).appendTo("#row2");
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post"action="http://node.jacquisflowershop.com/order">
        <div id="oblock">
            <div class="dtable">
                <div id="row1" class="drow">
                </div>
                <div id="row2"class="drow">
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

The individual product elements are generated using data templates (as described in Chapter 12). I have specified a value for the action attribute of the form element, which means it will post to the following URL:

I am using two different servers. The first server (www.jacquisflowershop.com) is the one that has been serving up HTML content throughout this book. It delivers the static content such as HTML documents, scripts files, and images. For me, this is Microsoft’s IIS, but you can use any server that appeals to you. (I use IIS because a lot of my books are about Microsoft web technologies, and I already have a server set up and ready to go.)

The second server (node.jacquisflowershop.com) runs Node.js (using the formserver.js script shown previously), and when you submit the form in the example document, this is where the data will be sent. In this chapter, I don’t care a great deal about what the server does with the data it receives: I will be focused on the form itself. In Figure 13-1, you can see that I have entered some values into the input elements in the document.

9781430263883_Fig13-01.jpg

Figure 13-1. Entering data into the input elements

When I click the Place Order button, the form is submitted to the Node.js server, and a simple response is sent back to the browser, as shown in Figure 13-2.

9781430263883_Fig13-02.jpg

Figure 13-2. The response from the Node.js server

I know this is not an interesting response, but I just need somewhere to send the data for now, and I don’t want to get drawn off track into the world of server-side development.

Recapping the Form-Event Methods

jQuery includes a set of methods that deal with form-related events. It is worth recapping these now that you are specifically looking at forms. Table 13-2 describes the methods and the events to which they correspond.

Table 13-2. The jQuery Form-Event Methods

Method Event Description
blur(function) Blur Triggered when a form element loses the focus.
change(function) Change Triggered when the value of a form element changes.
focus(function) Focus Triggered when the focus is given to a form element.
select(function) Select Triggered when the user selects text within a form element.
submit(function) Submit Triggered when the user wants to submit the form.

image Tip  Don’t forget that jQuery defines a set of extension selectors that match form elements. See Chapter 5 for details.

Dealing with Form Focus

The blur and focus methods allow you to respond to changes in the focus. A common use for these features is to help the user by emphasizing which element has the focus (and thus which element will receive input from the keyboard). Listing 13-3 provides a demonstration.

Listing 13-3.  Managing Form Element Focus

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        function handleFormFocus(e) {
            var borderVal = e.type == "focus" ? "medium solid green" : "";
            $(this).css("border", borderVal);
        }
        $("input").focus(handleFormFocus).blur(handleFormFocus);
    });
</script>
...

In this example, I select all of the input elements and register the handleFormFocus function as the handler for both the focus and blur events. The function applies a green border when an element gains the focus and removes it when the focus is lost. You can see the effect in Figure 13-3.

9781430263883_Fig13-03.jpg

Figure 13-3. Emphasizing the focused element

Notice that I used the input selector. In other words, I selected the elements by tag. jQuery provides the extension selector :input (I described the extension selectors in Chapter 5), but the extension selector matches elements more broadly and will match button elements that are capable of submitting the form, which means if I had used use the extension selector, the border would have been applied to the button as well as the actual input elements. You can see the difference when the button is focused in Figure 13-4. Which selector you use is a matter for personal preference, but it is useful to be aware of the difference.

9781430263883_Fig13-04.jpg

Figure 13-4. The difference between the input and :input selectors

Dealing with Value Changes

The change event is triggered when the user changes the value in a form element. This is a useful event if you are providing cumulative information based on the values in the form. Listing 13-4 shows how you can use this event to track the total number of items selected in the flower shop document. This is the same approach I took when I refactored the example at the end of Part II of this book.

Listing 13-4.  Responding to the Change Event

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        function handleFormFocus(e) {
            var borderVal = e.type == "focus" ? "medium solid green" : "";
            $(this).css("border", borderVal);
        }
        $("input").focus(handleFormFocus).blur(handleFormFocus);
  
        var total = $("#buttonDiv")
            .prepend("<div>Total Items: <span id=total>0</span></div>")
            .css({clear: "both", padding: "5px"});
        $("<div id=bbox />").appendTo("body").append(total).css("clear: left");
          
        $("input").change(function (e) {
            var total = 0;
            $("input").each(function (index, elem) {
                total += Number($(elem).val());
            });
            $("#total").text(total);
        });
    });
</script>
...

In this example, I respond to the change event by totaling the values in all of the input elements and displaying the result in the span element that I had previously added to the document.

image Tip  Notice that I use the val method to get the value from the input elements.

Dealing with Form Submission

A lot of the more advanced activities you can perform with forms arise from the way you can prevent the browser’s default form mechanism from working. Listing 13-5 provides a simple demonstration.

Listing 13-5.  Intercepting the Form Submission

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        $("form").submit(function (e) {
            if ($("input").val() == 0) {
                e.preventDefault();
            }
        });
    });
</script>
...

I register a handler function for the submit event. This event will be triggered when the user clicks the Place Order button. If the value of the first input element is 0, I call the preventDefault method to interrupt the default action of the form, which is to submit the data to the server. For any other value, the form is submitted.

image Tip  As an alternative, you can return false from the event handler function to achieve the same effect.

There are two different to submit a form programmatically. You can use the jQuery submit method without any arguments and you can use the submit method, which is defined for form elements by the HTML5 specification. Listing 13-6 shows both approaches in use.

Listing 13-6.  Explicitly Submitting a Form

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        $("form").submit(function (e) {
            if ($("input").val() == 0) {
                e.preventDefault();
            }
        });
  
        $("<button>jQuery Method</button>").appendTo("#buttonDiv").click(function (e) {
            $("form").submit();
            e.preventDefault();
        });
  
        $("<button>DOM API</button>").appendTo("#buttonDiv").click(function (e) {
            document.getElementsByTagName("form")[0].submit();
            e.preventDefault();
        });
    });
</script>
...

I have added two button elements to the document. The one that uses the jQuery submit method triggers the submit event, which I set up a handler function for in the last example. This means that if the value of the first input element is zero, the form won’t be submitted.

The button elements that uses the DOM API and calls the submit method defined by the form element effectively bypasses the event handler because the submit event isn't triggered, and this means that the form will always be submitted, irrespective of the value of the first input element.

image Tip  My advice is to stick to the jQuery methods, of course, but if you do use the DOM method, at least you will understand the results you get.

Validating Form Values

The main reason for interrupting and preventing the browser from submitting data to the server is that you want to validate the values that a user has entered into the form. At some point, every web programmer realizes that users will type anything at all into an input element, and it is unwise to assume that your users will provide useful and meaningful data. There are an infinite number of different values you might have to process, but in my experience there are only a few reasons why the user gives you something unexpected in a form.

The first reason is that the user doesn’t understand what data you are after. You might have asked for the name on the credit card, but the user might have entered her card number, for example.

The second reason is that the user doesn’t want to give you the information you have requested and is just trying to get through the form as quickly as possible. She’ll enter anything that will move her to the next stage in the process. If you have a lot of new users whose e-mail address is [email protected], then you know that this is the problem.

The third reason is that you are asking for information the user doesn’t have, such as asking a U.K. resident which state he lives in. (We don’t have states here. I am looking at you, NPR. No donation for you.)

The final reason is that the user has made a genuine mistake, typically a typo. For example, I am a quick but inaccurate typist, and I often type my surname as Freman instead of Freeman, missing out an e.

There is nothing you can do about typos, but the way that you deal with the other three reasons can make the difference between creating a smooth and seamless application and something that annoys and angers users.

I don’t want to get into a long rant about the design of web forms, but I do want to say that the best way of approaching this issue is to focus on what the user is trying to achieve. And when things go wrong, try to see the problem (and the required resolution) the way the user sees it. Your users don’t know about how you have built your systems, and they don’t care about your business processes; they just want to get something done. Everyone can be happy if you keep the focus on the task the user is trying to complete and don’t needlessly punish her when she don’t give you the data you want.

jQuery provides you with all the tools you need to create your own system to validate data values, but I recommend a different approach. One of the most popular jQuery plug-ins is called Validation, and as you guess from the name, it handles form validation.

image Caution  What I am discussing in this chapter is client-side validation. This is a complement to rather than a ­replacement for server-side validation, where you check the data as it is received by the server. Client-side ­validation is for the benefit of the user: to stop him from having to make repeated submissions to the server to discover and correct data errors. Server-side validation is for the benefit of the application and ensures that bad data doesn’t cause ­problems. You must use both: it is trivial to bypass client-side validation, and it does not provide reliable protection for your ­application.

You can download the validation plug-in from http://jqueryvalidation.org or use the version that I included in the source code download for this book (available at Apress.com). Listing 13-7 shows the use of this plug-in. (As I write this, the current version is 1.1.1.)

Listing 13-7.  Using the Form Validation Plug-in

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <script src="jquery-2.0.2.js" type="text/javascript"></script>
    <script src="handlebars.js" type="text/javascript"></script>
    <script src="handlebars-jquery.js" type="text/javascript"></script>
    <script src="jquery.validate.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="styles.css"/>
    <style type="text/css">
        .errorMsg {color: red}
        .invalidElem {border: medium solid red}
    </style>
    <script id="flowerTmpl" type="text/x-handlebars-template">
        {{#each flowers}}
        <div class="dcell">
            <img src="{{product}}.png"/>
            <label for="{{product}}">{{name}}: </label>
            <input name="{{product}}" value="0" required />
        </div>
        {{/each}}
    </script>
    <script type="text/javascript">
        $(document).ready(function () {
  
            var data = { flowers: [
                { name: "Aster", product: "aster", stock: "10", price: "2.99" },
                { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
                { name: "Rose", product: "rose", stock: "2", price: "4.99" },
                { name: "Peony", product: "peony", stock: "0", price: "1.50" },
                { name: "Primula", product: "primula", stock: "1", price: "3.12" },
                { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
            };
  
            var templResult = $("#flowerTmpl").template(data).filter("*");
            templResult.slice(0, 3).appendTo("#row1");
            templResult.slice(3).appendTo("#row2");
  
            $("form").validate({
                highlight: function (element, errorClass) {
                    $(element).add($(element).parent()).addClass("invalidElem");
                },
                unhighlight: function (element, errorClass) {
                    $(element).add($(element).parent()).removeClass("invalidElem");
                },
                errorElement: "div",
                errorClass: "errorMsg"
            });
  
            $.validator.addClassRules({
                flowerValidation: {
                    min: 0
                }
            })
  
            $("input").addClass("flowerValidation").change(function (e) {
                $("form").validate().element($(e.target));
            });
        });
    </script>
</head>
<body>
    <h1>Jacqui's Flower Shop</h1>
    <form method="post" action="http://node.jacquisflowershop.com/order">
        <div id="oblock">
            <div class="dtable">
                <div id="row1" class="drow">
                </div>
                <div id="row2"class="drow">
                </div>
            </div>
        </div>
        <div id="buttonDiv"><button type="submit">Place Order</button></div>
    </form>
</body>
</html>

The plug-in is distributed as a zip file, which you will need to decompress. Copy the jquery.validate.js file from the dist folder so that it is in the same directory as the example.html file.

image Tip  There are a lot of different configuration options for the validation plug-in. In this chapter, I have focused on those that are most frequently used and that cover the broadest range of situations. If they don’t suit you, I suggest exploring some of the other options, which are described in the documentation available in the plug-in download.

image Note  HTML5 includes support for some form validation. It is a good start, but it is pretty basic, and there are still significant differences in the way that browsers interpret the specification. Until the scope, richness, and consistency of the HTML5 features improve, I recommend sticking with jQuery for form validation.

Importing the JavaScript File

The first thing I have to do is bring the template plug-in JavaScript file into the document with a script element, as follows:

...
<script src="jquery-2.0.2.js" type="text/javascript"></script>
<script src="handlebars.js" type="text/javascript"></script>
<script src="handlebars-jquery.js" type="text/javascript"></script>
<script src="jquery.validate.js" type="text/javascript"></script>
...

image Tip  I have used the debug version of the file, but there is a minimized version available, and some of the CDN services host this file because it is so popular.

Configuring the Validation

The next step is to configure the validation of the form element, which I do by calling the validate method on the form elements on which validation should be performed. The argument to the validate method is a map object that contains configuration settings, as shown in Listing 13-8.

Listing 13-8.  Configuring the Validation

...
$("form").validate({
    highlight: function(element, errorClass) {
        $(element).add($(element).parent()).addClass("invalidElem");
    },
    unhighlight: function(element, errorClass) {
        $(element).add($(element).parent()).removeClass("invalidElem");
    },
    errorElement: "div",
    errorClass: "errorMsg"
});
...

I have specified values for four options (highlight, unhighlight, errorElement, and errorClass); I’ll come back to these later in the chapter and explain their meaning.

Defining the Validation Rules

A lot of the flexibility of the validation plug-in comes from the way that rules to test for valid input can be quickly and easily defined. There are various ways of associating rules with elements and the one I tend to use works through classes. I define a set of rules and associate them with a class, and when the form is validated the rules are applied to any element contained with the form that is a member of the specified class. I created a single rule in the example, as shown in Listing 13-9.

Listing 13-9.  Defining a Validation Rule

...
$.validator.addClassRules({
    flowerValidation: {
        min: 0
    }
})
...

In this case, I created a rule that will be applied to elements that are members of the flowerValidation class. The rule is that the value should be equal to or greater than zero. I have expressed the condition in the rule using min. This is just one of a number of convenient predefined checks that the validation plug-in provides, and I’ll describe all of them later in the chapter.

Applying the Validation Rules

Validation rules are associated with the elements in the form by adding the element to the class specified in the previous step. This provides the ability to tailor the validation for different kinds of elements in a form. For this example, all of the elements are to be treated the same, so I use jQuery to select all of the input elements and add them to the flowerValidation class, as shown in Listing 13-10.

Listing 13-10.  Adding the Input Elements to the Class Associated with Validation

...
$("input").addClass("flowerValidation").change(function(e) {
    $("form").validate().element($(e.target));
});
...

I have also set up a handler function for the change event to explicitly validate the element whose value has changed. This ensures that the user gets immediate feedback if he corrects an error. You can see the effect of the validation plug-in in Figure 13-5. To create this figure, I entered -1 in one of the input fields and clicked the Place Order button.

9781430263883_Fig13-05.jpg

Figure 13-5. Using the validation plug-in

image Tip  The text of the message shown to the user is generated by the validation plug-in. I show you how to customize these messages later in the chapter.

The validation plug-in displays an error message, and the user is not able to submit the form until the problem has been resolved. The error message provides the user with guidance as to how the problems can be resolved. (The default messages, such as the one shown in the figure are a bit generic, but later in the chapter I’ll show you how to change the text.)

Using the Validation Checks

The validation plug-in supports a wide range of checks that you can use to validate form values. You saw the min check in the previous example. This ensures that the value is greater to or equal to a specified numeric value. Table 13-3 describes the set of checks you can perform.

Table 13-3. Validation Plug-in Checks

Checks Description
creditcard: true The value must contain a credit card number.
date: true The value must be a valid JavaScript date.
digits: true The value must contain only digits.
email: true The value must be a valid e-mail address.
max: maxVal The value must be at least as large as maxVal.
maxlength: length The value must contain no more than length characters.
min: minVal The value must be at least as large as minVal.
minlength: length; The value must contain at least length characters.
number: true The value must be a decimal number.
range: [minVal, maxVal] The value must be between minVal and maxVal.
rangelength: [minLen, maxLen] The value must contain at least minLen and no more than maxLen characters.
required: true; A value is required.
url: true The value must be a URL.

You can associate multiple rules together in a single rule. This allows you to perform complex validations in a compact and expressive way.

image Tip  Included in the validation plug-in distribution zip file is a file called additional-methods.js. This file defines some additional checks, including U.S. and U.K. phone numbers, IPv4 and IPv6 addresses, and some additional date, e-mail, and URL formats.

You can apply these checks to your elements in several ways. I describe each in the sections that follow.

image Note  The Validation plug-in also supports remote validation, where the data the user has entered into a field is checked using a remote server. This is useful when you need to check with data that cannot be distributed to the client, because it would be either insecure or impractical (such as checking that a username hasn’t already been used). I demonstrate remote validation in Chapter 16, after I introduce the features it relies on in Chapters 14 and 15.

Applying Validation Rules via Classes

As I explained earlier, the validation technique I use most frequently is to apply checks through classes and this is the approach I took in the example. I am not limited to a single check, however, and I can apply multiple checks together to validate different aspects of the value that the user provides, as demonstrated in Listing 13-11.

Listing 13-11.  Combining Multiple Checks in a Single Rule

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });
  
        $.validator.addClassRules({
            flowerValidation: {
                required: true,
                digits: true,
                min: 0,
                max: 100
            }
        });
  
        $("input").addClass("flowerValidation").change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

In this example, I have combined the required, digits, min, and max checks to ensure that the user provides a value that comprises only digits and that falls within the range of 0 to 100.

Notice that I associate the rule with the class using the addClassRules method. The argument to this method is one or more sets of checks and the class name they are to be applied to. As shown, you call the addClassRules method on the validator property of the main jQuery $ function.

Each validated form element is assessed individually, which means the user can be presented with different error messages for different problems, as shown in Figure 13-6.

9781430263883_Fig13-06.jpg

Figure 13-6. Applying multiple validation checks to form elements

I have entered several values that will fail the checks. It is important to note that the checks are performed in the order they are defined in the rule. If you look at the error message for the Rose product, you will see that it has failed the digits check. If you rearrange the order of the checks, you can get a different error. Listing 13-12 shows the validation checks arranged in a different order.

Listing 13-12.  Changing the Order in Which Checks Are Applied

...
$.validator.addClassRules({
    flowerValidation: {
        required: true,
        min: 0,
        max: 100,
        digits: true
    }
})
...

In this example, I have moved the digits check to the end of the rule. If I enter -1 into a form field now, it is the min check that will fail, as demonstrated by Figure 13-7.

9781430263883_Fig13-07.jpg

Figure 13-7. Changing the order in which the checks are applied during validation

Applying Validation Rules Directly to Elements

The next technique allows you to apply rules to a single element, as shown in Listing 13-13.

Listing 13-13.  Applying Validation Rules to the Elements in a Selection

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });
  
        $.validator.addClassRules({
            flowerValidation: {
                required: true,
                min: 0,
                max: 100,
                digits: true,
            }
        })
  
        $("#row1 input").each(function (index, elem) {
            $(elem).rules("add", {
                min: 10,
                max: 20
            })
        });
  
        $("input").addClass("flowerValidation").change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

I call the rules method on a jQuery object, passing in the string add and a map object with the checks you want to perform and their arguments. The rules method operates on only the first element in the selection, so I have to use the each method to apply the rules more broadly. In this case, I selected all of the input elements that are descendants of the row1 element and applied the min and max checks to ensure that the user enters a value between 10 and 20.

image Tip  You can remove rules from elements by replacing add with remove when you call the rules method.

Rules applied to elements using the rules methods are evaluated before those applied using a class. For my example, this means the input elements on the top row will be checked using a min value of 10 and a max value of 20, while the other input elements will use values of 0 and 100, respectively. You can see the effect of this in Figure 13-8.

9781430263883_Fig13-08.jpg

Figure 13-8. Applying rules directly to elements

Because I am dealing with validation for each element individually, I can tailor the checks even further, as demonstrated by Listing 13-14.

Listing 13-14.  Tailoring Checks for Elements

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });
  
        $("input").each(function (index, elem) {
            var rules = {
                required: true,
                min: 0,
                max: data.flowers[index].stock,
                digits: true
            }
            if (Number(data.flowers[index].price) > 3.00) {
                rules.max--;
            }
            $(elem).rules("add", rules);
        });
  
        $("input").addClass("flowerValidation").change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

In this example, I tailor the value of the max check using the data object that I added to the document to generate elements using the template. The value for the max check is set based on the stock property and adjusted down if the price is greater than $3. When you have data like this, you are able to perform much more useful validation. You can see the effect of this change in Figure 13-9.

9781430263883_Fig13-09.jpg

Figure 13-9. Setting different values for validation checks based on data

Applying Validation Rules via the Element Name Attribute

Validation rules can also be applied to elements based on the value of the name attribute. Nothing in the HTML specification requires the name attribute value to be unique, and a single value is often used to categorize a group of form elements. In my flower shop example document, each name is different and corresponds to a specific product. Either way, you can create rules that correspond to a name attribute value and rules that apply to all elements assigned that value. Listing 13-15 gives a demonstration.

Listing 13-15.  Assigning Validation Rules Based on Element Name

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        var rulesList = new Object();
        for (var i = 0; i < data.flowers.length; i++) {
            rulesList[data.flowers[i].product] = {
                min: 0,
                max: Number(data.flowers[i].stock),
            }
        }
  
        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg",
            rules: rulesList
        });
  
        $("input").change(function (e) {
            $("form").validate().element($(e.target));
        });
  
    });
</script>
...

I added rules that rely on element names using the rules property of the configuration object I pass to the validate method when I set up form validation. Notice that I have used just the data object to create the set of rules and also that the product property in the data object is used to generate the name attribute on the input elements. Also notice that I have to use Number to convert the string data value so that it is processed correctly.

image Tip  I tend not to use this approach in my own projects, since I would rather work directly with the elements in the document, but this technique can be handy if you have a data object and want to set up validation before the form elements have been added to the document.

Applying Validation Rules Using Element Attributes

The final way to apply validation checks to elements is to use attributes. The validation plug-in examines form elements to see whether they define attributes that correspond to the name of the built-in checks, so an element that defines a required attribute is assumed to need the required check. Listing 13-16 provides a demonstration.

Listing 13-16.  Performing Validation Using Element Attributes

...
<script id="flowerTmpl" type="text/x-handlebars-template">
    {{#each flowers}}
    <div class="dcell">
        <img src="{{product}}.png"/>
        <label for="{{product}}">{{name}}: </label>
        <input name="{{product}}" value="0" required min="0" max="{{stock}}"/>
    </div>
    {{/each}}
</script>
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });
  
        $("input").change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

I like this technique when it is used in conjunction with a data template, but I find it clutters up a document when applied to statically defined elements because the same attributes are applied to elements over and over again.

Specifying Validation Messages

The validation plug-in defines a default error message for all of the built-in checks, but these are generic and not always useful to the user. As a simple example, if I set a max check with a value of 10 and the user enters 20 in the field, then the error message will be as follows:

Please enter a value less than or equal to 12

This message describes the constraint you have applied on a form element, but it doesn’t provide any guidance to the user as to why there is a limit. Fortunately, you can change these messages to provide some additional context and tailor the message to your needs. The method used to change the messages depends on how you validation rule was created in the first place. It isn't possible to change the messages when you apply rules using a class, but in the following sections I describe how to define custom messages for the other techniques.

Specifying Messages for Attribute and Name Validation

When relying on the name attribute or on check attributes to associate rules with elements, you can change the messages displayed to the user by adding a messages property to the options object passed to the validate method. Listing 13-17 provides a demonstration.

Listing 13-17.  Using the messages Property on the options Object

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg",
            messages: {
                rose: { max: "We don't have that many roses in stock!" },
                primula: { max: "We don't have that many primulas in stock!" }
            }
        });
  
        $("input").change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

The validation for this example is applied through the min and max attributes applied to the input element in the template, and you can see the structure of the object that sets out the values for the messages property in the JavaScript code.

Within the messages object, I define a property using the name of the element I am interested in and set the value of this property to be a map between the check and the new message you want to use. In this example, I have changed the message for the max check on the elements with the names rose and primula. You can see the effect in Figure 13-10, which illustrates how these custom messages are displayed.

9781430263883_Fig13-10.jpg

Figure 13-10. Changes messages via the options object

The syntax for setting up these validation messages can be duplicative, so I tend to create an object with the messages I want programmatically, as shown in Listing 13-18.

Listing 13-18.  Defining Custom Messages Programmatically

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        var customMessages = new Object();
        for (var i = 0; i < data.flowers.length; i++) {
            customMessages[data.flowers[i].product] = {
                max: "We only have " + data.flowers[i].stock + " in stock"
            }
        }
  
        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg",
            messages: customMessages
        });
  
        $("input").change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

In this example, I incorporate the stock property from the data objects to give a more meaningful message to the user, as shown in Figure 13-11.

9781430263883_Fig13-11.jpg

Figure 13-11. Generating custom validation messages programmatically

Specifying Messages for Per-Element Validation

When applying rules to individual elements, you pass in a messages object that defines the messages you want for your checks. Listing 13-19 shows how this is done.

Listing 13-19.  Specifying Messages for Rules Applied on a Per-Element Basis

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = { flowers: [
            { name: "Aster", product: "aster", stock: "10", price: "2.99" },
            { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
            { name: "Rose", product: "rose", stock: "2", price: "4.99" },
            { name: "Peony", product: "peony", stock: "0", price: "1.50" },
            { name: "Primula", product: "primula", stock: "1", price: "3.12" },
            { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg",
        });
  
        $("input").change(function (e) {
            $("form").validate().element($(e.target));
        }).each(function (index, elem) {
            $(elem).rules("add", {
                messages: {
                    max: "We only have " + data.flowers[index].stock + " in stock"
                }
            })
        });
    });
</script>
...

Once again, I have used the stock property from the corresponding flowers data object to define the message. For simplicity, I have assumed that the input elements are ordered in the same way that the data items are ordered. You can see the effect of these messages in Figure 13-12.

9781430263883_Fig13-12.jpg

Figure 13-12. Specifying messages that are derived from the data object

image Tip  I have only specified the messages using JavaScript. The min and max rules are still applied to the input elements via the template, as shown in Listing 13-17.

Creating a Custom

You can create a custom validation check if the built-in ones don’t suit your needs. This is a simple process that means you can closely relate validation to the structure and nature of your web application. Listing 13-20 provides a demonstration.

Listing 13-20.  Creating a Custom Validation Check

...
<script id="flowerTmpl" type="text/x-handlebars-template">
    {{#each flowers}}
    <div class="dcell">
        <img src="{{product}}.png"/>
        <label for="{{product}}">{{name}}: </label>
        <input name="{{product}}" value="0" required />
    </div>
    {{/each}}
</script>
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = {
            flowers: [
                { name: "Aster", product: "aster", stock: "10", price: "2.99" },
                { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
                { name: "Rose", product: "rose", stock: "2", price: "4.99" },
                { name: "Peony", product: "peony", stock: "0", price: "1.50" },
                { name: "Primula", product: "primula", stock: "1", price: "3.12" },
                { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });
  
        $.validator.addMethod("stock", function (value, elem, args) {
            return Number(value) < Number(args);
        }, "We don't have that many in stock");
  
        $("input").each(function (index, elem) {
            $(elem).rules("add", {
                stock: data.flowers[index].stock
            })
        }).change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

I have removed the min and max attributes from the input elements in the template and introduced a custom validation check in the JavaScript code. (You can mix and match custom and built-in validation freely, but the example in this listing duplicates the functionality of the max validator.)

Custom checks are created using the addMethod method, which is called on the validator property of the $ function. The arguments to this method are the name you want to assign the check, a function that is used to perform validation, and a message to show if validation fails. In this example, I have defined a check called stock, which I explain in the sections that follow.

Defining the Validation Function

The arguments to the custom validation function are the value entered by the user, the HTMLElement object representing the form element, and any arguments that were specified when the check is applied to an element for validation, like this:

...
$(elem).rules("add", {
    min: 0,
    stock:data.flowers[index].stock
})
...

When I applied the rule, I specified the value of a stock property from the flower data object that corresponds to the input element as the argument to the check. This is passed as is to the custom validation function:

...
function(value, elem,args) {
    return Number(value) <= Number(args);
}
...

The value and the arguments are presented as strings, and that means I have to use the Number type to ensure that JavaScript properly compares the values as numbers. The result of the validation function indicates if the value is valid – for an acceptable value, return true and return false for unacceptable values. For my function, a value is valid if it is smaller than or equal to the argument.

Defining the Validation Message

You can specify the message that is displayed in two ways. The first is as a string, which is what I used in the earlier example. The other way to specify a message is with a function, allowing you to create messages with a lot more context. Listing 13-21 provides a demonstration.

Listing 13-21.  Creating a Message for a Custom Check Using a Function

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = {
            flowers: [
                { name: "Aster", product: "aster", stock: "10", price: "2.99" },
                { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
                { name: "Rose", product: "rose", stock: "2", price: "4.99" },
                { name: "Peony", product: "peony", stock: "0", price: "1.50" },
                { name: "Primula", product: "primula", stock: "1", price: "3.12" },
                { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });
  
        $.validator.addMethod("stock", function (value, elem, args) {
            return Number(value) < Number(args);
        }, function(args) {
            return "We only have " + args + " in stock"
        });
  
        $("input").each(function (index, elem) {
            $(elem).rules("add", {
                stock: data.flowers[index].stock
            })
        }).change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

The argument passed to the function is the argument you provided when applying the rule, which in this case is the stock property value from the data flower object. You can see the effect in Figure 13-13.

9781430263883_Fig13-13.jpg

Figure 13-13. Defining error messages for custom checks using a function

Formatting the Validation Error Display

To my mind, one of the best features of the validation plug-in is the wide range of ways that you can configure how validation error messages are displayed to the user. In the examples so far in this chapter, I have relied on the configuration options highlighted in Listing 13-22.

Listing 13-22.  The Configuration Options for Formatting the Validation Errors

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = {
            flowers: [
                { name: "Aster", product: "aster", stock: "10", price: "2.99" },
                { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
                { name: "Rose", product: "rose", stock: "2", price: "4.99" },
                { name: "Peony", product: "peony", stock: "0", price: "1.50" },
                { name: "Primula", product: "primula", stock: "1", price: "3.12" },
                { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).add($(element).parent()).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).add($(element).parent()).removeClass("invalidElem");
            },
            errorElement: "div",
            errorClass: "errorMsg"
        });
  
        $.validator.addMethod("stock", function (value, elem, args) {
            return Number(value) < Number(args);
        }, function(args) {
            return "We only have " + args + " in stock"
        });
  
        $("input").each(function (index, elem) {
            $(elem).rules("add", {
                stock: data.flowers[index].stock
            })
        }).change(function (e) {
            $("form").validate().element($(e.target));
        });
    });
</script>
...

I have relied on four different configuration options, but they are tightly coupled together. I explain the significance of each in the following sections.

Setting the Class for Invalid Elements

The errorClass option specifies a class that will be associated with invalid values. This class is applied to error message elements when they are added to the document. In my examples, I specify a class called errorMsg, for which there is a corresponding CSS style in the style element, as shown in Listing 13-23. The style sets the text color to red to emphasize the validation error.

Listing 13-23.  The style Element for the Example Document

...
<style type="text/css">
    .errorMsg {color: red}
    .invalidElem {border: medium solid red}
</style>
...

Setting the Error Message Element

Error messages are inserted into the document as the immediate next sibling of the form element that contains the invalid value. By default, the error message text is contained within a label element. This didn’t suit me in the examples, because the external style sheet already contains a selector that matches all label elements within the cell-level div elements in the CSS table layout and applied a style that prevented the text from displaying properly. To address this, I used the errorElement option to specify that a div element be used instead, as shown in Listing 13-24.

Listing 13-24.  Specifying the Element That Will Be Used for the Error Message

...
$("form").validate({
    highlight: function(element, errorClass) {
        $(element).add($(element).parent()).addClass("invalidElem");
    },
    unhighlight: function(element, errorClass) {
        $(element).add($(element).parent()).removeClass("invalidElem");
    },
    errorElement: "div",
    errorClass: "errorMsg",
      
});
...

Setting the Highlighting for Invalid Elements

The highlight and unhighlight options specify functions that will be used to highlight elements that contain invalid values. The arguments to the functions are the HTMLElement object representing the invalid element and the class specified using the errorClass option.

As you can see in Listing 13-25, I ignore the second attribute but use the HTMLElement object to create a jQuery selection, navigate to the parent element, and add it to the invalidElem class.

Listing 13-25.  Controlling the Element Highlight

...
$("form").validate({
    highlight: function(element, errorClass) {
        $(element).add($(element).parent()).addClass("invalidElem");
    },
    unhighlight: function(element, errorClass) {
        $(element).add($(element).parent()).removeClass("invalidElem");
    },
    errorElement: "div",
    errorClass: "errorMsg",
      
});
...

The function specified by the unhighlight option is called when the user has corrected the problem and the element contains a valid value. I use this opportunity to remove the class I added in the other function. The invalidElem class corresponds to a selector in the style element contained in the document, as shown in Listing 13-26.

Listing 13-26.  The Style Used for Highlighting Element

...
<style type="text/css">
    .errorMsg {color: red}
    .invalidElem {border: medium solid red}
</style>
...

You can select and manipulate elements in these functions in any way you like. I have applied a border to the parent element, but I could have operated directly on the element itself or on another part of the document entirely had I preferred.

Using a Validation Summary

The validation plug-in can present the user with a single list of all the validation errors, rather than add individual messages next to each element. This can be useful if the structure or layout of your document can’t easily flex to accommodate additional elements. Listing 13-27 shows how to create a validation summary.

Listing 13-27.  Using a Validation Summary

...
<script type="text/javascript">
    $(document).ready(function () {
  
        var data = {
            flowers: [
                { name: "Aster", product: "aster", stock: "10", price: "2.99" },
                { name: "Daffodil", product: "daffodil", stock: "12", price: "1.99" },
                { name: "Rose", product: "rose", stock: "2", price: "4.99" },
                { name: "Peony", product: "peony", stock: "0", price: "1.50" },
                { name: "Primula", product: "primula", stock: "1", price: "3.12" },
                { name: "Snowdrop", product: "snowdrop", stock: "15", price: "0.99" }]
        };
  
        var plurals = {
            aster: "Asters", daffodil: "Daffodils", rose: "Roses",
            peony: "Peonies", primula: "Primulas", snowdrop: "Snowdrops"
        };
  
        var templResult = $("#flowerTmpl").template(data).filter("*");
        templResult.slice(0, 3).appendTo("#row1");
        templResult.slice(3).appendTo("#row2");
  
        $("<div id='errorSummary'>Please correct the following errors:</div>")
            .addClass("errorMsg invalidElem")
            .append("<ul id='errorsList'></ul>").hide().insertAfter("h1");
  
        $("form").validate({
            highlight: function (element, errorClass) {
                $(element).addClass("invalidElem");
            },
            unhighlight: function (element, errorClass) {
                $(element).removeClass("invalidElem");
            },
            errorContainer: "#errorSummary",
            errorLabelContainer: "#errorsList",
            wrapper: "li",
            errorElement: "div"
        });
  
        $.validator.addMethod("stock", function (value, elem, args) {
            return Number(value) <= Number(args.data.stock);
        }, function (args) {
            return "You requested " + $(args.element).val() + " "
                + plurals[args.data.product] + " but we only have "
                + args.data.stock + " in stock";
        });
  
        $("input").each(function (index, elem) {
            $(elem).rules("add", {
                stock: {
                    index: index,
                    data: data.flowers[index],
                    element: elem
                }
            })
        }).change(function (e) {
            $("form").validate().element($(e.target));
        });
  
    });
</script>
...

For this example, I am going to work backward and show you the result before explaining how I get there. Figure 13-14 shows the validation summary being displayed.

9781430263883_Fig13-14.jpg

Figure 13-14. Using a validation summary

Preparing the Validation Messages

The first issue to solve when using a validation summary is that the context that is implied by placing an error message next to a form element is lost; I have to put some additional work into the error messages so that they make sense. To start with, I defined an object that contained the plurals of the flower names:

...
var plurals = {
    aster: "Asters", daffodil: "Daffodils", rose: "Roses",
    peony: "Peonies", primula: "Primulas", snowdrop: "Snowdrops"
}
...

I use these values to generate a specific error message using the function feature of the custom check, like this:

...
$.validator.addMethod("stock", function (value, elem, args) {
    return Number(value) <= Number(args.data.stock);
}, function (args) {
    return "You requested " + $(args.element).val() + " "
        + plurals[args.data.product] + " but we only have "
        + args.data.stock + " in stock";
});
...

The link between these two stages is the argument object that I specify when applying the custom check to the form elements. The built-in checks have simple arguments, but you can create complex objects and pass whatever data suits you, like this:

...
$("input").each(function (index, elem) {
    $(elem).rules("add", {
        stock: {
            index: index,
            data: data.flowers[index],
            element: elem
        }
    })
}).change(function (e) {
    $("form").validate().element($(e.target));
});
...

In this case, I have passed the index, the data array, and the element itself, all of which I use to piece together the message to display to the user. (I'll show you a useful feature for simplifying string composition later in the chapter.)

Creating the Validation Summary

I am responsible for creating the element that will contain the validation summary and adding it to the document. To that end, I added a div element that contains an ul element. My goal is to create an unnumbered list showing each error:

...
$("<div id='errorSummary'>Please correct the following errors:</div>")
    .addClass("errorMsg invalidElem").append("<ul id='errorsList'></ul>").hide().insertAfter("h1");
...

The text in the div element is displayed above the list of errors. Notice that I have used the hide method after appending these elements to the DOM. Not only am I responsible for creating the elements, but I am also responsible for ensuring that they are not visible when there are no errors. The hide method ensures that the validation summary isn't initially visible to the user – the validation plug-in will take care of the visibility once the validation process begins.

Now that I have all the pieces in place, I can configure the validation summary, as follows:

...
$("form").validate({
    highlight: function (element, errorClass) {
        $(element).addClass("invalidElem");
    },
    unhighlight: function (element, errorClass) {
        $(element).removeClass("invalidElem");
    },
    errorContainer: "#errorSummary",
    errorLabelContainer: "#errorsList",
    wrapper: "li",
    errorElement: "div"
});
...

I have changed the implementation of the hightlight and unhighlight functions to style just the input elements. The errorContainer option specifies a selector that will be made visible when there are validation errors to display. In my case, this is the element with the errorSummary ID (the div element). The errorLabelContainer option specifies the element into which the individual error messages will be inserted. For my example, this is the ul element, since I want my messages displayed as a list.

The wrapper option specifies an element into which the validation message will be inserted. This is useful only if you want a list display. Finally, the errorElement specifies the element that will contain the error text. This is the label element by default, but I have switched to div elements to make the formatting easier. The result of these options is the validation summary I showed you in Figure 13-14.

The validation plug-in removes messages from the summary when the user resolves an issue, and when there are no issues at all, the validation summary is entirely hidden and the user can submit the form. Figure 13-15 shows the validation summary after two of the three errors from the last figure have been resolved.

9781430263883_Fig13-15.jpg

Figure 13-15. A validation summary showing fewer error messages

The choice between inline messages and validation summaries is a personal one and is usually driven by the structure of the document. The good news is that the validation plug-in is flexible, and it usually doesn’t take much work to define and apply validation that is closely tailored to your needs.

Tidying Up the Error Message Composition

I am going to make one final change in this chapter, just to demonstrate a useful feature of the validation plug-in that is not directly related to validating data. In the previous example, when I wanted to create a contextual error message, I did so by concatenating strings and variables, like this:

...
$.validator.addMethod("stock", function (value, elem, args) {
    return Number(value) <= Number(args.data.stock);
}, function (args) {
    return "You requested " + $(args.element).val() + " "
        + plurals[args.data.product] + " but we only have "
        + args.data.stock + " in stock";
});
...

This works, but it is ugly and hard to read. The validation plug-in method provides a formatter than works in a way similar to string composition in languages like C#, and you can see how I have used this feature in Listing 13-28.

Listing 13-28.  Using the jQuery Validator String Formatting Feature

...
$.validator.addMethod("stock", function (value, elem, args) {
    return Number(value) <= Number(args.data.stock);
}, function(args) {
    return $.validator.format("You requested {0} {1} but we only have {2} in stock",
        $(args.element).val(), plurals[args.data.product], args.data.stock )
});
...

The string composition is performed by the $.validator.format method, which takes a template string and a number of value arguments. The template string is parsed for occurrences of the brace characters surrounding an integer, such as {0}, and these are replaced with the corresponding value argument. The first value argument is referenced by {0}, the second by {1} and so on. The $.validator.format method returns a function that isn’t evaluated until the error message is displayed, which ensures that the correct values are used when composing the string.

This can be an odd way of creating strings if you are not used to it, but a sorely missed feature if you are accustomed to a language like C# that frequently relies on this approach to string composition.

Summary

In this chapter, I showed you the support that jQuery provides for forms. I began by recapping the form-related event methods and explained the roles that the most important ones play in the life of an HTML form. Most of the chapter was spent covering the Validation plug-in, which provides flexible and extensible support for validating the values that users enter into a form and providing the means for resolving any problems before the data is submitted to the server. In Chapter 14, I begin the process of describing the support that jQuery provides for making Ajax requests.

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

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