Tonight, I continue my exploration of validations in Backbone.js.
Last night, I came up with a decent solution for a jQuery UI dialog. When creating a new appointment in my Backbone calendar, callbacks on the
create()
grab validation errors to provide useful error messages for the user:First up tonight, I do the same for editing existing appointments. I have a separate dialog for editing (it just seemed easier that way), so I add an error
<div>
to that dialog (using jade templating):#edit-dialog(title="Edit calendar appointment") h2.startDate div.errors(style="background:yellow; display: none") p title p input.title(type="text", name="title") p description p input.description(type="text", name="description")Since Backbone will handle the success and failure of submitting the form, I remove the OK button's click handler:
$(function() {
$('#edit-dialog').dialog({
autoOpen: false,
modal: true,
buttons: [
{ text: "OK",
class: "ok" },
{ text: "Cancel",
click: function() { $(this).dialog("close"); } }
]
});
});
The jQuery dialog still retains control of the Cancel button.Lastly, I add the optional
options
argument to the save()
call in the Backbone view's update handler: var AppointmentEdit = new (Backbone.View.extend({
// ...
events : {
'click .ok': 'update'
},
update: function() {
var attributes = {
title: $('.title', '#edit-dialog').val(),
description: $('.description', '#edit-dialog').val()
}
var options = {
success: function() { $('#edit-dialog').dialog("close"); },
error: function(model, errors) {
$('.errors', '#edit-dialog').html(errors.join("<br>")).show();
}
};
this.model.save(attributes, options);
}
}));
The options
hash contains success and error callbacks for save()
. If save()
is successful, then the success()
callback closes the edit jQuery dialog. If save()
fails, then the error
callback is responsible for placing the error messages inside the <div class="errors">
tag that I added to my jQuery UI dialog.With that, I have validations working in my edit-appointment views as well:
That is all well and good, but I think that I might like to have some Rails-like validation reporting—full messages plus highlighting individual fields that have failed validations. For that, I need an
Errors
class that very roughly approximates what the Rails' Error class does: var Errors = function(options) {
options || (options = {});
this.on = options;
};
_.extend(Errors.prototype, {
isEmpty: function() {
return (_.size(this.on) === 0);
},
add: function(attribute, error) {
this.on[attribute] = error;
},
each: function(callback) {
_.each(this.on, callback(attribute, error));
},
full_messages: function() {
return _.map(this.on, function(error, attribute) {
return attribute + " " + error;
});
}
});
Now I can switch my model over to use this error object: var Models = (function() {
var Appointment = Backbone.Model.extend({
// ...
validate: function(attributes) {
var errors = new Errors();
if (!(/\\S/.test(attributes.title)))
errors.add("title", "cannot be blank.");
if (!/\\S/.test(attributes.description))
errors.add("description", "cannot be blank.");
if (!errors.isEmpty())
return errors;
},
// ...
});
And, lastly, I convert my appointment edit View to pull the error messages out of the full_messages method:
var AppointmentEdit = new (Backbone.View.extend({
// ....
events : {
'click .ok': 'update'
},
update: function() {
var attributes = {
title: $('.title', '#edit-dialog').val(),
description: $('.description', '#edit-dialog').val()
}
var options = {
success: function() { $('#edit-dialog').dialog("close"); },
error: function(model, errors) {
var full_messages = errors.full_messages();
$('.errors', '#edit-dialog').
html(full_messages.join("<br/>")).
show();
}
};
this.model.save(attributes, options);
}
}));
With that, I have my dialog working just as before, but with the rails like error handling in place. I call it a night here and will pick back up tomorrow decorating the individual fields with highlights when they fail validations.Day #154
I couldnt get your example to work until i did this:
ReplyDeleteeach: function(callback) {
_.each(this.on, callback(attribute, error));
},
TO
each: function(callback) {
_.each(this.on, function(error, attribute) {
callback(error, attribute);
});
}