Setting Backbone attributes in a model in a nested collection - javascript

Pretty new to Backbone JS and I need to know the 'right' way of looping through and setting attributes on models in a collection that is within a model.
My models look like this:
var mediaItem = Backbone.Model.extend({
});
var mediaItems = Backbone.Collection.extend({
model: mediaItem
});
var story = Backbone.Model.extend({
initialize: function () {
this.MediaItems = new mediaItems(this.get('MediaItems'));
this.MediaItems.parent = this;
}
});
What I want to do is loop through the MediaItems in a given story and set the width and height of each. If I do it like this...
storyInstance.MediaItems.each(function (mediaItem) {
mediaItem.set('Width', 200);
mediaItem.set('Height', 100);
});
...then the MediaItem models within the storyInstance.MediaItems property are correctly updated, but the objects within storyInstance.attributes.MediaItems are not. And it's the attributes tree that appears to be used when I subsequently call toJSON() on the Story model.
I can probably amend the above to loop through attributes instead, but I get the feeling I've set up the models wrong or there's a more standard way of doing this?
Thanks.

Probably initialize something other than what you expected.
The below code
var story = Backbone.Model.extend({
initialize: function () {
this.MediaItems = new mediaItems(this.get('MediaItems'));
this.MediaItems.parent = this;
}
});
should have been
var story = Backbone.Model.extend({
initialize: function () {
this.MediaItems = this.get('MediaItems');
this.MediaItems.parent = this;
}
});
and instantiating items should be done with instantiation of story model like
var storyInstance = new story({
MediaItems: new mediaItems()
})
then
story.MediaItems.each(function (mediaItem) {
mediaItem.set('Width', 200);
mediaItem.set('Height', 100);
});
would result updating both

Edit: Did not realize this was from '13. It showed up in questions tagged backbone.js and I did not notice the date/time till now.
Try to check for the instance of Array in the initialize section.
var story = Backbone.Model.extend({
initialize: function () {
if(this.get('MediaItems') instanceof Array){
this.MediaItems = new mediaItems(this.get('MediaItems'));
}
else {
this.MediaItems = this.get('MediaItems');
}
this.MediaItems.parent = this;
}
});

Related

Understanding backboneJS: View.extend()

Until today I thought using backbones functionality like
var PageView = Backbone.View.extend({
template: $.Deferred,
getTemplate: function(tplName) {
var self = this;
ajaxCall(tplName).then(function(hbs) {
self.template.resolve(hbs);
});
}
});
var home = new PageView();
home.getTemplate('home.hbs');
is similar to a pure JS OOP approach like
var PageView = function() {
this.template = $.Deferred();
}
PageView.prototype.getTemplate = function(tplName) {
var self = this;
ajaxCall(tplName).then(function(hbs) {
self.template.resolve(hbs);
});
}
var home = new PageView();
home.getTemplate('home.hbs');
However, I'm currently trying to rebuild a web-app with backbone and it seems like solving that deferred with
home.getTemplate('home.hbs');
will resolve this for all instances of the Backbone view PageView while in pure JS this would only resolve for that one particular instance home.
When I do a second instance:
var page1 = new PageView();
the template property is already resolved with the home.hbs template in backbone. Which is very weird to me.
So my guess is that I'm fundamentally misunderstanding how backbone views work.
Can someone enlighten me?
The difference is in the first snippet template property of all the instances refer to the same object and in the second snippet the property is set using a different/new instance of Deferred when the constructor function is executed. If you set the template property within the initialize function of the constructor you will get the same result as the second snippet:
var PageView = Backbone.View.extend({
// template: $.Deferred,
initialize: function() {
this.template = $.Deferred;
},
getTemplate: function(tplName) {
var self = this;
ajaxCall(tplName).then(function(hbs) {
self.template.resolve(hbs);
});
}
});
In the second example if you set the template to the prototype of the constructor then it will behave like the backbone constructor:
var PageView = function() {
// this.template = $.Deferred();
}
PageView.prototype.template = $.Deferred();
var instance1 = new PageView();
var instance2 = new PageView();
console.log( instance1.template === instance2.template ); // true

Backbone view that render child views of the same type causes endless loop

