Form manipulation

As of version 0.2, Ojay includes a package called Ojay.Forms. This package provides a number of useful classes and APIs for dealing with forms: it allows for easy serialization and submission using Ajax; it allows radio buttons, checkboxes and drop-down menus to be styled using CSS, and it provides a validation mini-language for checking data before it is sent to the server.

Required files

  • http://yui.yahooapis.com/2.7.0/build/yahoo-dom-event/yahoo-dom-event.js
  • http://yui.yahooapis.com/2.7.0/build/selector/selector-beta.js
  • http://yoursite.com/ojay/js-class.js
  • http://yoursite.com/ojay/core.js
  • http://yoursite.com/ojay/pkg/forms.js

Examples

Utility functions

First off, the small stuff. Ojay.Forms has a few utility methods that help when dealing with forms. They are as follows:

  • Ojay.Forms.getLabel(input) – this returns an Ojay collection wrapping the label tag associated with the given form input, which can be a CSS selector string or an element reference. It knows about labels as parent elements and about the for attribute and makes dealing with labels really simple.
  • Ojay.Forms.getQueryString(form) – returns the form’s current data as a query string. form can be a CSS selector string or an element reference.
  • Ojay.Forms.getData(form) – the same as getQueryString(), except that it returns the data as an object where the property names mirror the form’s input names.
  • Ojay.Forms.submit(form) – this is like calling form.submit(), except that the form submission is routed through Ojay’s form rules system so any validation and Ajax rules are checked before the submission is allowed to complete.

Visual styling

Ojay.Forms includes a set of classes that can be used to replace unstylable form elements with HTML you can style with CSS. These classes are designed to be lightweight, unobtrusive replacements for the YAHOO.widget.Button family of classes for the basic case where you just want to style form inputs. Ojay’s input replacements do not rely on extra button elements and hidden fields – they simply hide the original inputs and use class names on label tags to allow styling using CSS. As the fundamental HTML structure of the form is left untouched, you can still read data out of the form much more easily than you can using the YAHOO classes.

Setting up checkboxes and radio buttons is straightforward. Just set up a form that works without JavaScript, and associate a label with each form element. Then make a couple of JavaScript calls and have your inputs transformed. See the example implementation for more details on CSS class names and the like.

    <form action="/" method="post" id="the-form">
      <div class="radios">
        <input type="radio" name="cc" value="visa" 
            id="cc_visa" />
        <label for="cc_visa">Visa</label>

        <input type="radio" name="cc" value="mastercard" 
            id="cc_mastercard" />
        <label for="cc_mastercard">Mastercard</label>

        <input type="radio" name="cc" value="amex" 
            id="cc_amex" />
        <label for="cc_amex">AmEx</label>

        <input type="radio" name="cc" value="switch" 
            id="cc_switch" />
        <label for="cc_switch">Switch</label>
      </div>
      <input type="checkbox" name="accept" id="accept" value="1" /> 
      <label for="accept">Accept terms
          and conditions?</label>
    </form>

And the script:

    new Ojay.Forms.RadioButtons('.radios input');
    new Ojay.Forms.Checkbox('#accept');

Ojay ‘hides’ the original form inputs by wrapping them in a span and using positioning, for example:

    <span style="position: relative;">
      <input type="checkbox" name="foo" 
          style="position: absolute; left: -5000px; top: 0;" />
    </span>

This is designed to preserve keyboard navigability by keeping the inputs at their original vertical place in the document; using display: none stops you from selecting the inputs using the keyboard. This works well in most situations but sometimes setting position: absolute on the span works better. To do this, pass the wrapperPosition option when setting up an Ojay.Forms object:

    new Ojay.Forms.Checkbox('#foo', {wrapperPosition: 'absolute'});

select elements are similarly easy to style. Just add a select with a label to your form and make a quick JavaScript call as follows:

    <label for="my-select">Delivery method</label>
    <select id="my-select">
      <option value="null">Choose...</option>
      <option value="first-class">First class post</option>
      <option value="recorded-delivery">Recorded delivery</option>
      <option value="air-mail">Air mail</option>
    </select>

with this script:

    var selector = new Ojay.Forms.Select('#my-select');

