Judge 1.5.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.5.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.x

$ 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 and above

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

Add some validation to your model:

class Post < ActiveRecord::Base
  validates :title, :presence => true
end

Make sure your form uses the Judge::FormBuilder and add the :validate option to the field:

<%= form_for(@post, :builder => Judge::FormBuilder) do |f|%>
  <%= f.text_field :title, :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('post_title'));

Validate the watched element and give some simple visual feedback – this checks validity at the time you call validate(), not the time at which the watcher was instantiated:

watcher.validate(function(valid, messages, element) {
  if (!valid) {
    element.style.border = '1px solid red';
  }
});

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:false, messages:["can't be blank"], element:HTMLInputElement }, ... ]

judge.validate accepts a callback function too, which is called once for every element passed in the first argument.

Ruby

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 inherit from 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

Custom validators

If you create a custom EachValidator, Judge provides a way to ensure that your I18n error messages are available on the client side. Simply pass to declare_messages any number of message keys and Judge will look up the translated messages. Let's run through an example.

# autoload this file 
class FooValidator < ActiveModel::EachValidator
  declare_messages :not_foo

  def validate_each(record, attribute, value)
    unless value == "foo"
      record.errors.add(:title, :not_foo)
    end
  end
end

We'll use the validator in the example above to validate the title attribute of a Post object:

# your model
class Post < ActiveRecord::Base
  validates :title, :foo => true
end
# your view
<%= form_for(@post, :builder => Judge::FormBuilder) do |f| %>
  <%= text_field :title, :validate => true %>
<% end %>

Judge will look for the not_foo message at

activerecord.errors.models.post.attributes.title.not_foo

first and then onwards down the Rails I18n lookup chain.

Our client side validator would then look something like this:

judge.customValidators.foo = function(value, options, messages) {
  var errorMessages = [];
  if (value !== "foo") {
    errorMessages.push(messages.not_foo);
  }
  return errorMessages;
};

Missing

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, [callback])

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 } ]

The callback function, if provided, will be executed once for each element passed in the first argument. The callback receives three arguments: (valid, messages, element).

var elements = document.querySelectorAll('input[type=text]');
judge.validate(elements, function(valid, messages, element) {
  if (!valid) {
    element.style.border = '1px solid red';
  }
});

Static properties

customValidators judge.customValidators

An empty object. Add your own validators here. For example:

judge.customValidators.myValidator = function(value, options, messages) {
  var errorMessages = [];
  // add validation logic here
  return errorMessages;
};

Judge validator methods receive the form element value and the validator options as defined in your model. The third parameter, messages, contains any error messages that may be used to validate the form element including those explicitly declared inside custom validator classes.

Any validator methods added to judge.customValidators will be called automatically whenever a watcher validator of the same name is detected. Custom methods added here will override the standard validator types (presence, length etc.) if you use the same names – be cautious.

Custom validator methods must return an array of error messages. If the value is valid, they must return an empty array.

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([callback])

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 }

The callback function, if provided, receives three arguments: (valid, messages, element).

watcher.validate(function(valid, messages, element) {
  if (!valid) {
    element.style.border = '1px solid red';
  }
});

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.getElementsByTagName('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, [callback])

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

The callback function will be executed once for each watcher stored against the given key. Same behaviour as judge.validate.

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.save('inputs', document.getElementsByTagName('input'));
judge.store.save('textareas', document.getElementsByTagName('textarea'));
judge.store.clear('inputs');
  // => { textareas: [ HTMLInputElement … ] }
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 test suite yourself:

# All specs, JavaScript specs run headlessly (requires phantomjs and casperjs)
$ bundle exec rake

# Only Ruby specs
$ bundle exec rake spec

# Only JavaScript specs, headless (requires phantomjs and casperjs)
$ bundle exec rake jasmine:headless

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

Extensions

If you use Formtastic or SimpleForm, there are extension gems to help you use Judge within your forms without any extra setup. They are essentially basic patches that add the :validate => true option to the FormBuilder input method.

Formtastic

https://github.com/joecorcoran/judge-formtastic

gem "judge-formtastic", "~> x.x.x", :require => "judge/formtastic"
<%= semantic_form_for(@user) do |f| %>
  <%= f.input :name, :validate => true %>
<% end %>

SimpleForm

https://github.com/joecorcoran/judge-simple_form

gem "judge-simple_form", "~> x.x.x", :require => "judge/simple_form"
<%= simple_form_for(@user) do |f| %>
  <%= f.input :name, :validate => true %>
<% end %>

Changelog

1.5.0 Added interface for declaring localised messages within EachValidators, which means we can reliably pass custom messages to the client side. Some internal implementation details have been altered and underscore.js was updated.

1.4.0 judge.store.validate now accepts callback function too.

1.3.0 Validate methods now accept callbacks; judge.utils removed in favour of functions within a closure; some specs were tidied/deleted as appropriate.

1.2.0 Changes to the format of validator functions; improved method of including custom validators; updated dependencies; fixed RegExp flag bug.

1.1.0 Fixed incorrect Enumerable implementation in ValidatorCollection. Lots more internal tidying, including extraction of HTML attribute building.

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.