Chapter 5

Creating Forms

Forms are an integral part of most apps and websites. In recent years, forms have progressed to a point in HTML5 where you can provide very rich functionality without any JavaScript at all. Of course, not all browsers support these advanced features, so you'll often need to provide JavaScript fallbacks. Fortunately, a lot of third-party polyfills are available that you can use to back up HTML5 form features automatically.

In this chapter, you learn how to use a progressive enhancement approach in your forms. You start with a solid-base state that works across all browsers, and then you add JavaScript features on top. You then discover a variety of HTML5 form features, such as special input types, widgets, and validation. Next, you learn how to use polyfills to support these features in older, non-supportive browsers. You also read about some techniques for posting your forms via Ajax. Finally, you find out how to connect your forms with Backbone, automatically generating the view and posting the form data with Backbone's syncing.

Understanding Progressive Enhancement

This chapter's general approach is one of progressive enhancement, which is widely accepted as the best practice for handling forms with JavaScript (and pretty much everything else you might augment with JS). This approach starts with a more limited foundation that works across all browsers, and then progressively enhances that base state with additional features wherever they can be supported. That's ideal because it provides a wide range of compatibility across different browsers and environments while also providing richer features wherever possible.

The Progressive Enhancement Approach

The basic progressive enhancement approach for forms is as follows:

1. Use standard HTML5 form markup to achieve a solid foundation for your form.

2. With JavaScript, leverage HTML5 attributes from the markup to determine what you want to do to each form element.

3. Add a scripting layer on top of these elements, widgetizing the form with date pickers and other widgets, as well as providing enhancements such as placeholder text, Ajax submission, and so forth.

Why Progressive Enhancement?

The logic of progressive enhancement is that it first provides an adequate base state that is universal across all the browsers you support. This notion is particularly important when it comes to forms, because these components are crucial to the overall goals of a site. For instance, you may find it acceptable that an intro video or animation on the homepage doesn't load, but would it be acceptable if the user can't submit an order form? Or can't even log in? For that reason, it's absolutely essential that your forms work, in some capacity, across all the browsers and environments you want to support. Additionally, they'll need to remain accessible to assisted devices for vision impaired users and people with other disabilities.

Moreover, it's important to provide the best experience possible for your users. With all the advancements in web development, users have come to expect a streamlined experience, and JavaScript form enhancements are a big part of that.

Progressive enhancement provides the best of both worlds: basic support for all users and richer features wherever they're supported.

Deciding Which Environments to Support

You've probably already realized that your forms need to work in older versions of IE and other browsers that don't support HTML5. But they also need to work even if the user has turned off JavaScript. Yes, your site should work (at least a little bit) without any JavaScript at all.

Even if you aren't worried about users who have turned off their JavaScript, keep in mind that when JavaScript breaks, it often stops all other scripting on the page. A site that depends entirely on JavaScript can therefore be quite brittle. It's much better to provide yourself some outs so that a small bug can't disable other functionality on the page.

Additionally, if your JavaScript is loading slowly, the user won't have to wait for it. They can begin entering form data while the JavaScript loads. As Jake Archibald said, “Everyone has JavaScript disabled while they're downloading your JavaScript.”

This chapter demonstrates how to use HTML5 form elements, but these all default back to basic form elements in unsupported browsers. Then I show you how to add a JavaScript layer on top of your forms in a way that enables your users to be able to use the forms without JavaScript. Even though you end up posting the form with Ajax, the user will still be able to submit the form the old fashioned way.

