I'm just trying to figure out a probably simple question.
Should views set model data directly or only call model methods that change their own data?
like everything else in software development, "it depends".
if you're using form inputs in your views and you just need to get the data from those inputs in to the models, set the data directly. You can do this any number of ways, including "change" events from the input fields, like this:
MyView = Backbone.View.extend({
events: {
"change #name", "setName"
},
setName: function(e){
var val = $(e.currentTarget).val();
this.model.set({name: val});
}
});
On the other hand, if you're kicking off business logic and other code that happens to set the data in the model (but really only does this as part of the business logic), then you should call a method on the model.
A "state machine" would be a good example of when you would do this. Or, in an image gallery that I wrote, I had some logic around the selection of an image for display. If an image is already selected, don't select it again. I put this logic in a method on my image model:
Image = Backbone.Model.extend({
select: function(){
if (!this.get("selected")){
this.set({selected: true});
}
}
});
As illustrated here, I like to run by the simple rule that if I have zero logic around the call to set, then I set it directly from wherever I am. If there is any logic related to the model, around the set, then I put it in the model.
Either way, when you do want to set data, you should use the set method. Bypassing this and setting the model's attributes directly through model.attributes will prevent a lot of Backbone's code from firing and potentially cause problems for you.
That depends on your programming style. I always set them directly. If the Law of Demeter sounds good to you and you're into object orientation (and possibly with a Java/Microsoft -stack background), then the style would be to create getter/setter methods.
If you on the other hand is in the "Size is the Enemy" camp (I also recommend Jeff Atwood's comments) the you certainly should set model data directly (as I hinted before, I'm in this camp myself).
That being said, Backbone.js models already have getter and setter methods .get and .set. You should not manipulate the .attributes member directly. I'm not as sure you shouldn't read from it, I tend to do that now and then and haven't run problems because of that yet.
Related
I am practising Backbone js. I have a collection consisting of 10 models. In just one model, I want to change 2 of the attributes. I am using the device local storage. I have tried the following code (this is just part of the larger script):
this.collection.forEach(function(user) {
if (user.get('subject') === 'Physics') {
user.set({'title': 'hdrodynamics'});
user.save();
console.log(JSON.stringify(user));
return;
}
});
I consider this method inefficient. What happens as the collection length increases. I believe there is a better way than this.
You can use collection.findWhere, which:
directly returns only the first model in the collection that matches the passed attributes.
Which can reduce the number of iterations run.
You can also use model.save with attributes to avoid doing the extra 'set'.
// Stops when matching model is found
var physicsModel = this.collection.findWhere({ subject: 'Physics' });
// might want to do some null-checking...
// Performs a set prior to POSTing, firing correct 'change' events
physicsModel.save({ title: 'hydrodynamics' });
Note: A more efficient way to do this would be to avoid the lookup in the collection. For example, if this model should 'save' by some action from the user, you could leverage the model's view to gain access to the correct model (i.e. this.model.save()). This of course is implementation dependent.
To all the Ti Alloy experts out there:
There are a couple of questions out there already dealing with this same problem, none of them marked with an accepted answer and certainly none answered to my personal satisfaction. Let me explain why I still believe the problem is relevant, existing answers and comments notwithstanding:
In Alloy, you are allowed to bind repeater-type elements to models in a Backbone collection; this is straightforward enough and works beautifully.
However, in a typical master-detail view arrangement you may wish to bind a view to a specific model instance, which you have passed into the controller for this purpose.
As far as I've been able to make out the standard Alloy technique for this seems to be that you declare a model instance in the Alloy markup, eg. <Model id="myModelInstance" src="myBackboneModel" instance="true"> and then proceed to work with this model in the controller code by way of $.myModelInstance... references.
The problem arises when you have passed your own model instance into the controller, as you are unable to "point" the declared view model instance to your existing model instance. You must instead "copy" your model attributes to the view's model instance by doing
$.myModelInstance.set(myPassedInModel.attributes);
This works well enough in practice, but requires that you marshall changes to the view model back to the "real" model that was passed in, and is also inconvenient if the view is to be responsive to changes in the underlying collection.
Am I missing something here, or is there perhaps a better solution I'm not aware of?
Any feedback or insights will be much appreciated.
Cheers,
Francois.
I see that this question is now about a year and half old, but for posterity I will offer my preferred solution: Instead of using an instance, use a singleton for the detail view and set the model data globally. A simple example:
// alloy.js
// initialize the model on app start
var detailModel = Alloy.Models.instance('myModel');
// repeater.xml
<Button title='Show Detail' onClick='showDetail' />
// repeater.js
function showDetail() {
Alloy.Models.myModel.clear();
Alloy.Models.myModel.set($.model.toJSON());
navigateToDetailView();
}
// detail.xml
// access global singleton in your detail view
<Alloy>
<Model src='myModel' />
<Window title='{myModel.title}' >
<Label text='{myModel.description}' />
</Window>
</Alloy>
Note that to do this, your repeater must have its own controller (use Require element) as best I can tell. I don't think this is significantly different than the solution offered in the question, but I think it is simpler.
As i follow some tuts for angular and ember.js I came across the term Two way data binding. Where data displayed on UI are bind with database and any changes to one is quickly propagated to the other. When I started learning meteor.js i came across term "Reactivity" which for me makes same sense as two way data binding. Can you please tell me fundamental difference between these two terms?
Reactivity is in fact more general than data binding. With reactivity you can implement data binding, in a really simple way, e.g.
var myAwesomeData = "some data";
var myAwseomeDependency = new Tracker.Dependency();
var getData = function () {
myAwesomeDependency.depend();
return myAwesomeData;
};
var setData = function(value) {
if (value !== myAwesomeData) {
myAwesomeData = value;
myAwesomeDependency.changed();
}
}
Now, every time the getData routine is called within a computation, so basically within Tracker.autorun environment, it gets recomputed. By default the meteor's collection API is implemented to be reactive, so every time fetch some data from you'r database you can be sure that it gets updated as soon as the data changes.
Also note, that you can use the above reactivity pattern without any database or values, so for example you can trigger and monitor events, states and so on.
This Wikipedia Article will help you:
http://en.wikipedia.org/wiki/Reactive_programming
It basically says, that changes of data in specific dataLayers are automatically propagated. This paradigm seems to be the generic term and each framework with databinding / two way databinding is building on it and gives their technique a different name.
My understanding is that two-way data binding is a form of reactive programming. Reactive simply means that a flow of changes in your data drives action. Whether the change comes from both the DOM and the data in your application or just one of those, does not really matter.
From my understanding the attributes of a Backbone.js model are supposed to be declared as somewhat private member variables by saying
this.set({ attributeName: attributeValue })
// accessing the value
this.get('attributeName');
But when I am writing functions whitin the actual model it seems much simpler to say like this:
this.attributeName = attributeValue;
// accessing the value
this.attributeName;
Also I would assume that the latter version would be faster to process since it doesn't go through backbone.js's event management.
So I was wondering how you pros do with attributes that are primarily used internally in the model. These are the attributes that one would actually want to be a bit shielded from the outside so having them exposed like in the latter example maybe isn't right still. When I have been looking at examples for the backbone.js view which doesn't have get and set methods it seems fine to do like in the second example. So is there any nice rule of thumb when to use get/set(attribute) or this.attribute when coding within the model? Or maybe an example of a model that makes this clearer?
When to use model.get(property) and model.set(...)
You should use get and set to access the model's data. This means any attributes that are part of the model's serialized representation that is retrieved using fetch and persisted using save.
When to use model.attributes.property
Never.
You should always use get, and especially set, instead of accessing the model.attributes object directly, although I've seen conflicting opinions about this. I believe there is a contract between a model and it's consumers, which guarantees that the consumer can be notified of any changes to the model's data using the change event. If you modify the internal attributes object directly, events are not sent and this contract is broken. Backbone events are very fast, especially if you don't have any listeners attached to them, and it's not a point that benefits from over-optimization on your part.
Although accessing the attributes directly instead of get is quite harmless on it's own, it should be avoided so the attributes object can be considered totally, completely private.
If you absolutely need to prevent some change triggering events, you can use the silent:true option: model.set({key:val}, {silent:true}). This does break the aforementioned contract, and even Backbone's own documentation gives the following caveat:
Note that this is rarely, perhaps even never, a good idea. Passing through a specific flag in the options for your event callback to look at, and choose to ignore, will usually work out better.
When to use model.property
Any properties which are not data, i.e. temporary state variables, calculated properties etc. can be attached directly to the model entity. These properties should be considered temporary and transitive: they can be recreated upon model initialization or during its lifetime, but they should not be persisted, whether public or private. A typical naming convention is to prefix private properties with the _ character as follows:
this._privateProperty = 'foo';
this.publicProperty = 'bar';
Never is an incomplete answer.
Sometimes you want access to the collection of model attributes - whatever those attributes might be. Consider a utility method to perform calcs on attributes, format them for output, etc.
A convenient way to do this is to access model.attributes
Consider one alternative, below:
var attributesNames = ['foo', 'bar', 'baz'];
var attributes = _(attributesNames ).map(function(attr) { return model.get(attr); });
callSomeUtilityMethod(attributes);
Two problems:
We've introduced coupling in the "attributeNames" collection. What if that list changes?
We've lost the association of name/value. We could rewrite the map above, but it becomes more work.
In this scenario, it's much more convenient to do something like this:
callSomeUtilityMethod(model.attributes);
I read in the backbone documentation that calling collection.reset() clears the collection. I want to know if it removes the models as well or do they continue to live in memory?
If they're not removed is there an easier way to remove all the models in a collection without iterating through the models and calling model.remove()?
You could listen for the reset event from the model and do your cleanup and this.destroy() in response. That's what the event hooks are for. See http://backbonejs.org/#Events-catalog
Note: You absolutely should not change or override any method or property prefixed by an underscore, such as _removeReference. The underscores mean that it is intended as an internal method or property, and that the internal implementations may change (their API's are considered unstable). Upgrading Backbone could break any code that relies on underscore-prefixed methods, even if the release is advertised as a backwards-compatible change.
I know your question says "without iterating", but it really is the most reliable way of handling this. Consider a case where a model has been moved from one collection to another, but it's still listening on the first collection's reset event (because a programmer six months later didn't notice the connection).
Now when the first collection gets reset, the moved model gets destroyed. Oops!
Iterating over the collection probably is the best way to handle this if you don't have an endpoint on your API that will delete all objects in a collection in batch on the API server (which is often how this is handled).
Luckily, that iteration is pretty easy:
destroyAll: function () {
var promises = [];
while(this.models.length > 0) {
promises.push( this.models[0].destroy() );
}
// handle errors communicating with the server
$.when(promises).fail(function (response) {
this.trigger('syncError',
response);
}.bind(this));
}
What you are looking for is, probably, for the models to be garbage-collected. That is, that nobody has a reference to these models anymore, after they are removed from the collection.
Backbone does its part of removing the references that it set on the models, when they are removed from the collection. However, you have to do your own cleanup if your code has references to those models. Most of the time, this happens if those models are registered as event listeners, like in this example: http://jsfiddle.net/dira/4uxp4/2/
Taking a look at the implementation of reset, you could change _removeReference to call a cleanup function on the model as well. And in the model, remove the model from all the listeners/all the other objects that keep a reference to it.