Despite my opinions of ActiveModel::Validations (a post/patch for another day),
I like ActiveModel’s Errors object. Errors’s simplicity, uniquity, and
consistency is a great boon for handling error messages of varying entities.
The future of the web is asynchronous and client side computation is steadily becoming more
sophisticated and well practiced.
errors.js provides an Errors object similar to ActiveModel’s Errors object to
provide a simple, ubiquitous, consistent interface to handle and use error
messages on the client side.
errors.js is on GitHub , here is the source,
( function ( $ , _ ){
$ . widget ( "travisjeffery.errors" , {
_create : function (){
_ . bindAll ( this )
this . container = this . element
this . list = this . element . find ( "ul" )
}
})
$ . extend ( $ . travisjeffery . errors . prototype , {
add : function ( error ){
var errors = this . _buildErrorList ( error )
this . reset ()
this . show ()
this . list . append ( errors )
},
show : function (){
this . container . show ()
},
hide : function (){
this . container . hide ()
},
reset : function (){
this . hide ()
this . list . empty ()
},
handle : function ( xhr , status , error ){
var errors = $ . parseJSON ( xhr . responseText ). errors
this . add ( errors )
},
_buildErrorList : function ( errors ){
var errorArray = $ . makeArray ( errors ),
stringToItem = function ( message ){ return "<li>" + message + "</li>" },
errorList = $ . map ( errorArray , stringToItem ). join ( "\n" )
return errorList
}
})
}( jQuery , _ ));
It’s small and really simple, and only depends on jQuery/UI and Underscorejs. Using it looks
like this,
Controller
class PeopleController < ApplicationController
def show
@person = Person . find params [ :id ]
end
def update
@person = Person . find params [ :id ]
@person . update_attributes params [ :person ]
if @person . valid?
render :json => @person , :status => 200
else
render :json => { :errors => @person . errors . full_messages }, :status => 422
end
end
end
JavaScript Asset
jQuery ( function ( $ ){
App = window . App || ( window . App = {})
App . Errors =
$ ( "div#js-errors" )
. errors ()
. data ( "errors" )
$ ( "a.js-person" )
. on ( "click" , function ( event ){
event . preventDefault ()
var personId = $ ( "[data-person-id]" ). data ( "person-id" ),
personURL = "/people/" + personId
$ . ajax ({
url : personURL ,
// => ["Name can't be blank"]
data : { id : personId , person : { name : "" }},
type : "put" ,
error : App . Errors . handle
})
})
})
View
<%= link_to @person . name , person_path ( @person ),
:method => "put" ,
:remote => true ,
:"data-person-id" => @person . id ,
:class => "js-person"
%>
<div id="js-errors"><ul></ul></div>
API (Usable Without #handle Callback)
var Errors = $ ( "div#errors" ). errors (). data ( "errors" )
Errors . add ( "You shall not pass!" )
Errors . show ()
Errors . hide ()
Errors . reset ()