There are many conflicting views on how many users have turned off their JavaScript (or have a device that doesn't support it). Some numbers are as high as 5 percent, but that seems like an exaggeration. The truth is that this stat is pretty hard to determine. Most analytics, such as Google Analytics, run in JavaScript, so there's no way to discover how many users have it turned off. Some enterprise-level analytics like Omniture have worked around this, but I'm skeptical about the quality of their data.

You can't figure out JavaScript support from the server side or the user agent. So the only way to calculate this stat is a combination of front and back end analytics. But what happens if the user leaves the page before the JavaScript instantiates? Or before it's able to relay its first bit of data via Ajax? These uncertainties can inflate numbers and cause alarming stats like 5 percent of users don't have JavaScript.

Letting HTML5 Do the Work for You

The foundation of your form will be built on HTML5, which offers elements that provide a surprisingly rich level of interactivity, without any JavaScript whatsoever. In fact, the HTML5 elements provide a better experience than you could deliver with JavaScript. They are fast, bug-free, and easy to implement. They'll still work even if scripting breaks elsewhere on the page. All things considered, you should use native browser functionality whenever possible. It just doesn't make sense to reinvent the wheel.

However, keep in mind that these elements will work only in contemporary browsers. You'll need to use JavaScript to provide fallbacks if you want this richer functionality across the board. That said, most HTML5 form features still work at a basic level in any browser. Older browsers can't understand the HTML5 elements and attributes, so they default to basic text inputs without any bells and whistles. That means that no matter what, your users will still be able to use your forms, even if they're on an older browser and JavaScript doesn't load.

HTML5 Input Types

Prior to HTML5, the input types were pretty basic:

text inputs for plain text

password inputs to hide typing

checkbox and radio inputs for certain types of data

file inputs for uploading files to the server

• And a handful of others like hidden and submit

Although these are certainly useful, they pale in comparison to the functionality introduced by HTML5. HTML5 input types include an assortment of useful widgets such as sliders, date pickers, and color pickers. Additionally, on mobile devices, many HTML5 input types provide special keyboards that make entering data easier.

Widgets

Although the HTML5 specification outlines a number of different form widgets, browser support is still a bit spotty. However, wherever a form widget is unsupported, it will simply revert to a standard text input.

At the time of this writing, Firefox and IE both have pretty bad HTML5 form widget support.

Range Slider

The range slider is one of the most widely supported form widgets, working in the latest versions of all major browsers. To call a slider, use the following markup:

<input type="range">

This markup creates a slider widget, which you can use to control a number value, as shown in Figure 5-1.

9781118524404-fg0501.tif

Figure 5-1 The slider widget provides an intuitive control for numbers. This is how it appears in Chrome.

You can also change the slider's styling in WebKit browsers. To learn more, see http://css-tricks.com/value-bubbles-for-range-inputs/.

You can also control some aspects of your slider:

<input type="range" min="0" max="100" step="5" value="30">

These attributes are as follows:

min is the minimum value of the slider (the left side).

max is the maximum value (the right side).

step controls how much each tic of the slider should adjust the number (defaults to 1).

value is the default starting place for the slider (defaults to halfway between the min and max).

At the very least, you need to set the min and max for your slider to provide reasonable boundaries for the number value.

Although the range slider is useful, it doesn't relay much information to the user. That's fine if you're using it for a “feeler” type value—for example, “How strongly do you feel about this question.” But if you want to relay the actual number to the user, you need to tap into it with JavaScript. First, set up some basic markup:

<input type="range">

<span class="range-value"></span>

Next, you can hook into any changes in the range value using jQuery's change() handler:

$('input[type="range"]').change(function(e) {

  var $this = $(this);

  $this.siblings('.range-value').text($this.val());

});

Here an attribute selector first finds all range inputs. Then, whenever these inputs change, the sibling .range-value span is located and the text of that span is set to match the value of the range input. As Figure 5-2 demonstrates, this snippet displays the number value next to the slider.

9781118524404-fg0502.tif

Figure 5-2 The value of this range slider is displayed with JavaScript.

This script is working pretty well, but you also need to show the initial value of the range when the page loads. To do so, extract it into a function and call it in both cases:

$.fn.displaySliderVal = function() {

  this.siblings('.range-value').text( this.val() );

return this;

};

    

$('input[type="range"]').change(function(e) {

  $(this).displaySliderVal();

}).displaySliderVal();

Here, a few changes have been made:

1. The script defines displaySliderVal(), a method on jQuery's prototype that allows chaining by returning the jQuery object.

2. displaySliderVal() is called on $(this) in the change handler.

3. displaySliderVal() is also called on the initial reference to $(‘input[type=”range”]'), so it shows the default value.

You can also use the number picker for numbers with <input type=”number”>.

Date Picker

Another useful widget is the date picker, which you can call with the following markup:

<input type="date">

This creates a date picker in some browsers, as shown in Figure 5-3.

9781118524404-fg0503.tif

Figure 5-3 The date picker in Chrome.

However, keep in mind that a calendar date picker doesn't always provide the best user experience. It's really useful for dates that are close to the current date (or an arbitrary start point). But because it's hard to switch years, a calendar date picker isn't a good idea for birthdates. You can see the issue in Figure 5-4.

9781118524404-fg0504.tif

Figure 5-4 Switching years in the date picker is a hassle. The user can still enter any date manually but will probably be thrown off by the usability issues in the widget.

Therefore, with birthdates and other dates that are more than a year or two old, it's a much better idea to use a combination of <input type=”date”>, <input type=”month “>, and <input type=”year”>. Or use a JavaScript widget that handles the issue more gracefully.

Color Picker

Although most forms won't use the color picker, it can come in handy in the right situation. You can call it with the following markup:

<input type="color">

Browser support for the color picker is poor, but the current versions of Chrome and Opera both support it, as you can see in Figure 5-5.

9781118524404-fg0505.tif

Figure 5-5 The color picker in Chrome (left) and Opera (right). They are different visually. The Chrome version is better for fine-grained control, whereas the Opera version is easier to use in most cases.

The color picker returns a hex value—for example, #ff7f24. You access this value with the same attribute selector used for the range input:

$('input[type="color"]').val();

In addition to the widgets described here, there's also a search input. Its functionality isn't overly interesting, but it's useful at times.

Contextual Keyboards

Some HTML5 input types also have a special connotation on mobile devices such as phones and tablets. They display specialized keyboards that make it easier for the user to enter specific data.

These days most smartphones and tablets use a software keyboard, meaning a keyboard that displays on the touch screen whenever it's needed, as shown in Figure 5-6.

9781118524404-fg0506.tif

Figure 5-6 The standard keyboard on iPhone.

However, using these keyboards can be a bit cumbersome, especially on phones.

Fortunately, certain HTML5 input types trigger a special keyboard that makes entering data much easier. For example, the following markup displays a telephone number pad like the one shown in Figure 5-7.

<input type="tel">

9781118524404-fg0507.tif

Figure 5-7 This numeric keypad displays in iOS for <input type=”tel”>.

Other input types add special keys that help in the particular situations—for example, <input type=”email”> and <input type=”url”>—which are shown in Figure 5-8.

9781118524404-fg0508.tif

Figure 5-8 The email input (left) and URL input (right) on iPhone.

Another useful input type is <input type=”number”>, the result of which you can see in Figure 5-9.

These input types also have important connotations for semantics and form validation. You learn more about these in the next section.

9781118524404-fg0509.tif

Figure 5-9 The number input type calls up the number keyboard in iOS, so the user doesn't have to switch to it.

Interactive Features

In addition to the new input types, HTML 5 introduces several interactive features such as placeholder text and validation. That means native support for a variety of functionalities that previously could be supported only with JavaScript.

Placeholder Text

Placeholder text is displayed in a form element before the user enters a value. It's commonly used to provide a short hint about what should be entered in the field—for example, the particular format for a date input.

With HTML5, you can display placeholder text in your form fields using the placeholder attribute:

<input type="email" placeholder="[email protected]">

In the browser, this placeholder displays as lighter text that is replaced by whatever value the user enters. See Figure 5-10.

9781118524404-fg0510.tif

Figure 5-10 This placeholder text provides a visual cue that enhances user experience.

Placeholder text is a great way to provide a quick visual cue and speed up form entry. Remember, the faster users can enter data in your form, the more likely they will “convert” to another sale or signup. As you can see from this data, higher clarity of form requirements leads to better conversion rates: http://www.lukew.com/ff/entry.asp?1416.

You can also style the placeholder text with the following snippet for various browsers:

input::-webkit-input-placeholder {color:green;}

input::-moz-placeholder {color:green;}

input:-ms-placeholder {color: green;}

Note that you cannot combine these selectors—you must separate experimental selectors or browsers will ignore the entire block.

Autofocus

Autofocus is another way to speed up form entry. It brings the cursor into a specific field when the page loads so that the user can simply start typing instead of clicking or tabbing into the form. To use autofocus in your HTML5 forms, simply add the autofocus attribute to any element:

<input type="text" autofocus>

This attribute brings the cursor into this field when the page loads.

Besides not needing any extra code, native HTML5 autofocus works a lot better than the JavaScript alternatives, because JavaScript focus() scripts sometimes take a while to load. During that delay, the user may start typing into another field. However, once the focus() function executes, it hijacks the user's cursor into the autofocused field—and that results in a very bad user experience.

Validation

Native form validation is probably my favorite new feature of HTML5 forms. It allows you to skip the whole rat's nest of JavaScript form validation and have the browser do the work for you.

Basic Form Validation

Best of all, if you're using HTML5 input types, you're already 90 percent of the way there, because HTML5 forms validate against the input type. For example, if you have <input type=”email”>, HTML5 ensures that it's a properly formatted email address when the user submits the form, as you can see in Figure 5-11.

9781118524404-fg0511.tif

Figure 5-11 HTML5 displays an error message if the email address is formatted incorrectly.

It also takes into account any ranges you may have set—for example:

<input type="number" min="0" max="500">

This form throws an error if the number is outside of the bounds set in the markup (or if it isn't a number).

Additionally, you can make any field required by adding the required attribute:

<input type="text" required>

This field throws an error if the user fails to fill it out, as you can see in Figure 5-12.

9781118524404-fg0512.tif

Figure 5-12 This error message is thrown if the user fails to fill out a required field.

Always validate on the server side for security reasons. Hackers can use older browsers and disable JavaScript to get around any front-end validation measures you may have enacted (or just post directly into your form handler).

Custom Validation Rules

You can also create your own validation rules for any fields that aren't covered. To do so, simply add a regex to the pattern attribute:

<input type="text" pattern="[a-zA-Z]+">

In this example, whatever the user enters will be checked against the regular expression [a-zA-Z]+, which only accepts letter values (no numbers, spaces, or special characters).

The only downside with creating your own custom rules is that the browser displays the generic error message shown in Figure 5-13.

9781118524404-fg0513.tif

Figure 5-13 This generic validation error displays if the user enters data that doesn't match the pattern attribute.

Fortunately, you can add your own custom validation message using JavaScript. Simply add it to an oninvalid listener:

<input type="text" pattern="[a-zA-Z]+"

oninvalid="setCustomValidity('You may only enter letters.')," >

Here, the native setCustomValidity() method is used to display the custom message, as shown in Figure 5-14. You might be scoffing at the fact that I included this script inline in the markup. I did that mainly for simplicity's sake, but it's also not a bad idea here because the script is fundamentally tied to the markup for this element.

9781118524404-fg0514.tif

Figure 5-14 This custom validation message is displayed via JavaScript.

Although this custom validation message is more useful than the generic message, here are some caveats to consider:

• Native validation messages are localized for the user's language, and you're probably not planning on translating your message into hundreds of different languages.

• This validation message will display no matter the reason for the error. For instance, it will also display if the user enters more text than the maxlength or fails to fill out a required field.

For these reasons, it's best to avoid overwriting the default messages for email, url, and number unless you're very unhappy with the default text. Also, if you have multiple validation rules on a field, build in a check to determine exactly what triggered the error.

You may not care about localizing error messages if your site is provided only in English. But keep in mind that if you use custom messages for some fields and defaults for others, there may be validation error messages in multiple languages.

Using Polyfills for Older Browsers

Although HTML5 forms are fairly well supported in modern browsers, the sad reality is that a lot of your users probably haven't upgraded yet. Even if they have, support is spotty for certain features depending on which browser they use. So, you need to build backups for these components with JavaScript.

Finding Third-Party Polyfills

Fortunately, you're not the only person to have this problem, and there are a number of very comprehensive options out there. Best of all, these open-source offerings provide most of this functionality pretty much out of the box.

Polyfills are shims that provide backups for newer features that aren't available in older browsers. In general, polyfills use JavaScript to re-create native functionality wherever it is lacking. But they don't necessarily have to use JavaScript—for instance, polyfills for HTML5 video use Flash.

The Modernizr team maintains a great list of various polyfills at https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills.

All the polyfills on that list are decent, and you should be able to find one for any HTML5 form functionality you need. Of course, if you can't find the perfect polyfill, you can always write your own.

Writing Your Own Polyfills

While you can download a polyfill for just about any form feature you need to support, I want to walk through some quick examples of how to build them yourself. As a result, you'll understand what's going on in whatever you download, and you'll be able to customize an existing polyfill or write your own if you're unhappy with the third-party options.

When building polyfills, remember to test in a browser that doesn't support the feature you're trying to implement. Otherwise, you'll think you're coding everything perfectly even if your script has bugs.

General Approach

In general, the best way to provide backups for HTML5 is to leverage the markup you've already written. That means you should hook into the different form elements and create functionality based on their attributes, exactly like HTML5 does in supportive browsers.

For example, you might have this range input:

<input type="range" min="0" max="100" value="30">

The best approach is to first leverage an attribute selector to determine when to display a JavaScript slider widget:

$('input[type="range"]'),

Then use the various attributes to set the options for your JavaScript slider, setting the upper and lower bounds with the min and max and the initial value with the value.

Because it provides the loosest coupling, taking this sort of approach is always best for polyfills. The alternative of hardcoding specific values in your JavaScript means that you enter this data in two places, which is more complicated to develop and, more importantly, harder to maintain. The worst part is that you have to remember to make these changes, even though you won't see any issues in your A-list HTML5 browser.

Writing an Autofocus Polyfill

Autofocus is one of the easiest polyfills to write, because there isn't much more to do than find the element with the autofocus attribute and set focus() on it. For example, you can accomplish this in jQuery:

$('[autofocus]').focus();

This script uses an attribute selector to find any element with the autofocus attribute set, such as <input type=”text” autofocus>. Then it uses the focus() method to call cursor focus into that element.

Implementing the Polyfill

Now, you need to determine when to use the polyfill. Note that because your polyfills will never be as good as native support, avoid implementing them whenever the functionality is supported natively.

In this example, the JavaScript fallback might cause usability problems—for example, if the user starts typing into a different form field before the JavaScript finishes loading. Once the polyfill kicks in, it hijacks the user's input cursor and places it into a different field, a problem that does not exist in the native implementation of autofocus.

Therefore, implement the polyfill only when autofocus isn't supported. But don't go looking up which browsers support autofocus and which don't. Browser-sniffing is a terrible practice that you should avoid unless absolutely necessary. Instead, use feature-checking and determine whether autofocus is supported using JavaScript. To do so, you can use Modernizr, which is available at http://modernizr.com.

After including Modernizr, you can build in a check for autofocus:

if ( ! Modernizr.input.autofocus ) {

  $('[autofocus]').focus();

}

This script uses the Modernizr API to determine whether autofocus is supported. It then calls the polyfill in non-supportive browsers.

If the polyfill script is really large, consider loading it externally within this feature check. As a result, modern browsers don't get weighed down by a lot of code they won't use. Consider using a JavaScript loader like require.js to load scripts in this type of situation.

Improving Your Polyfill

Although that works on the basic level, pay careful attention when creating polyfills, because you want the polyfill to match native functionality, regardless of the situation. Taking the careful approach ensures that your polyfill won't give you any surprises. Remember that you primarily work in a browser that supports HTML5, so you won't necessarily see potential problems in your polyfill right away.

The steps for writing a robust polyfill are as follows:

1. Determine an edge-case behavior that might cause problems.

2. See how that situation is handled in browsers that natively support the functionality.

3. Mirror the native behavior in your polyfill.

4. Create unit tests to ensure the polyfill handles all situations appropriately.

For instance, you probably shouldn't have more than one element with autofocus on a page, but what happens if you do? Set up a test page in Chrome or another modern browser, and you will see that the cursor focus will be brought into the last element on the page with autofocus.

That's not necessarily a problem in the autofocus polyfill you wrote because jQuery loops through all the matching elements, bringing focus into each. Because it changes the focus with each new element in the loop, the focus ends up on the last element with autofocus. Sure, the approach is slightly worse for performance, but it's probably not worth optimizing such an edge case. However, what happens if you also have a focus event listener on each element? That handler will then fire twice rather than once. Fortunately, you can fix it easily using the :last pseudo-class:

$('[autofocus]').filter(':last').focus();

Here, the jQuery's filter() method is used to find the last element with autofocus. You could also attach :last in the main selector, but filter() is better for performance (in all situations, not just edge cases).

But wait a minute. Why don't you test focus events in a browser that natively supports autofocus? You'll see that they actually don't fire at all.

Is your head spinning? I'm not trying to torture you here; I'm just trying to show you how difficult it can be to write a truly robust polyfill, even for a simple feature like autofocus. I hope this exercise gives you more respect for the third-party polyfills you download, and if you ever have a problem with one of them, you can use these steps to patch it.

And I can keep going: How do elements with tabindex affect the order?

Writing a Placeholder Polyfill

Writing a polyfill for placeholder text is a little more complicated than writing one for autofocus, but it's still one of the easier HTML5 form features that you can implement.

Detecting Support

The first step to this polyfill is determining whether you actually need it. For this, you can turn again to Modernizr:

if ( ! Modernizr.input.placeholder ) {

    jsPlaceholder();

}

This snippet uses Modernizr.input.placeholder to detect placeholder support. If it's unsupported, a jsPlaceholder() function is called.

Determining the Goals

Before you dig in and start building this polyfill, think about what it should do:

1. The polyfill should take the text from the placeholder attribute and place it in the input field.

2. When users click into the field, the polyfill should remove the text so that they can add their own.

3. Then if users click out of the field without adding text, the polyfill should add the placeholder text again.

4. The polyfill shouldn't do any of this if users added text. You don't want the placeholder text to overwrite fields that have already been populated. Also, you don't want it to empty out the text of a field if users modified it—users should be able to revisit fields they've already changed without the fields emptying.

5. The polyfill should attach a placeholder classname whenever the placeholder is active. That way, the placeholder text can be styled differently than normal text.

Building the Basic Polyfill

Now that you've established the goals, you can get started with the polyfill. First, pull in any form field with the placeholder attribute; then attach blur and focus events to add and remove the placeholder text:

var jsPlaceholder = function() {

  // when the field gains focus

  $('[placeholder]').focus(function() {

    var $input = $(this);

    

    //  determine if the field is showing the placeholder text

    if ($input.val() === $input.attr('placeholder')) {

      // empty out the field and remove the placeholder class

      $input.val(''),

      if ( $input.hasClass('placeholder') ) {

        $input.removeClass('placeholder'),

      }

    }

    

  // when the field loses focus

  }).blur(function(){

    var $input = $(this);

    

    // replace the placeholder if they haven't entered any text

    if ($input.val() === '' || $input.val() === $input.attr('placeholder')) {

      // add the placeholder text and class

      $input.val($input.attr('placeholder'));

      $input.addClass('placeholder'),

    }

  // trigger the blur so the placeholder text is added on page load

  }).trigger('blur'),

};

Now, walk through it step by step:

1. The polyfill begins by selecting any form field with a placeholder attribute.

2. It sets up a focus() event for when the user clicks into the field. If the text in the field matches the placeholder, it's removed along with the placeholder classname.

3. It sets up a blur() event for when the user clicks out of the field. Here, if the user hasn't entered text, it adds the placeholder text as well as the classname.

4. It triggers the blur() event, which adds the placeholder text when the page loads (but only if the field isn't populated).

Probably the most confusing part of this script is Step 4. Rather than explicitly adding the placeholder text when the page loads, you trigger the blur event the polyfill already handles. Thus, you don't have to repeat those lines.

It's worth noting that this example doesn't exactly follow the W3C spec for placeholder implementation. In native implementations, the content of the input isn't removed on focus, rather it's removed once the user begins typing into the field. As an exercise, modify the script to follow the spec more accurately.

Submitting the Form

Now, you can see the placeholder text being added and removed from your form fields just like in supportive browsers. However, there are still some things you need to consider. Mainly, the polyfill replaces the values of the form elements with the placeholder text, which can cause problems when the form is submitted because these values will be passed as though the user entered them. To get around this issue, patch into the submit event of all the forms on your page:

// must remove placeholders when the form submits so they don't post

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

  $(this).find('[placeholder]').each(function() {

    var $input = $(this);

    // empty the field if it is displaying the placeholder

    if ($input.val() === $input.attr('placeholder')) {

      $input.val(''),

    }

  })

});

This example empties out the value of any field that is displaying the placeholder. Because it isn't preventing the default action of the submit event, it still posts as normal after this change is made. Be sure to bind this event handler somewhere within your jsPlaceholder function.

Handling Password Inputs

Now the script is working pretty much like you need it to. The only problem occurs if you have password inputs. Password inputs obfuscate their values, displaying text as *******. Although that's good for passwords, it's no good for placeholder text.

Unfortunately, this is a pretty tricky one to get around. You have to create a new text input for the placeholder text and switch it out for the password input. To make sure the user's password doesn't get displayed on the screen, you also have to switch back to the password input when the placeholder is inactive.

To implement this change, you modify your blur handler:

...

// when the field loses focus

}).blur(function() {

  var $input = $(this);

  

  // replace the placeholder if they haven't entered any text

  if ($input.val() === '' || $input.val() ===

    $input.attr('placeholder')) {

    // if password input, you have to clone it as a text input and

    // then remove this later (otherwise it will show up as ******)

    if ( $input.attr('type') == 'password' ) {

      var $newInput = $input.clone();

      $newInput.attr('type', 'text'),

      $newInput.val($input.attr('placeholder'));

      $newInput.addClass('placeholder clone'),

      $newInput.insertAfter($input);

      

      $input.hide();

      

      // add focus state to remove this input and show / focus the

      // original

      $newInput.focus(function() {

        $(this).remove();

        $input.show().focus();

      });

    }

    else {

      // add the placeholder text and class

      $input.val($input.attr('placeholder'));

      $input.addClass('placeholder'),

    }

  }

// trigger the blur so the placeholder text is added on page load

}).trigger('blur'),