I have a category model that has child category models (That works fine) via this code:
var ImageSetCategory = Backbone.Model.extend({
childrenCategories : new Array(),
initialize: function () {
var self = this;
if (this.has('childrenCategories')) {
$.each(this.get('childrenCategories'), function () {
var category = new ImageSetCategory(this);
self.childrenCategories.push(category);
});
}
}
});
I also have a view that uses this model and renders all the children categories. (basicly, I'm attempting to make a tree view) It loops through the child categories using jquery, instantiates a new version of its self with each child category as the model, and renders it. But I'm hitting an endless loop that constantly is trying to process the same model.
var ImageSetCategoryView = Backbone.View.extend({
tagName: 'li',
className: 'nested-category',
template: Handlebars.templates.imageSetCategoryView,
render: function() {
var self = this;
var templateHtml = this.template(this.model.toJSON());
self.$el.html(templateHtml);
// *****************************
// ENDLESS LOOP
// this is always the same model from the array
// *****************************
$.each(self.model.childrenCategories, function () {
var categoryView = new ImageSetCategoryView({ model: this });
self.$el.children('ul').append(categoryView.render().el);
});
return this;
},
});
Why is this causing an endless loop? Am I'm not following best practices? My background is C# so I'm trying to accomplish this in an OOP way.
The reason is that all instances of ImageSetCategory share the same childrenCategories array. This way in ImageSetCategory.initialize function you create circular references (ImageSetCategory.childrenCategories points to the array and ImageSetCategory.childrenCategories[0] points to ImageSetCategory itself). This makes $.each in ImageSetCategoryView.render iterate over the same model. To avoid it you should initialize array inside of ImageSetCategory.initialize function:
var ImageSetCategory = Backbone.Model.extend({
initialize: function () {
var self = this;
this.childrenCategories = [];
if (this.has('childrenCategories')) {
$.each(this.get('childrenCategories'), function () {
var category = new ImageSetCategory(this);
self.childrenCategories.push(category);
});
}
}
});
To learn more about why this happens read about prototypes in JavaScript and how they are used to implement object-oriented paradigm.

How to get a filtered collection

Lets suppose I have a big collection and I want to use a subset of this big collection for different view.
I tried the following code but it does not work because the filtered collection actually is a new one and it does not refer to the BigCollection instance.
My question is:
how can I get a collection which is a subset of BigCollection instance?
Here is my code. Please see the comments for more info:
// bigCollection.js
var BigCollection = Backbone.Collection.extend({
storageName: 'myCollectionStorage',
// some code
});
// firstView.js
var firstView = Marionette.CompositeView.extend({
initialize: function(){
var filtered = bigCollection.where({type: 'todo'});
this.collection = new Backbone.Collection(filtered);
// the issue is about the fact
// this.collection does not refer to bigCollection
// but it is a new one so when I save the data
// it does not save on localStorage.myCollectionStorage
}
});
Use BigCollection to make filtered collection, like this :
// firstView.js
var firstView = Marionette.CompositeView.extend({
initialize: function(){
var filtered = bigCollection.where({type: 'todo'});
this.collection = new BigCollection(filtered);
// now, it will save on localStorage.myCollectionStorage
}
});
You could just save your original models in a variable inside your collection so you can restore them after you de-apply the filter, like this:
// bigCollection.js
var BigCollection = Backbone.Collection.extend({
storageName: 'myCollectionStorage',
// some code
});
// firstView.js
var firstView = Marionette.CompositeView.extend({
initialize: function(){
bigCollection.original_models = bigCollection.models;
bigCollection.models = bigCollection.where({type: 'todo'});
}
});
And then you can restore them when you toggle the filter:
bigCollection.models = bigCollection.original_models;

Fetch data having a specific id defined in the view instance

I need to fetch data having a specific id
and which id is defined in the view instance.
Here the example, see the comments in MyModel definition:
// my view instance
var myView = new MyView({
model: {id: 12321}
});
MyView = Backbone.View.extends({
initialize: function()
{
myModel.fetch();
}
});
MyModel = Backbone.Model.extends({
url: function url ()
{
// how to get the id passed to view instance?
return "http:..../id/" + this.id;
}
});
Model should not has any knowledge of the existence of the View, so the View should be the one that sais to the Model which id to fetch:
MyView = Backbone.View.extends({
initialize: function()
{
myModel.id = this.model.id;
myModel.fetch();
}
});
(I've used your example code as template for my example, but I have to say I feel several weird things on it, I suppose is just a matter of taste)
Update: My very personal taste opinions
Is very difficult to do this but as you requested I'll share with you my very personal code review of your example code. Take this as it is: a very humble opinion.
this.model confused
I would not use attribute names that can create confussion:
var myView = new MyView({
model: {id: 12321}
});
Into this instance this.model is making reference to a raw Hash but in a Backbone context this is against the intuitive feeling that this is gonna be a Backbone.Model.
I rather change it for something like this:
var MyView = Backbone.View.extend({
initialize: function( opts ){
this.model_id = opts.model_id;
}
})
var myView = new MyView({ model_id: 12321 });
I think this naming is more intuitive.
close variables scopes
This code can only works if myModel is in an scope bigger that it should be:
MyView = Backbone.View.extends({
initialize: function()
{
myModel.fetch();
}
});
I rather prefer using more encapsulated scopes, even if myModel has been declared in the out-side context of your View the View should use a variable of its private context. For example
var MyView = Backbone.View.extends({
initialize: function( opts ) {
this.model = opts.model;
this.model.fetch();
}
});
var myView = new MyView({ model: myModel });
Check the detail that I have also added var in front of MyView because if not MyView will be a window global variable.
use the Backbone urlRoot
In your example, this ...
MyModel = Backbone.Model.extends({
url: function url ()
{
// how to get the id passed to view instance?
return "http:..../id/" + this.id;
}
});
... can be summarized as this:
MyModel = Backbone.Model.extends({
urlRoot: "http:..../id"
});

Accessing parent class in Backbone

I need to call the initialize method of the parent class, from inside the inherited MyModel-class, instead of completely overwriting it as I am doing today.
How could I do this?
Here's what my code looks right now:
BaseModel = Backbone.Model.extend({
initialize: function(attributes, options) {
// Do parent stuff stuff
}
});
MyModel = BaseModel.extend({
initialize: function() {
// Invoke BaseModel.initialize();
// Continue doing specific stuff for this child-class.
},
});
Try
MyModel = BaseModel.extend({
initialize: function() {
BaseModel.prototype.initialize.apply(this, arguments);
// Continue doing specific stuff for this child-class.
},
});
MyModel = BaseModel.extend({
initialize: function() {
MyModel.__super__.initialize.apply(this, arguments);
// Continue doing specific stuff for this child-class.
},
});
This worked for me, when I was trying to inherit among my models:
MyModel.prototype.initialize.call(this, options);
Referenced from http://documentcloud.github.com/backbone/#Model-extend
Thanks.
I think it'd be
MyModel = BaseModel.extend({
initialize: function() {
this.constructor.__super__.initialize.call(this);
// Continue doing specific stuff for this child-class.
},
});
this seems to be almost a duplicate of Super in Backbone, so you want something like this:
Backbone.Model.prototype.initialize.call(this);
Similar to #wheresrhys, but I would use apply instead of call in case BaseModel.initialize is expecting arguments. I try to avoid processing the attributes map that can be passed to a Backbone Model upon initialization, but if the BaseModel were actually a View or a Collection then I might want to set options.
var MyModel = BaseModel.extend({
initialize: function() {
this.constructor.__super__.initialize.apply(this, arguments);
// Continue doing specific stuff for this child-class.
},
});
here's a multi generation callSuper method, just add it to your extending class.
callSuper: function (methodName) {
var previousSuperPrototype, fn, ret;
if (this.currentSuperPrototype) {
previousSuperPrototype = this.currentSuperPrototype;
// Up we go
this.currentSuperPrototype = this.currentSuperPrototype.constructor.__super__;
} else {
// First level, just to to the parent
this.currentSuperPrototype = this.constructor.__super__;
previousSuperPrototype = null;
}
fn = this.currentSuperPrototype[methodName];
ret = (arguments.length > 1) ? fn.apply(this, Array.prototype.slice.call(arguments, 1)) : fn.call(this);
this.currentSuperPrototype = previousSuperPrototype;
return ret;
}
You might consider rewriting your code using functional inheritance.
var BackBone=function(){
var that={};
that.m1=function(){
};
return that;
};
var MyModel=function(){
var that=BackBone();
var original_m1=that.m1;
//overriding of m1
that.m1=function(){
//call original m1
original_m1();
//custom code for m1
};
};

Categories

Resources