This will insert the following markup after the select tag in your document:

    <div class="select-container">
      <div class="select-display">Choose...</div>
      <div class="select-button"></div>
      <div class="select-list">
        <ul>
          <li class="hovered">Choose...</li>
          <li>First class post</li>
          <li>Recorded delivery</li>
          <li>Air mail</li>
        </ul>
      </div>
    </div>

The list positioning is handled for you by Ojay, and you can take care of all the presentation using CSS. We recommend that you do not add borders or padding to the select-container or select-list elements, but apply such styling to select-display and the ul element instead. This will make sure the positioning is handled correctly and still gives you room to style the menu. The hovered class on the li element will move between elements as the user interacts with the control. Again, see the example implementation for more ideas.

All label elements and select-container elements receive a selection of CSS classes as the user interacts with the form and these classes can be used in your stylesheet to change appearance. The classes are:

  • hovered – applied when the mouse is over the element
  • focused – applied when the (hidden) original form element has focus
  • disabled – applied when an element is disabled
  • checked – for radios and checkboxes, applied when the element is checked

Changing values and handling events

The Ojay.Forms.Checkbox, Ojay.Forms.RadioButtons and Ojay.Forms.Select classes all support a common API for getting and setting form values and handling events. All types of input publish a change event when their value is changed, and you can listen to such events and respond to them:

    // formInput is a Checkbox, RadioButtons or Select instance
    formInput.on('change', function(input) {
      var currentValue = input.getValue();
      // do something with value
    });

The callback will always be passed a reference to the Forms.* object that triggered the event. The Forms classes all have a getValue() method, which returns the value of the currently selected option (or true or false for checkboxes). They also have a setValue() method. This will select the option with the given value if such an option exists. For checkboxes, pass true or false to set the state of the box.

    // for radio button groups and select boxes...
    group.setValue('some-value');

    // for checkboxes...
    checkbox.setValue(true);

By default, setValue() will trigger a change event as described above if it changes the value of the input. To suppress the event from firing, pass false as the second argument to setValue().

    // don't alert listeners to the change in value
    radioButtons.setValue('the-value', false);

Finally, Checkbox, RadioButtons and Select instances have two methods for accessing their underlying DOM elements:

  • getInput() returns an Ojay collection wrapping the input element(s) (plural for radio buttons) belonging to the instance.
  • getLabel() returns an Ojay collection wrapping the label element(s) belonging to the instance.

Validation API

The final aspect of the Forms package is the validation framework. This provides developers with a very high-level API for expressing form validation rules, error handling routines and Ajax submission processes. To inspect form data, this part of the package uses YAHOO.util.Connect.setForm() so you will need to include that module in your page before using Ojay.Forms for validation. Also, if you want to use Ajax submission you’ll need the Ojay.HTTP package.

  • http://yui.yahooapis.com/2.7.0/build/connection/connection.js
  • http://yoursite.com/ojay/pkg/http.js

The best way to get a handle on how this API works is to read the example implementation. This takes you through defining a set of validation rules, handling errors and submitting the form with Ajax. Here we will cover the API and explain what each bit does.

First off, note that all validation rules live inside an Ojay.Forms() function call and a with block. This block is a special environment that provides a few top-level helper functions that do not exist in the global namespace.

The basic building block of a validation rule is the requires() statement, aliased as expects(). This statement says that a field with the given name, in a form with the id passed to form(), must be filled in before the form is allowed to submit.

    Ojay.Forms(function() { with(this) {
        form('login')
            .requires('username')
            .requires('password');
    }});

That’s all the code you need to write. Ojay will take care of intercepting submit events, extracting form data and validating it. If any errors are found, the form will not be allowed to submit and the list of error messages will be passed back to your code – we’ll get on to how to handle them in due course.

As well as the basic requires() statement, Ojay.Forms provides a few helper functions that implement some common validation requirements. To use them, you just call them after the appropriate requires() or expects() statement.

    Ojay.Forms(function() { with(this) {
        form('signup')
            .requires('username')   .toHaveLength({minimum: 6})
            .requires('email')      .toMatch(EMAIL_FORMAT)
            .requires('email_conf') .toConfirm('email');
    }});