This blur handler first detects whether the field is a password input. Then the polyfill clones the input—as a result, the input retains all the classnames, styling, and so on, that have been applied to the original. You must then change the cloned input to a normal text input and add the placeholder text and classname, along with a clone classname to tap into later. Once that is complete, you can add the new field to the DOM and hide the old. Finally, add a focus handler to the cloned field. Consequently, when the user clicks into it, the polyfill removes this field, shows the password input, and moves the cursor into it.

But the blur handler is still not complete. You must modify the submit handler to remove any cloned fields so that they don't get posted with the form:

// must remove placeholders when the form submits so they don't post

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

  $(this).find('[placeholder]').each(function() {

    var $input = $(this);

    

    // remove any cloned password inputs

    if ( $input.hasClass('clone') ) {

      $input.remove();

      return;

    }

    

    // empty the field if it is displaying the placeholder

    if ($input.val() === $input.attr('placeholder')) {

      $input.val(''),

    }

  })

});

Putting It All Together

Now, the polyfill is working almost like native functionality. When all the code is pieced together, it looks like this:

var jsPlaceholder = function() {

  // when the field gains focus

  $('[placeholder]').focus(function() {

    var $input = $(this);

    

    //  determine if the field is showing the placeholder text

    if ($input.val() === $input.attr('placeholder')) {

      // empty out the field and remove the placeholder class

      $input.val(''),

      if ( $input.hasClass('placeholder') ) {

        $input.removeClass('placeholder'),

      }

    }

    

  // when the field loses focus

  }).blur(function() {

    var $input = $(this);

    

    // replace the placeholder if they haven't entered any text

    if ($input.val() === '' || $input.val() ===

      $input.attr('placeholder')) {

      // if password input, you have to clone it as a text input

      // and then remove this later

      // (otherwise it will show up as ******)

      if ( $input.attr('type') == 'password' ) {

        var $newInput = $input.clone();

        $newInput.attr('type', 'text'),

        $newInput.val($input.attr('placeholder'));

        $newInput.addClass('placeholder clone'),

        $newInput.insertAfter($input);

        

        $input.hide();

        

        // add focus state to remove this input and show / focus

        // the original

        $newInput.focus(function() {

          $(this).remove();

          $input.show().focus();

        });

      }

      else {

        // add the placeholder text and class

        $input.val($input.attr('placeholder'));

        $input.addClass('placeholder'),

      }

    }

  // trigger the blur so the placeholder text is added on page load

  }).trigger('blur'),

  

  // must remove placeholders when the form submits so they don't post

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

    $(this).find('[placeholder]').each(function() {

      var $input = $(this);

      

      // remove any cloned password inputs

      if ( $input.hasClass('clone') ) {

        $input.remove();

        return;

      }

      

      // empty the field if it is displaying the placeholder

      if ($input.val() === $input.attr('placeholder')) {

        $input.val(''),

      }

    })

  });

};

