Judge 1.0.0

Simple client side form validation for Rails 3

Judge allows easy live form validation for Rails 3 apps, by porting many ActiveModel::Validation features to JavaScript and exposing your model validations as JSON within HTML5 data attributes. It's fast and it's purely on the client side – no more code duplication for standard validators, no need for gratuitous AJAX.

Dependencies

Judge relies on Underscore.js, along with json2.js, which defers to the native JSON global object in modern browsers.

Installation

Add the following to your Gemfile:

gem "judge", "~> 1.0.0"

and then run:

$ bundle install

To copy the Judge JavaScript files to your app, use the generator:

$ rails generate judge [path] [options]

The path parameter is optional and defaults to public/javascripts. So running the generator without specifying a path and with no options will give you:

The only option is --no-dep, which prevents the generator from copying Underscore.js and json2.js into your chosen directory if you already have them.

Rails 3.0

$ rails generate judge

Then do whatever it is you usually do to ensure that JavaScript files are included properly (e.g. add them to assets.yml if you are using Jammit).

Rails 3.1

Run the generator as follows to copy judge.js and its dependencies to your app:

$ rails generate judge app/assets/javascripts

These files should now be included, thanks to the following line in your manifest file (app/assets/javascripts/application.js):

//= require_tree .

If you ever come across problems with your require order, you can ensure that Judge's dependencies are required first by doing something like this:

//= require underscore
//= require json2
//= require_tree .

Quick start

As of version 1.0.0, Judge form builder methods are no longer prefixed with “validated_”. The syntax is now in line with standard Rails form builders, as follows:

<%= form_for(@post, :builder => Judge::FormBuilder) do |f|%>
  <%= f.text_field :title, :validate => true %>
  <%= f.text_area :body, :validate => true %>
<% end %>

Most Judge functionality on the client side is based on the use of watchers. A watcher is a sort of pointer object for a DOM element. Create a watcher for an input element:

var watcher = new judge.Watcher(document.getElementById('foo'));

Check whether the value of the watched input element is valid – this checks validity at the time you call validate(), not the time at which the watcher was instantiated:

watcher.validate().valid; // => true 

If you want to do a quick validation and see no benefit in creating a watcher, you can do that too. This example will validate all input elements on the page, provided they have been created with Judge::FormBuilder:

judge.validate(document.getElementsByTagName('input'));
  // => [ { valid:true, messages:[], element:HTMLInputElement },
  //      { valid:false, messages:['must be even'], element:HTMLInputElement } ]

Ruby

What’s available

You can use any of the standard form builders from ActionView::Helpers::FormBuilder – just add :validate => true to the options hash.

Validators:

The allow_blank option is available everywhere it should be. Error messages are looked up according to the Rails i18n API.

If you have any custom form builders defined that override ActionView::Helpers::FormBuilder in your app, you could make them validatable on the client side by inheriting Judge::FormBuilder instead:

class MyCustomFormBuilder < Judge::FormBuilder
  # methods like text_field, text_area etc. go here
end

What’s not

The tokenizer option for the length validator is currently missing. Options like if, unless and on, which require more involved interactions with your Rails application, aren't really viable in a universal way on the client side and are simply ignored. Validating uniqueness with Judge might happen in the future, provided the method arrived at:

  1. Makes no assumptions about your app;
  2. Can add the necessary routes/actions to your app in a way that doesn't make anyone barf; and
  3. Doesn't rely on jQuery to make requests.

For now, you can define a custom validator that makes an XMLHttpRequest if you need to.

JavaScript

This is the judge.js API documentation. For some literate programming-type goodness, check out the annotated JavaScript source, generated by Docco.

judge

The global namespace.

Static methods

validate judge.validate(elements)

Perform quick and easy validation on a single element or an array of elements (including array-like collections such as NodeList), without making public any watchers. An array of objects is returned, each object containing the validated element, a Boolean valid flag and an array of error messages, which is empty if valid is true.

judge.validate(document.getElementsByTagName('input'));
  // => [ { valid:true, messages:[], element:HTMLInputElement },
  //      { valid:false, messages:['must be even'], element:HTMLInputElement } ]

Static properties

customValidators judge.customValidators

An empty object. Add your own validators here. Let's say you have something like this set up:

# autoload this file 
class FooValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    record.errors[attribute] << "Should be foo" unless value == "foo"
  end
end
# your model
class Thing < ActiveRecord::Base
  validates :title, :foo => true
end
# your view
<%= form_for(@thing, :builder => Judge::FormBuilder) do |f| %>
  <%= text_field :title, :validate => true %>
<% end %>

Then Judge will expect a method named foo to have been added to judge.customValidators:

// on the client side
judge.customValidators.foo = function(options, messages) {
  // do your foo checking here
};

Judge validator methods receive an options object and a messages object and must return an object literal with properties valid (Boolean) and messages (an array of error messages, only necessary if valid is false).

The options object contains the validation options from your Ruby custom validator. To get a better idea of what options has to offer, open up a browser console and take a look at the JSON stored in the data-validate attribute of any form element created with the Judge form builders.

