CMS.Views.ValidatingView = Backbone.View.extend({ // Intended as an abstract class which catches validation errors on the model and // decorates the fields. Needs wiring per class, but this initialization shows how // either have your init call this one or copy the contents initialize : function() { this.model.on('error', this.handleValidationError, this); this.selectorToField = _.invert(this.fieldToSelectorMap); }, errorTemplate : _.template('<%= message %>'), events : { "change input" : "clearValidationErrors", "change textarea" : "clearValidationErrors" }, fieldToSelectorMap : { // Your subclass must populate this w/ all of the model keys and dom selectors // which may be the subjects of validation errors }, _cacheValidationErrors : [], handleValidationError : function(model, error) { // error triggered either by validation or server error // error is object w/ fields and error strings for (var field in error) { var ele = this.$el.find('#' + this.fieldToSelectorMap[field]); if (ele.length === 0) { // check if it might a server error: note a typo in the field name // or failure to put in a map may cause this to muffle validation errors if (_.has(error, 'error') && _.has(error, 'responseText')) { CMS.ServerError(model, error); return; } else continue; } this._cacheValidationErrors.push(ele); if ($(ele).is('div')) { // put error on the contained inputs $(ele).find('input, textarea').addClass('error'); } else $(ele).addClass('error'); $(ele).parent().append(this.errorTemplate({message : error[field]})); } }, clearValidationErrors : function() { // error is object w/ fields and error strings while (this._cacheValidationErrors.length > 0) { var ele = this._cacheValidationErrors.pop(); if ($(ele).is('div')) { // put error on the contained inputs $(ele).find('input, textarea').removeClass('error'); } else $(ele).removeClass('error'); $(ele).nextAll('.message-error').remove(); } }, saveIfChanged : function(event) { // returns true if the value changed and was thus sent to server var field = this.selectorToField[event.currentTarget.id]; var currentVal = this.model.get(field); var newVal = $(event.currentTarget).val(); this.clearValidationErrors(); // curr = new if user reverts manually if (currentVal != newVal) { this.model.save(field, newVal); return true; } else return false; }, // these should perhaps go into a superclass but lack of event hash inheritance demotivates me inputFocus : function(event) { $("label[for='" + event.currentTarget.id + "']").addClass("is-focused"); }, inputUnfocus : function(event) { $("label[for='" + event.currentTarget.id + "']").removeClass("is-focused"); } });