// add placeholder polyfill if it's unsupported

if ( ! Modernizr.input.placeholder ) {

  jsPlaceholder();

}

Although this implementation handles placeholder text almost exactly like it's handled in supportive browsers, there is still one issue. Unfortunately, the user won't be able to enter the same text as the placeholder into any field because the script will think it's the placeholder text and remove it.

But that's a pretty extreme edge case, and one you just have to live with. If you can see a way around that, patch this script and message me on Twitter @jonraasch.

Combining Polyfills and Widgets

Finally, just because you're writing your own polyfill doesn't mean you have to start completely from scratch. You can modify an existing polyfill or use a third-party widget within your polyfill.

For example, jQuery UI provides a lot of form widgets that are similar to those in HTML5. You can easily leverage these widgets in a polyfill. Simply pull in information about the widget from the form markup and then pass that to the jQuery UI widget.

Connecting to a REST API

Now you need to think about how your form will be posted to the server. You can always post it the old-fashioned way using a submit button, but you're building a JavaScript app here. Using Ajax is a better idea. With Ajax, you can provide a more streamlined experience for your users. Instead of a hard page refresh, you can provide a nice JavaScript animation to indicate the form has been posted. Of course, the form should also be able to post the old-fashioned way in case JavaScript isn't working for some reason.

Before you can post the form, you need some markup:

<form method="post" action="my-api.php">

  <fieldset>

    <label for="name">Name:</label>

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

  </fieldset>

    

  <fieldset>

    <label for="email">Email</label>

    <input type="email" name="email" id="email">

  </fieldset>

    

  <fieldset>

    <label for="username">Username:</label>

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

  </fieldset>

    

  <fieldset>

    <label for="password">Password:</label>

    <input type="password" name="password" id="password">

  </fieldset>

    

  <fieldset>

    <input type="submit">

  </fieldset>

</form>

This is a pretty basic signup form that posts to my-api.php. This section covers only the front end of the REST API. Building the back end is up to you.

