Skip to content

Instantly share code, notes, and snippets.

@philfreo
Last active October 6, 2015 21:37
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save philfreo/3056724 to your computer and use it in GitHub Desktop.
Save philfreo/3056724 to your computer and use it in GitHub Desktop.
Backbone Model mutators / computed properties
var Model = Backbone.Model.extend({
/**
* Override main attribute getter to handle mutators.
* Looks for an object on the model called 'mutators', keyed by key name,
* with values of either the getter function or an object like:
* { get: function() {}, set: function() {} }
*/
get: function(attr) {
var val,
mutator = this.mutators && this.mutators[attr];
if (mutator) {
mutator = _(mutator).isFunction() ? mutator : mutator.get;
return mutator.apply(this, arguments);
} else {
return this._super.apply(this, arguments);
}
},
/**
* Override main setter function to handle mutators that have a setter.
* See "get" above for details.
* example:
* mutators: {
* full_name: {
* get: function() { return this.get('first_name') + ' ' + this.get('last_name'); },
* triggers: ['first_name', 'last_name'] // trigger change:full_name whenever these 2 fields are changed
* }
* }
*/
set: function(key, value, options) {
options = options || (options = {});
// we could first figure out which keys are mutators which will get
// re-set below in the loop and pull them out from getting set unnecessarily first
// by super. doesn't seem to matter for now though.
this._super.apply(this, arguments);
// since key can be string or object
if (_.isObject(key) || key === null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
// see if we're setting any mutators that have a 'set' function
// the mutator setter functions should pass a {fromMutator: true} option when it does its own setting
if (!options || !options.fromMutator) {
_(attrs).each(function(value, key) {
// get the setter mutator function if it exists for this attribute/key
var mutator = this.mutators && this.mutators[key] && _(this.mutators[key].set).isFunction() && this.mutators[key].set;
if (mutator) {
mutator.call(this, key, value, { silent: true });
}
if (!options || !options.silent) {
// see if we're changing (without silent) and attributes listed in the 'triggers' section of any mutator
// if so that means that the model should trigger a change:mutator_name
if (this.mutators) {
_(this.mutators).each(function(mutator, mutatorName) {
if (mutator.triggers && _(mutator.triggers).indexOf(key) !== -1) {
// we're setting a key that should trigger a change:mutator_name
// HACK. we probably would want to just directly call this.trigger('change:mutator_name')
// but that doesn't work if something binds just on 'change' (e.g., Backbone.ModelBinder)
// so we do a real "set" on this computed attribute
// but then immediately unset it (silently) since we don't really want to keep it around
this.set(mutatorName, this.get(mutatorName));
this.unset(mutatorName, { silent: true });
}
}, this);
}
}
}, this);
}
return this;
},
});
@philfreo
Copy link
Author

Note: this code relies on https://github.com/lukasolson/Backbone-Super but could be easily tweaked to not use it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment