My solution was to replace Backbone.Model.prototype.set
with a preprocessor proxy:
/**
* Intercept calls to Backbone.Model.set and preprocess attribute values.
*
* If the model has a <code>preprocess</code> property, that property will be
* used for mapping attribute names to preprocessor functions. This is useful
* for automatically converting strings to numbers, for instance.
*
* @param Backbone
* the global Backbone object.
*/
(function(Backbone) {
var originalSet = Backbone.Model.prototype.set;
_.extend(Backbone.Model.prototype, {
set: function(key, val, options) {
if(!this.preprocess) {
return originalSet.apply(this, arguments);
}
// If-else copied from Backbone source
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
for(attr in this.preprocess) {
if(_.has(attrs, attr)) {
attrs[attr] = this.preprocess[attr](attrs[attr]);
}
}
return originalSet.call(this, attrs, options);
},
});
})(Backbone);
After this, models with a preprocess
property will use it to map attribute names to preprocessor functions. For instance, preprocess: { age: parseInt }
means that whenever the age
attribute is set, the value will be passed through parseInt
before actually setting it. Attributes with no corresponding preprocess
entry will not be affected.
Example usage:
var Thing = Backbone.Model.extend({
preprocess: {
mass: parseInt,
created: function(s) { return new Date(s); },
},
});
var t = new Thing({
label: '42',
mass: '42',
created: '1971-02-03T12:13:14+02:00',
});
console.log(t.get('label')+3); // 423
console.log(t.get('mass')+3); // 45
console.log(t.get('created').toLocaleString('ja-JP', { weekday: 'short' })); // 水
Pros
- The functionality is available in all models without needing to duplicate code
- No need to send
{ validate: true }
in every call to set
- No need to duplicate preprocessing in
validate
, since this happens before validate
is called (this might also be a con, se below)
Cons
- Some duplication of Backbone code
- Might break validation since preprocessing happens before
validate
is called. JavaScript parsing methods usually return invalid values instead of throwing exceptions, though (i.e. parseInt('foo')
returns NaN
), so you should be able to detect that instead.