Later in the Forms in Backbone section, I show you how to connect this form to Backbone and sync automatically with the back end. But for now, I show you how to do it manually.

Posting the Form

Now, post the form using jQuery's Ajax API. First, hijack the form's submit event, then get the post method and URL from the form element:

// hijack the form's submit event

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

  // prevent it from posting the old fashioned way

  e.preventDefault();

  

  // pull the post method and URL

  var $form = $(this),

  postMethod = $form.attr('method') || 'GET',

  postURL = $form.attr('action'),

    

  // log it

  console.log('Form submitted to ' + postURL + ' via ' + postMethod);

});

The preceding code gets the method and action attributes off the form element. Notice how there's a failsafe if method is undefined—in that case, forms default to the GET method.

Next, use these variables to set up an Ajax request, pulling the various values off the form to create the post data:

// hijack the form's submit event

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

  // prevent it from posting the old fashioned way

  e.preventDefault();

  

  // pull the post method and URL

  var $form = $(this),

  postMethod = $form.attr('method') || 'GET',

  postURL = $form.attr('action'),

  

  // set up an AJAX request

  $.ajax({

    url: postURL,

    type: postMethod,

    data: {

      // pull the values off the form

      name: $form.find('input[name="name"]').val(),

      email: $form.find('input[name="email"]').val(),

      username: $form.find('input[name="username"]').val(),

      password: $form.find('input[name="password"]').val()

    },

    // on post success

    success: function(data) {

      // log the response

      console.log(data);

    },

    // on post error

    error: function(e) {

      // log error info

      console.log('Error submitting form - ' + e.status + ': ' + e.statusText);

    }

  });

});

Here, the post URL and method pulled off the form element are used to create an Ajax request. To build the post data, you pull the various elements off the form and fetch their values.

Setting Up a Universal Function

Pulling each value off the form manually is a pretty crude approach. It's annoying to implement, especially if you have to set it up for more than one form. But more importantly, it couples the JavaScript tightly to the DOM.

It's much better to set up a universal function you can use to post any form via Ajax. You've already seen how the form markup can drive this functionality after you pull the post method and URL off the form element. To take this to the next level, pull all the data you need from the form.

First, make sure the names of the fields in your form match up with the keys of the JSON you want to pass to the server. Once they match up, all you have to do is serialize the form data and post it:

// hijack the form's submit event

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

  // prevent it from posting the old fashioned way

  e.preventDefault();

  

  // pull the post method and URL

  var $form = $(this),

  postMethod = $form.attr('method') || 'GET',

  postURL = $form.attr('action'),

  

  // serialize the form data

  var postData = $form.serialize();

  

  // set up an AJAX request

  $.ajax({

    url: postURL,

    type: postMethod,

    data: postData,

    // on post success

    success: function(data) {

      // log the response

      console.log(data);

    },

    // on post error

    error: function(e) {

      // log error info

      console.log('Error submitting form - ' + e.status + ': ' + e.statusText);

    }

  });

});

This example takes advantage of jQuery's serialize() API, which converts all the data in a form into an object. serialize() is handy—without it you have to loop through all the elements in the form and pull the value from each.

Following this procedure ensures that every form in your app posts via Ajax.

If you don't want to post every form via Ajax, you can set up a classname such as post-via-ajax to hook into when you set up the submit() handler.

Forms in Backbone

If you're using Backbone, you can set up your form to sync automatically with a REST API. Setting this up in Backbone is more complicated than simply setting up an Ajax script.

But Backbone provides a lot of other niceties you can use to enhance the experience of your form. In this example, you create a form that displays inline error messages as the user enters invalid data.

Setting Up the Form Model

The first step to creating a form in Backbone is defining the model. The idea is to do so before you write any markup. That's because Backbone is all about the data. In general, that's how you will work: creating an app that is driven by the data, not the other way around. So start by defining a model to store the various values you want when a user signs up. Notice that this process has nothing to do with forms; it's all about the business logic your app needs.

var User = Backbone.Model.extend({

  defaults: {

    name: '',

    email: '',

    username: '',

    password: '',

    passwordConf: ''

  }

});

var user = new User;

Here, you set up some defaults for the form fields. In order to sign up, users must provide their name, email, username, and password. They'll also have to confirm their password. For now, you set these to blank values, because the fields in the form will start out blank.

Normally, you'd build validation into your model using validate() in order to check the valid email, confirmed password, and so on. However, this form is a special case, and Backbone's native model validation will cause problems.

For the sign-up process, the user's password needs to be part of this model. But after the user signs up, be sure not to relay it back to the front end. Use a session variable or some other means to keep the user logged in, without having to expose his or her password—this is absolutely essential for security.

Setting Up the Form View

Now that you have the model, it's time to set up the view and template, but first, start with the on-page markup:

<div id="signup-form-wrapper"></div>

This element is just a wrapper that you'll use to insert the form content into the DOM. In order to make the form accessible without JavaScript, you should also fill this wrapper with a static version of the form. However, I'm skipping that to make the example shorter. Now, define the view for the sign-up form:

var SignupView = Backbone.View.extend({

  el: '#signup-form-wrapper',

  template: _.template( $('#form-template').text() ),

  

  initialize: function() {

    // render the form

    this.render();

  },

  

  render: function() {

    // convert the model to an object

    var modelData = this.model.toJSON();

    

    // insert it into the DOM

    this.$el.html( this.template( modelData ) );

  }

});

// create a new instance of the view

var signupView = new SignupView({

  model: user

});

So far the view is pretty straightforward. First, you tie it to the wrapper element you already put on the page. Then you use an Underscore template. Finally, you render the view when it loads using the template and the data from the model. Now, you need to create the template. For this, you use similar markup to the sign-up form you built earlier this chapter:

<script type="text/template" id="form-template">

<form method="post" action="my-api.php">

  <fieldset>

    <label for="name">Name:</label>

    <input type="text" name="name" id="name" value="<%= name %>">

  </fieldset>

  

  <fieldset>

    <label for="email">Email:</label>

    <input type="email" name="email" id="email" value="<%= email %>">

  </fieldset>

  

  <fieldset>

    <label for="username">Username:</label>

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

value="<%= username %>">

  </fieldset>

  

  <fieldset>

    <label for="password">Password:</label>

    <input type="password" name="password" id="password" value="<%= password

%>">

  </fieldset>

  

  <fieldset>

    <label for="password-conf">Confirm Password:</label>

    <input type="password" name="passwordConf" id="password-conf" value="<%=

passwordConf %>">

  </fieldset>

  

  <fieldset>

    <input type="submit">

  </fieldset>

</form>

</script>

Here, you create the form that posts to whichever location you need it to. Pay careful attention to the ID of the template's <script> element. The ID needs to match the selector you use in your view.

As you can see in Figure 5-15, the form is now rendering on the page.

9781118524404-fg0515.tif

Figure 5-15 The sign-up form is being rendered from the model using an Underscore template.

Saving Form Fields to the Model

Now that you've built your view, you need to figure out a way to relay the data the user enters in the form back to the model. To do that, you need a new concept: Backbone view events.

You've already used internal Backbone events to track changes to models, collections, and so on. But now you'll use view events to bind different handlers to elements in the view. For instance, you can bind a handler for change events on any of the form fields:

var SignupView = Backbone.View.extend({

  el: '#signup-form-wrapper',

  template: _.template( $('#form-template').text() ),

  

  events: {

    'change input': 'inputChange'

  },

  

  initialize: function() {

    // bind the this context

    _.bindAll( this, 'inputChange' );

    

    // render the form

    this.render();

  },

  

  render: function() {

    // convert the model to an object

    var modelData = this.model.toJSON();

    

    // insert it into the DOM

    this.$el.html( this.template( modelData ) );

  },

  // whenever a form field changes

  inputChange: function(e) {

    var $input = $(e.target);

    

    // get the name of the key in the model

    var inputName = $input.attr('name'),

    

    // set the new value in the model

    this.model.set(inputName, $input.val());

  }

});

