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.6.0/build/yahoo-dom-event/yahoo-dom-event.jshttp://yui.yahooapis.com/2.6.0/build/selector/selector-beta.jshttp://yoursite.com/ojay/js-class.jshttp://yoursite.com/ojay/core.jshttp://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 thelabeltag associated with the given forminput, which can be a CSS selector string or an element reference. It knows about labels as parent elements and about theforattribute and makes dealing with labels really simple.Ojay.Forms.getQueryString(form)– returns the form’s current data as a query string.formcan be a CSS selector string or an element reference.Ojay.Forms.getData(form)– the same asgetQueryString(), except that it returns the data as an object where the property names mirror the form’s input names.
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');
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 elementfocused– applied when the (hidden) original form element has focusdisabled– applied when an element is disabledchecked– 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.6.0/build/connection/connection.jshttp://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 arraylist. Comparison is performed using==.toBeNoneOf(list)– makes sure the data does not equal any of the values in the arraylist.toConfirm(field)– checks that the required field’s value matches that in in the input with namefield.toHaveLength(options)– makes sure the input has a certain length.optionscan be a number, or an object withminimumand/ormaximumfields.toHaveValue(options)– checks that the value falls within a certain range.optionsis an object withminimumand/ormaximumfields.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).