Note that EMAIL_FORMAT is a constant defined for you, since it tends to come up pretty often in web forms. The full list of validation functions is as follows:

  • toBeChecked() – useful for making sure checkboxes are checked.
  • toBeNumeric() – makes sure the content of a field is a number.
  • toBeOneOf(list) – makes sure the data matches one of the values in the array list. Comparison is performed using ==.
  • toBeNoneOf(list) – makes sure the data does not equal any of the values in the array list.
  • toConfirm(field) – checks that the required field’s value matches that in in the input with name field.
  • toHaveLength(options) – makes sure the input has a certain length. options can be a number, or an object with minimum and/or maximum fields.
  • toHaveValue(options) – checks that the value falls within a certain range. options is an object with minimum and/or maximum fields.
  • toMatch(regexp) – uses a regular expression to test the input.

Cusom error messages. All these helper methods will generate default error messages for you. The displayed name for a field is inferred from its label, or failing that, its name attribute. Error messages are particular to the type of validation check. Both the display names and error messages can be overridden with your own custom messages as follows:

  • To show a custom name, pass a second string argument to requires()/expects().
  • To show a custom message, pass an additional argument to any of the validation methods.

Some examples to illustrate:

    Ojay.Forms(function() { with(this) {

        // Gives message "User email is not valid" 
        form('signup')
            .requires('userEmail').toMatch(EMAIL_FORMAT);

        // Gives message "Your email address is not valid" 
        form('signup')
            .requires('userEmail', 'Your email address')
            .toMatch(EMAIL_FORMAT);

        // Gives message "User email is not a valid email address" 
        form('signup')
            .requires('userEmail')
            .toMatch(EMAIL_FORMAT, 'is not a valid email address');
    }});

Custom validation routines. If you have more specific validation rules that the above methods cannot handle, you can write your own custom validation methods using the validates() statement. Within your callback, you can get() data from the form and add() errors by field name as in the example:

    form('signup').validates(function(data, errors) {
        if (/admin/i.test(data.get('username')))
            errors.add('username', 'cannot contain the phrase "admin"');
    });

If you want to add an error to the form as a whole rather than a specific field, call errors.addToBase(message) instead of errors.add(field, message).

Error handling. So now that our rules are set up, we need to do something to display the errors to the user. The example provides this snippet (remember, this should all go inside an Ojay.Forms( ... ) block):

    when('signup').isValidated(function(errors) {
        Ojay('#signup .error-message').remove();
        errors.forEach(function(error) {
            var field = Ojay('#signup input[name=' + error.field + ']');
            field.insert(Ojay.HTML.div({
                className: 'error-message'
            }, error.message), 'after');
        });
    });

You use when(id).isValidated() to respond to failed submission attempts. Ojay.Forms hands you a list of error objects, each with a field property (null if the error comes from an addToBase() call) and a message property. You can loop through these errors and add them to your document – the above example displays each error after its respective field, as we know we’re not using any addToBase() errors here.

If you just want to list the errors all in one block, we’ve got a helper method for that too: just specify a CSS selector or element reference for where you want the errors to be listed:

    when('signup').isValidated(displayErrorsIn('#errors'));

This will produce markup similar to the following:

    <div class="error-explanation">
        <p>There were 3 errors with the form:</p>
        <ul>
            <li>Username must contain at least 6 characters</li>
            <li>Your email is not valid</li>
            <li>Password is required</li>
        </ul>
    </div>

Ajax form submissions

The other functionality provided by the Ojay.Forms DSL is submission of forms using Ajax. Ajax-ified forms will still not submit until all their validation rules pass, though you don’t have to validate them on the client-side if you don’t want to. To handle the server response, use when(id).responseArrives(), which passes you an HTTP.Response object just like all our HTTP methods.

    Ojay.Forms(function() { with(this) {

        form('signup').submitsUsingAjax();
        when('signup').responseArrives(function(response) {
            response.insertInto('#results');
            // further response logic
        });
    }});

If all you want to do is insert the response into the page, use the displayResponseIn() helper:

    when('signup').responseArrives(displayResponseIn('#results'));

If you want to style your form inputs, we reccommend using the styling classes detailed above rather than the YAHOO.widget.Button family of classes. The latter require you to make calls to insert some hidden fields when the form is submitted and make repeated serialization of the form unreliable (see YUI’s known issues for more information).