Here, the events property of the view object is used to bind the change handler to the input. The syntax for that object is as follows: ‘eventType selector': callback. For example, to bind a click handler to all <a> elements, you write

myView.events = {

  'click a': clickHandler

}

The handler for the change event is pretty straightforward: You use the name and value off the input and use those to set the corresponding value in the user model.

It's important to bind the view events through Backbone. Doing so ensures that they are applied every time the view renders.

Adding Validation

Now, you need to set up validation for the fields in your form. Sure you could rely on HTML5 validation, but you're going to get a bit fancier here. I'm going to show you how to render inline validation messages through your view.

Normally, you set up validation on the model, but in this case, that's a bit too fussy. Because you're saving the data to the model at each step along the way, the script might throw errors while users are still entering data. For instance, when they enter the first password, it by definition doesn't match the password confirmation and throws an error every time. Worst of all, the script doesn't even save the password confirmation to the model, so there's no way to complete the form successfully.

The first step is to add a new model to store any errors:

// model for invalid fields

var Invalid = Backbone.Model.extend({});

var User = Backbone.Model.extend({

  defaults: {

    name: '',

    email: '',

    username: '',

    password: '',

    passwordConf: ''

  },

  

  initialize: function() {

    // tie in a sub-model to house invalid fields

    this.set('invalid', new Invalid);

  }

});

var user = new User;

This creates a new model for invalid fields and then ties that in as a sub-model of the original user model. It's important to define it in the user model's initialize function—if you set it as part of the defaults, all new instances of this model will be bound to the same instance of the Invalid model.

Now, you can add a custom validation function to your view, adding any errors to the invalid model:

var SignupView = Backbone.View.extend({

  el: '#signup-form-wrapper',

  template: _.template( $('#form-template').text() ),

  

  events: {

    'change input': 'inputChange'

  },

  

  initialize: function() {

    // bind the this context

    _.bindAll( this, 'validateForm', 'inputChange' );

    

    // render the form

    this.render();

  },

  

  render: function() {

    // convert the model and sub-model to an object

    var modelData = this.model.toJSON();

    modelData.invalid = modelData.invalid.toJSON();

    

    // insert it into the DOM

    this.$el.html( this.template( modelData ) );

  },

  

  validateForm: function() {

    // convert the model data to an object

    var data = this.model.toJSON();

    data.invalid = data.invalid.toJSON();

    

    // check for valid email

    var emailRegex = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-

   9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-

   9](?:[a-z0-9-]*[a-z0-9])?/;

    

    if ( data.email.length && ! data.email.match(emailRegex) ) {

      // add it to the invalid model

      this.model.get('invalid').set('email', 'Must provide a valid email'),

    }

    else {

      // otherwise remove it

      this.model.get('invalid').unset('email'),

    }

    

    // check that passwords match

    if ( data.password.length && data.passwordConf.length && data.password !=

   data.passwordConf ) {

      // add it to the invalid model

      this.model.get('invalid').set('password', "Passwords don't match");

      this.model.get('invalid').set('passwordConf', "Passwords don't match");

    }

    else {

      // otherwise remove it

      this.model.get('invalid').unset('password'),

      this.model.get('invalid').unset('passwordConf'),

    }

       

    // if any invalid fields, return false, otherwise return true

    if ( _.size( this.model.get('invalid').toJSON() ) ) {

      return false;

    }

    else {

      return true;

    }

  },

  

  // whenever a form field changes

  inputChange: function(e) {

    var $input = $(e.target);

    

    // get the name of the key in the model

    var inputName = $input.attr('name'),

    

    // set the new value in the model

    this.model.set(inputName, $input.val());

    

    // check if the form is valid, if not re-render it to display

    // error

    if ( ! this.validateForm() ) this.render();

  }

});

Here's a break down of this view:

1. If the user has filled out the email input, you check it against an email regex. If the email doesn't match, you're adding an error message to the Invalid sub-model.

2. However, if the email is valid, you remove any message from the Invalid sub-model. Consequently, if the user entered incorrect data previously, the script will remove the message once the data is sufficient.

3. If the user has filled out both password fields, you check to make sure they match. If not, you set both errors in the Invalid sub-model.

4. At the end of the validation function, you check the length of the Invalid sub-model using Underscore's _.size() method. That way, you can return true or false, depending on whether the form is valid.

5. At the end of the inputChange() function, you call the validateForm() function. If it returns false, you re-render the form to display any errors.

Because the validation function is called every time an input changes, the form renders errors as they occur; hence, the user can see potential problems before submitting the form. Additionally, take a look at the render function. It's mostly the same, except that you add the invalid object to the data that you're passing to the template—because you need that information to render the errors in the form.

Now, you need to add error messages to the template. You add a class to any field with an error, along with a <span> to hold the error message:

<script type="text/template" id="form-template">

<form method="post" action="my-api.php">

  <fieldset>

    <label for="name">Name:</label>

    <input type="text" name="name" id="name" value="<%= name %>" <%=

   invalid.name ? 'class="error"' : "" %>>

    <% if ( invalid.name ) { %>

      <span class="error-message"><%= invalid.name %></span>

    <% } %>

  </fieldset>

  

  <fieldset>

    <label for="email">Email:</label>

    <input type="email" name="email" id="email" value="<%= email %>" <%= invalid.email ? 'class="error"' : "" %>>

    <% if ( invalid.email ) { %>

      <span class="error-message"><%= invalid.email %></span>

    <% } %>

  </fieldset>

  

  <fieldset>

    <label for="username">Username:</label>

    <input type="text" name="username" id="username" value="<%= username %>"

  <%= invalid.username ? 'class="error"' : "" %>>

    <% if ( invalid.username ) { %>

      <span class="error-message"><%= invalid.username %></span>

    <% } %>

  </fieldset>

  

  <fieldset>

    <label for="password">Password:</label>

    <input type="password" name="password" id="password" value="<%= password

  %>" <%= invalid.password ? 'class="error"' : "" %>>

    <% if ( invalid.password ) { %>

      <span class="error-message"><%= invalid.password %></span>

    <% } %>

  </fieldset>

  

  <fieldset>

    <label for="password-conf">Confirm Password:</label>

    <input type="password" name="passwordConf" id="password-conf" value="<%=

   passwordConf %>" <%= invalid.passwordConf ? 'class="error"' : "" %>>

    <% if ( invalid.passwordConf ) { %>

      <span class="error-message"><%= invalid.passwordConf %></span>

    <% } %>

  </fieldset>

  

  <fieldset>

    <input type="submit">

  </fieldset>

</form>

</script>

As you can see here, you added the appropriate error messages. Now, you can hook into the error classname on the fields and add a red border with CSS. You should also display the error-message spans in red, as shown in Figure 5-16.

9781118524404-fg0516.tif

Figure 5-16 The form is displaying error messages properly.

Re-rendering the form to display errors removes the cursor focus from whichever field the user is editing. For an extra credit assignment, figure out a way to pull in what it currently focused before rendering and then reapply the focus afterward.

Cleaning Up the Template

After adding the validation messages to the form template, you may have noticed that it's getting a bit long and unwieldy. So now you should clean it up a bit. You can go about this a couple of ways. You could loop through the fields and generate the form. However, then you'd have to add some more code to your models, which would only concern how the form displays, so that's not such a good idea. The better approach is to create a helper function you can leverage within the template:

<script type="text/template" id="form-template">

<%