The messages object contains all possible error messages for this attribute.

Validator methods have access to a variable named watcher, which points to the watcher for the element being validated. So watcher.element gives you access to the element itself.

Any validators added to judge.customValidators will be called automatically whenever a watcher validator of the same name is detected. Phew!

judge.Watcher

Constructor for watchers. Watchers are wrapper objects that contain a DOM element and various validation methods. The constructor takes one argument, a DOM element. For example:

var watcher = new judge.Watcher(document.getElementById('foo'));

Instance methods

validate watcher.validate()

Validate the current value of the wrapped DOM element. An object is returned, containing the validated element, a Boolean valid flag and an array of error messages, which is empty if valid is true.

watcher.validate();
  // => { valid:false, messages:['must be even'], element:HTMLInputElement }

Instance properties

validators watcher.validators

Returns an array of validators (JavaScript object representations of the associated ActiveModel validators) for the wrapped element.

watcher.validators;
  // => [ { kind:'presence', options:{ … } }, { kind:'format', options:{ … } } ]

judge.store

Contains some methods for storing and retrieving watchers in groups, for more customised validation scenarios. Have you ever wanted to validate a number of form elements when the user triggers an event? Storing your reference to the elements can make things easier.

Static methods

save judge.store.save(key, element)

Store watcher(s) for element(s) against a key, for later use. If the key doesn't already exist, it will be created. Returns the store object.

judge.store.save('mykey', document.getElementById('foo'));
  // => [ { element:HTMLInputElement, … } ]

judge.store.save('mykey', document.getElementByTagName('input'));
  // => [ { element:HTMLInputElement, … }, { element:HTMLInputElement, … }, … ]
get judge.store.get(key)

Return an array of watchers saved against the key. If no key is given, the store object is returned.

judge.store.get('mykey'); 
  // => [ { element:HTMLInputElement, … } ]

judge.store.get();
  // => { mykey:[ { element:HTMLInputElement, … } ], … }
getDOM judge.store.getDOM(key)

Return an array of DOM elements from watchers stored against a key. If no key is given, the store object is returned, with all watchers converted to their wrapped DOM elements.

judge.store.getDOM('mykey');
  // => [ HTMLInputElement, HTMLInputElement … ]
  
judge.store.getDOM();
  // => { mykey: [ HTMLInputElement ], mykey2: [ HTMLInputElement … ] }
validate judge.store.validate(key)

A shortcut for judge.validate(judge.store.getDOM(key)). Validate all elements stored within watchers against the given key. Returns null if no key is passed or no watchers are found.

judge.store.validate('mykey');
// => [ { valid:true, messages:[], element:HTMLInputElement },
//      { valid:false, messages:['must be even'], element:HTMLInputElement } ]

judge.store.validate();
  // => null
remove judge.store.remove(key, element)

Remove individual stored watcher. Returns remaining watchers stored at key, or undefined if none remain (store property is then deleted). If you want to remove all watchers stored against a key, or all stored watchers, use the clear method.

judge.store.remove('mykey', document.getElementById('foo'));
  // => { mykey:[ … ], mykey2:[ … ] }
clear judge.store.clear(key)

Remove all watchers stored against a key or, if no key is given, all stored watchers. Returns store object after watchers were removed.

judge.store.clear('mykey');
  // => { mykey:[], … }
judge.store.clear();
  // => {} 

Tests

Judge uses Travis for continuous integration. Current status of master branch: Judge build status on Travis

Since Travis is still quite young, you might want to run the tests yourself:

# All tests, JavaScript tests headlessly (requires phantom.js)
$ bundle exec rake test

# Only Ruby tests
$ bundle exec rake test:ruby

# Only JavaScript tests, headless (requires phantom.js)
$ bundle exec rake jasmine:phantom

# Only JavaScript tests, with Selenium
$ bundle exec rake jasmine:ci

Changelog

1.0.0 Validator code extracted into classes; Form builder methods no longer require “validated_” prefix; Added judge.store.validate() shortcut method; Some minor implementation updates to judge.js; No more dummy app in tests, no more crappy Gemfile, no more Jeweler.

0.5.0 Error messages looked up through Rails i18n.

0.4.3 IE bug fixes: No longer checking for element type in the Watcher constructor, to avoid IE object string "quirk"; now using typeof to check for undefined properties of window.

0.4.2 Fixed bug introduced in the last bug fix – now globally replacing double slashes :)

0.4.1 Fixed slash escaping bug in judge.js RegExp converter; removed uniqueness data from data attributes to prevent judge.js from expecting a validation method.

0.4.0 Added new form builders.

0.3.1 Removed unused keys from validator options in data attributes. Include Judge::FormHelper in ApplicationHelper to avoid clash with haml aliases.

0.3.0 Added confirmation and acceptance validation, more form builders.

0.2.0 Remove jQuery dependency; refactored to include new namespace, watchers and store; added headless testing.

0.1.1 Removed duplicate dependencies in gemspec.

0.1.0 First release.