Four Backbone.js Model questions
I'm using a Backbone.js to keep track of the state in a visualization application.
The model has attributes such as:
indicatorX : "income"
indicatorY : "emissions"
indicatorXScale : "lin"
indicatorYScale : "log"
year : 1980
layout : {leftPanel : {[...]}, rightPanel : {[...]}}
1. What is a good way of handling "dependent attributes" in a Backbone.js model?
For example, when changing the indicatorX attribute, I'd also li开发者_Go百科ke the model to update the indicatorXScale attribute.2. How can I handle "lingering" model attributes? Example:
The model contains this:
indicatorX : "income"
indicatorXScale : "log"
If only indicatorX is set on the model, the scale should be set to the default:
model.set( {indicatorX : "emissions"} )
if("indicatorX" in changedAttrs){
indicatorXScale = dataSource[indicatorX].defaultScale
}
What if however the user wants to override the default scale which is "lin" in the case of the "emissions" indicator?
model.set( {indicatorX : "emissions", indicatorXScale : log} )
As the model attribute indicatorXScale already is set to "log", the changed attribute is not recorded. How can I then make sure that the defaultScale is not loaded in this case - but instead the one passed to the model?
3. Is it a good idea to let the model use an extra attribute "action" to describe changes in the model?
In this way controllers can listen for one attribute instead of specifying handlers for combinations of attributes. These are the alternatives:Alt 1. Controller has handlers for specific attributes:
this.model.bind("change:year", this.render);
this.model.bind("change:layout", this.updateLayout);
Alt 2. Controller has handler for model change and render() figures out what to do:
this.model.bind("change", this.render);
render() {
var changedAttributes = this.model.changedAttributes
if (a,b && c in changedAttributes) x()
if (a,d in changedAttributes) y()
}
Alt 3. Let model describe what a combination of attribute changes signify:
this.model.bind("change:action", this.render);
render() {
var changedAttributes = this.model.changedAttributes
var action = this.model.get("action")
if (action == gui_changeIndicator) x()
if (action == gui_opacity) y()
}
4. Are there any pitfalls to watch out for when using objects as attributes in a Backbone.js model?
Is it for example expensive to perform isEqual on the layout state that I try to keep in my model? Also, when setting the model, objects are passed by reference, so it better be a new object for the comparison to work?1. What is a good way of handling "dependent attributes" in a Backbone.js model? For example, when changing the indicatorX attribute, I'd also like the model to update the indicatorXScale attribute.
IMHO, extend the model and bind into the change events. For example:
MyModel = Backbone.Model.extend({
initialize: function() {
this.bind('change:width', this.updateArea);
this.bind('change:height', this.updateArea);
this.updateArea();
},
updateArea: function () {
this.area = this.get('width') * this.get('height');
}
});
var model = new MyModel({height: 10, width: 10});
console.log(model.area); //100
model.set({width: 15});
console.log(model.area); //150
This is pretty basic, but change events are called per key and as a whole 'change'.. so you can bind into certain changes and update as necessary. If it's a large model with lots of keys that are updated intermittently this is definitely the way to go. If it's just those two keys.. well.. you could probably just bind to the regular ol' change event once.
2. How can I handle "lingering" model attributes?
Override the set method and add in some of your own code. Example:
MyModel = Backbone.Model.extend({
constructor: function (obj) {
this.touched = {}; //Set before the prototype constructor for anything being set
Backbone.Model.prototype.constructor.call(this, obj);
//set after for only things that the program has updated.
return this;
},
set: function(attributes, options) {
if(attributes.keyIWantToListenFor !== undefined && !this.touched.keyIWantToListenFor) {
console.log("keyIWantToListenFor was set, let's fire off the right methods");
this.touched.keyIWantToListenFor = true;
}
Backbone.Model.prototype.set.call(this, attributes, options);
return this;
}
});
var model = new MyModel({height: 10, width: 10});
model.set({keyIWantToListenFor: 15});
This keeps absolute "has the key been set at all" on the model. It may not be quite specific enough for your needs, but it does work.. so feel free to use and hack away at it.
3. Is it a good idea to let the model use an extra attribute "action" to describe changes in the model?
The way that the Backbone folks have it set up is that, as you already know, change:key is specifically for the change event on a certain key. By relying on a change:action you're kind of adding 'gotcha!'s to your code. I don't see how the other two methods are any better than the first, especially considering now you have logic thrown into an event listener to determine what to fire off.. instead of just attaching that code directly to the appropriate listeners. Given a choice, I'd stick with the first one - it is a clear "This key has updated, so we're going to do X". Not a "something has updated so let's go figure out what it is!" and potentially have to go through a dozen if statements or switches.
4. Are there any pitfalls to watch out for when using objects as attributes in a Backbone.js model?
Well, isEqual performs a deep comparison.. so you're running the risk of doing all of that comparison code plus the risk of recursion. So, yes, that could certainly be a pitfall if you're doing it a number of times.
The object by reference is certainly an issue - I've got a nice little hole in the wall where I've put my head through a few times wondering why something changed in a completely unrelated.. oh wait.. To remedy this a bit, you could override the get method to, in cases where it is returning an object, return something like $.extend(true, {}, this.get(key));
Also, you don't really know what exactly changed in the object based on plain Backbone. So, if you're doing lots of 'stuff' on a change (rebuilding a view, etc), you're potentially going to run into performance issues even if all you did was add another attribute to that object and it isn't used for any of said changes. (i.e. set({layout: layoutObj}) vs set({layoutPageTitle: 'blah'}) which may only update the title.. instead of causing the entire view to reload).
Otherwise, at least in the app that I'm working on, we've had no real issues with objects in backbone. They sync fairly well, and it's certainly better than .get('layout.leftPanel[0]') and having some magical translation to make that work. Just be careful of the reference part.
Hope that helps at least a little!
精彩评论