var displayField = function(fieldKey, fieldValue, displayName, inputType) {

  if ( typeof( inputType ) === 'undefined' ) inputType = 'text'

  %>

  <fieldset>

    <label for="<%= fieldKey %>"><%= displayName %>:</label>

    <input type="<%= inputType %>" name="<%= fieldKey %>" id="<%= fieldKey

  %>" value="<%= fieldValue %>" <%= invalid[fieldKey] ? 'class="error"' : ""

  %>>

    <% if ( invalid[fieldKey] ) { %>

      <span class="error-message"><%= invalid[fieldKey] %></span>

    <% } %>

  </fieldset>

  <%

}

%>

<form method="post" action="my-api.php">

  <% displayField('name', name, 'Name'), %>

  

  <% displayField('email', email, 'Email', 'email'), %>

  

  <% displayField('username', username, 'Username'), %>

  

  <% displayField('password', password, 'Password', 'password'), %>

  

  <% displayField('passwordConf', passwordConf, 'Confirm Password',

  'password'), %>

  

  <fieldset>

    <input type="submit">

  </fieldset>

</form>

</script>

Here, you create the helper function, displayField(), which accepts a few arguments:

1. The key of the field

2. The value of what has been entered into that field

3. The display name you want to show in the <label>

4. An optional input type that defaults to text inputs

Once you input the correct data, the helper outputs the correct markup. As you can see, the template is looking a lot more manageable.

You could have included this helper in your JavaScript core—after all, your template can leverage any of the JavaScript in your app. But because this functionality is specific to the template, it's a better idea to just include the helper right there.

Required Fields

Now, your sign-up form looks pretty complete, but before you can submit the form, you need to make sure the user has filled out all the fields. To do so, modify the validateForm() function you built earlier:

validateForm: function(checkRequired) {

  // convert the model data to JSON

  var data = this.model.toJSON();

  data.invalid = data.invalid.toJSON();

  

  // save a message for required field - this will be reused a lot

  var requiredMsg = 'Required field';

  

  // check for valid email

  var emailRegex = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-

9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-

9](?:[a-z0-9-]*[a-z0-9])?/;

  

  if ( data.email.length && ! data.email.match(emailRegex) ) {

    // add it to the invalid model

    this.model.get('invalid').set('email', 'Must provide a valid email'),

  }

  else {

    // otherwise remove it if it's not a required field

    if ( data.invalid.email != requiredMsg ) {

      this.model.get('invalid').unset('email'),

    }

  }

  

  // check that passwords match

  if ( data.password.length && data.passwordConf.length && data.password != data.passwordConf ) {

    // add it to the invalid model

    this.model.get('invalid').set('password', "Passwords don't match");

    this.model.get('invalid').set('passwordConf', "Passwords don't match");

  }

  else {

    // otherwise remove it if it's not a required field

    if ( data.invalid.password != requiredMsg ) {

      this.model.get('invalid').unset('password'),

    }

    

    if ( data.invalid.passwordConf != requiredMsg ) {

      this.model.get('invalid').unset('passwordConf'),

    }

  }

  

  // check required fields

  if ( checkRequired ) {

    // make sure that all the fields are filled out

    _.each(data, function(value, key) {

      // check everything except the invalid model

      if ( key == 'invalid' ) return false;

      

      // if empty field

      if ( ! value.length ) {

        // add it to the invalid model

        this.model.get('invalid').set(key, requiredMsg);

      }

      else {

        // otherwise remove the invalid flag - but only if it's  

        // a required field flag

        if ( data.invalid[key] == requiredMsg ) {

          this.model.get('invalid').unset(key);

        }

      }

    }, this);

  }

  

  // if any invalid fields, return false, otherwise return true

  if ( _.size( this.model.get('invalid').toJSON() ) ) {

    return false;

  }

  

  return true;

  

},

Note that you first build in a checkRequired argument for the function to determine whether you're checking the required fields. It'd be pretty annoying to users if you constantly checked the required fields as they entered data into the form. So, you check them only when the form is actually submitted.

If the checkRequired argument is set, you use Underscore's _.each() method to loop through the fields in the model (skipping the invalid sub-model). If the field is empty, you pass the requiredMsg you set earlier in the function to the invalid sub-model; if the field isn't empty, you remove any invalid flag that was set previously. However, you need to check that you remove only required field errors from the invalid list; otherwise, you might remove an error message for an invalid email or mismatched password. So you compare the value in the invalid sub-model against the requiredMsg string. Finally, you must add the same failsafe to the email and password checks to ensure that you're not removing required field errors.

As shown in Figure 5-17, the empty fields throw errors if you call signupView.validateForm(true).

9781118524404-fg0517.tif

Figure 5-17 When the form is submitted, you check that all the fields are filled.

To make this example simpler, I just assume all the fields are required. But you could set up a separate list of required fields in a new sub-model and only loop through the ones that are required.

Submitting the Form

Finally, you need to create a way to submit this form to the REST API. Fortunately, all of the user's input is already being stored in the model. You just have to set up a method to sync it. To do so, set up a submit handler in the view's events object:

var SignupView = Backbone.View.extend({

  el: '#signup-form-wrapper',

  template: _.template( $('#form-template').text() ),

  

  events: {

    'change input': 'inputChange',

    'submit form': 'saveForm'

  },

  

...

  

  // when the form submits

  saveForm: function(e) {

    // prevent it from saving the form the old fashioned way

    e.preventDefault();

    

    // validate the form

    if ( this.validateForm(true) ) {

      // if valid, save the model

      this.model.save();

    }

    else {

      // otherwise render the errors

      this.render();

    }

  }

});

Here, a submit handler is set up on the form that triggers your saveForm() callback. In this callback, the default behavior of the form is prevented to make sure it doesn't post the old-fashioned way. Then you call the validateForm() function, passing the checkRequired argument to make sure it checks required fields. If the form passes validation, it is saved so that Backbone automatically syncs the model via Ajax. Otherwise, it is re-rendered to display any errors. However, Backbone doesn't know where to post this form, so you still need to get the URL from the form's action attribute:

// when the form submits

saveForm: function(e) {

  // prevent it from saving the form the old fashioned way

  e.preventDefault();

  

  // validate the form

  if ( this.validateForm(true) ) {

    // set the API location using the URL from the form

    this.model.url = this.$el.find('form').attr('action'),

    

    // if valid, save the model

    this.model.save();

  }

  else {

    // otherwise render the errors

    this.render();

  }

}

Alternatively, you could simply declare the url when you create the model, but let's keep the form markup driven.

Now, Backbone will post the form data to the same URL the form posts to, but you still have a little work to do. Some superfluous information in the model should not be sent to the server. For instance, the invalid sub-model and the password confirmation are not really necessary data to send to the API. To reduce the size of this request, remove them when you call the save() method. Simply clean up an object of data you want to save and pass that in:

// when the form submits

saveForm: function(e) {

  // prevent it from saving the form the old fashioned way

  e.preventDefault();

  

  // validate the form

  if ( this.validateForm(true) ) {

    // set the API location using the URL from the form

    this.model.url = this.$el.find('form').attr('action'),

    

    // clean up the data for the API

    var data = this.model.toJSON();

    delete data.invalid;

    delete data.passwordConf;

    

    // if valid, save the model

    this.model.save(data);

  }

  else {

    // otherwise render the errors

    this.render();

  }

}

This removes the unnecessary data from the model and passes that into the save() function. In that way, the script only passes relevant data to the API.

Putting It All Together

Now, your form is properly connected with Backbone. It is displaying inline errors as they occur in the form and then submitting the form data via Ajax. Piecing all the code together:

<div id="signup-form-wrapper"></div>

<script type="text/template" id="form-template">

<%

var displayField = function(fieldKey, fieldValue, displayName, inputType) {

  if ( typeof( inputType ) === 'undefined' ) inputType = 'text'

  %>

  <fieldset>

    <label for="<%= fieldKey %>"><%= displayName %>:</label>

    <input type="<%= inputType %>" name="<%= fieldKey %>" id="<%= fieldKey

%>" value="<%= fieldValue %>" <%= invalid[fieldKey] ? 'class="error"' : ""

%>>

    <% if ( invalid[fieldKey] ) { %>

      <span class="error-message"><%= invalid[fieldKey] %></span>

    <% } %>

  </fieldset>

  <%

}

%>

<form method="post" action="my-api.php">

  <% displayField('name', name, 'Name'), %>

  

  <% displayField('email', email, 'Email', 'email'), %>

  

  <% displayField('username', username, 'Username'), %>

  

  <% displayField('password', password, 'Password', 'password'), %>

    

  <% displayField('passwordConf', passwordConf, 'Confirm Password',

'password'), %>

  

  <fieldset>

    <input type="submit">

  </fieldset>

</form>

</script>

<script type="text/javascript">

var User = Backbone.Model.extend({

  defaults: {

    name: '',

    email: '',

    username: '',

    password: '',

    passwordConf: ''

  },

  

  initialize: function() {

    // tie in a sub-model to house invalid fields

    this.set('invalid', new Invalid);

  }

});

// model for invalid fields

var Invalid = Backbone.Model.extend({});

var user = new User;

var SignupView = Backbone.View.extend({

  el: '#signup-form-wrapper',

  template: _.template( $('#form-template').text() ),

  

  events: {

    'change input': 'inputChange',

    'submit form': 'saveForm'

  },

  

  initialize: function() {

    // bind the this context

    _.bindAll( this, 'validateForm', 'inputChange', 'saveForm' );

    

    // render the form

    this.render();

  },

  

  render: function() {

    // convert the model and sub-model to an object

    var modelData = this.model.toJSON();

    modelData.invalid = modelData.invalid.toJSON();

    

    // insert it into the DOM

    this.$el.html( this.template( modelData ) );

  },

  

  validateForm: function(checkRequired) {

    // convert the model data to JSON

    var data = this.model.toJSON();

    data.invalid = data.invalid.toJSON();

    

    // save a message for required field - this will be reused a lot

    var requiredMsg = 'Required field';

    

    // check for valid email

    var emailRegex = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-

9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-

9](?:[a-z0-9-]*[a-z0-9])?/;

    

    if ( data.email.length && ! data.email.match(emailRegex) ) {

      // add it to the invalid model

      this.model.get('invalid').set('email', 'Must provide a valid email'),

    }

    else {

      // otherwise remove it if it's not a required field

      if ( data.invalid.email != requiredMsg ) {

        this.model.get('invalid').unset('email'),

      }

    }

    

    // check that passwords match

    if ( data.password.length && data.passwordConf.length && data.password !=

  data.passwordConf ) {

      // add it to the invalid model

      this.model.get('invalid').set('password', "Passwords don't match");

      this.model.get('invalid').set('passwordConf', "Passwords don't match");

    }

    else {

      // otherwise remove it if it's not a required field

      if ( data.invalid.password != requiredMsg ) {

        this.model.get('invalid').unset('password'),

      }

      

      if ( data.invalid.passwordConf != requiredMsg ) {

        this.model.get('invalid').unset('passwordConf'),

      }

    }

    

    // check required fields

    if ( checkRequired ) {

      // make sure that all the fields are filled out

      _.each(data, function(value, key) {

        // check everything except the invalid model

        if ( key == 'invalid' ) return false;

        

        // if empty field

        if ( ! value.length ) {

          // add it to the invalid model

          this.model.get('invalid').set(key, requiredMsg);

        }

        else {

          // otherwise remove the invalid flag - but only if

          // it's a required field flag

          if ( data.invalid[key] == requiredMsg ) {

            this.model.get('invalid').unset(key);

          }

        }

      }, this);

    }

    

    // if any invalid fields, return false, otherwise return true

    if ( _.size( this.model.get('invalid').toJSON() ) ) {

      return false;

    }

    else {

      return true;

    }

  },

  

  // whenever a form field changes

  inputChange: function(e) {

    var $input = $(e.target);

    

    // get the name of the key in the model

    var inputName = $input.attr('name'),

    

    // set the new value in the model

    this.model.set(inputName, $input.val());

    

    // check if the form is valid, if not re-render it to display

    // error

    if ( ! this.validateForm(false) ) this.render();

  },

  

  // when the form submits

  saveForm: function(e) {

    // prevent it from saving the form the old fashioned way

    e.preventDefault();

    

    // validate the form

    if ( this.validateForm(true) ) {

      // set the API location using the URL from the form

      this.model.url = this.$el.find('form').attr('action'),

      

      // clean up the data for the API

      var data = this.model.toJSON();

      delete data.invalid;

      delete data.passwordConf;

      

      // if valid, save the model

      this.model.save(data);

    }

    else {

      // otherwise render the errors

      this.render();

    }

  }

});

// create a new instance of the view

var signupView = new SignupView({

  model: user

});

</script>

Just to recap what's going on here:

1. You create a template to display the form. It uses a helper function to display each form field along with any error message that may have occurred.

2. You create a model for the form data, along with a sub-model of invalid fields.

3. You build the view and attach events to track changes in the form fields, as well as the submission of the form.

4. When the view initializes, you render it with the template.

5. You validate the form fields every time one of them changes. You check the Email field against a regex and make sure the passwords match.

6. When the form submits, you also make sure that all the fields are filled.

7. Whenever a form input changes, you save the change in the model and validate the form, re-rendering it if there are error messages.

8. You hijack the form's submission event. If it's valid, you pull the post URL from the form and then sync a cleaned up version of the model with the server. However, if it's invalid, you re-render the form to show any error messages.

Summary

In this chapter, you took your app's forms to the next level. You learned about the progressive enhancement approach and how to use native HTML5 form features to enhance your forms without any JavaScript whatsoever.

Then you found out how to back up HTML5 features for older browsers using third-party polyfills, as well as how to write your own polyfill. Next, you discovered how to hijack the form's default submission behavior and, instead, post it to your API using Ajax. Finally, you learned how to integrate forms into Backbone, creating a data-driven form that displays inline error messages as they occur, and syncs with the server automatically.

Forms are an essential part of just about any app. A good form experience engages users and encourages them to complete signups, checkouts, and other critical actions related to your app's business logic. Consequently, many companies have discovered that the quality of their forms are directly linked to profits.

Additional Resources

HTML5 Form Basics

The Current State of HTML5 Forms: http://www.wufoo.com/html5/

Making Forms Fabulous with HTML5: http://www.html5rocks.com/en/tutorials/forms/html5forms/

A Form of Madness (Dive into HTML5): http://diveintohtml5.info/forms.html

HTML5 Form Tutorials

How to Build Cross-Browser HTML5 Forms: http://net.tutsplus.com/tutorials/html-css-techniques/how-to-build-cross-browser-html5-forms/

Constraint Validation: Native Client Side Validation for Web Forms: http://www.html5rocks.com/en/tutorials/forms/constraintvalidation/

Make Disaster-Proof HTML5 Forms: http://www.netmagazine.com/tutorials/make-disaster-proof-html5-forms

Usability

Web Form Design by Luke Wroblewski (highly recommended): http://amzn.to/YFjZXq

An Extensive Guide to Web Form Usability: http://uxdesign.smashingmagazine.com/2011/11/08/extensive-guide-web-form-usability/

Forward Thinking Form Validation: http://alistapart.com/article/forward-thinking-form-validation

Accessibility

W3C Recommendations for Form Accessibility: http://www.w3.org/TR/WCAG10-HTML-TECHS/#forms

Screen Reader Form Accessibility: http://webaim.org/techniques/forms/screen_reader

Accessible Forms: http://www.jimthatcher.com/webcourse8.htm

Polyfills: https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills

Widgets

HTML5 Forms: http://www.useragentman.com/blog/2010/07/27/creating-cross-browser-html5-forms-now-using-modernizr-webforms2-and-html5widgets-2/

jQuery UI: http://jqueryui.com/

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

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