MultiLevel Inheritance in Backbone view - javascript

Hi I am trying to achieve a multilevel inheritance in Backbone.view but getting "extend" is not a fuction,
example :
var Parent = Backbone.View.extend({
})
var Child = Parent.extend({
initialize:function(attributes) {
}
})
var SubChild = Child.extend({ //Getting exception here
initialize:function(attributes) {
}
})
Kindly guide me to resolve the issue.
Is it possible to do MultiLevel inheritance in BackBone.view?

Try following approach:
var ChildView = Backbone.View.extend({
initialize: function(attributes) {
// ...
}
});
var ParentView = Backbone.View.extend({
child: new ChildView(),
// ...
initialize: function(attributes) {
// ...
}
});
You can play with that and move instantiation of ChildView to the ParentView initialize (constructor) method if needed or leave it as it is.

Related

Backbone view inheritance

I know someone will mark this as duplicate but I went through almost all posts related to this subject and that's not what I am looking for. So here is the thing bugling my mind since last week.
I have been tasked to create an atomic design of views. There will be a core base view, then another view will extend it and so on. element->panel->window, element->panel->popup etc. With Backbone.View.extend I can simply do it like
var BaseView = Backbone.View.extend({
initialize : function(options) {
this.$el.attr('cid', this.cid);
this.base = options.base || arguments[0]['base'];
this.parent = options.parent || arguments[0]['parent'];
this.children = [];
if(typeof this.template !== 'undefined') {
if (typeof this.template=='function') {
// do nothing. template is already a underscore template and will be parsed when first call;
}
else if (typeof this.template=='string' && this.template.substr(0,1)=='#') {
if ($(this.template).length >0 ) {
this.template = _.template($(this.template).html());
}
else {
console.warn('Template element ' + this.template + 'could not be located in DOM.');
}
}
else {
this.template = _.template(this.template);
}
}
else {
this.template = _.template('<span></span>');
}
if (typeof this.parent!=='undefined' && this.parent ) {
this.parent.add.apply(this.parent, [this]);
}
},
add: function(child){
this.children.push(child);
},
getChildren : function(){
return this.children;
},
clean: function(){
this.$el.empty();
},
close: function () {
BaseView.prototype.clear.apply(this, [true]);
this.undelegateEvents();
this.unbind();
this.stopListening();
this.remove();
},
clear: function(){
if (this.children.length > 0) {
empty = empty || false;
_.each(this.getChildren(), function (child, index) {
Baseview.prototype.close.apply(child);
});
this.children = [];
if (empty) {
this.$el.empty();
}
}
return this;
}
})
then if I try use it as
var Layout = new BaseView.extend({
el: '#someElement',
template : '#sometemplate',
initialize : function(){
this.childView = new ChildView({parent: this, base: this, collection: someCollection});
return this;
},
render: function(){
this.clean().$el.append(this.template({}));
this.$('.content').append(this.childView.render().$el);
return this;
},
});
var ChildView = BaseView.extend({
tagName : 'div',
template : '#childTemplate',
initialize : function(){
return this;
},
render: function(){
var self = this;
this.clean().$el.append(this.template({}));
this.$list = this.$('ul');
_.each( this.collection.models, function(model){
var grandChildView = new GrandChildView({parent: self, base: self.base, model: model});
self.$list.append(grandChildView.render().$el);
})
return this;
}
});
var GrandChildView = BaseView.extend({
tagName : 'li',
template : '#grandChildTemplate',
initialize : function(){
return this;
},
render: function(){
this.clean().$el(this.template(this.model.toJSON()));
return this;
}
});
$(function(){
new Layout();
})
doesn't work because instead of running initialize on BaseView, Backbone calls for initiated first and this.template and all others are undefined.
Then I tried to replace it with constructor instead of initialize on BaseView. But then I end up this.$el undefined error because Backbone.View.constructor has not been called yet so no this.$el yet which is being created by _ensureElement
so with some researching the only thing I found was using
Backbone.View.prototype.constructor.apply(this,[options]);
But this also causes similar issue that at the end of Backbone.View, calls for this.initialize.apply(this, [options]), which then goes to child objects initialize instead. So I am stuck and couldn't wrap my head around this.
I also know that I can call parent's initialize function from childview but that's not preferable since there are lots of subviews extending each other. That's the reason I pass parent object to attach later object to it's children.
what I am trying to accomplish is creating a wrapper extended object that I can extend later for another object, but at the same time it should run some common tasks on original base view, attach additional prototype methods then call for callers initialize.
so pseudo
var BaseView {
extend Backbone view with the passed arguments,
check for base, parent arguments and set them
check for template option, if exists then get the element, create template function and replace with the string one,
if there is parent view passed, then attach yourself to children of parent
call your own initialize method,
return
}
If I understand you correctly, you want to run the "parent view's" initialize method when instantiating a child? If that is correct see this post: https://stackoverflow.com/a/8596882/1819684
What you're missing is the note in the backbone docs on "super" as referenced in that post.
Based on your comment I think this is what you're looking for. You have to call the "super" method (method of parent class) explicitly in backbone as shown in the post I referenced. You can do whatever you want/need to do in your "initialize" both before and after your call to the parent method. I also found this: Defining child views without an initialize method which may help.
var BaseView = Backbone.View.extend({
el: '#container1',
initialize: function(){
console.log('base view init');
this.render();
},
render: function(){
this.$el.html("Hello World 1");
}
});
var ChildView = BaseView.extend({
el: '#container2',
initialize: function() {
console.log('before call to parent (BaseView) init');
BaseView.prototype.initialize.apply(this, arguments);
console.log('ChildView init');
},
render: function(){
this.$el.html("Hello World 2");
}
});
var GrandChildView = ChildView.extend({
el: '#container3',
initialize: function() {
console.log('before call to parent (ChildView) init');
ChildView.prototype.initialize.apply(this, arguments);
console.log('GrandChildView init');
},
render: function(){
this.$el.html("Hello World 3");
}
});
var appView = new BaseView();
var appView = new ChildView();
var appView = new GrandChildView();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
<div id="container1">Loading...</div>
<div id="container2">Loading...</div>
<div id="container3">Loading...</div>

Simple BackboneJS 1.1.2 script with jquery 1.11.0 throws Object #<HTMLDivElement> has no method '_reset'

I'm trying to write a very simple BackboneJS script and I keep getting the error:
Uncaught TypeError: Object #<HTMLDivElement> has no method '_reset'
Here's the code in it's most simple format:
var myAPP = {};
myAPP.attribute = Backbone.Model.extend({});
myAPP.attributes = Backbone.Collection.extend({
model: myAPP.attribute
});
myAPP.indexView = Backbone.View.extend({
initialize: function(options) {
this.attributes = options.attributers;
alert('Works');
}
});
var view = new myAPP.indexView({attributes: new myAPP.attributes()});
The error is reproducible in jsfiddle: http://jsfiddle.net/cpu46/1/
It seems to be something with jQuery. Interestingly enough I can switch from jQuery to Zepto and things work fine. However, in my environment Zepto isn't an option so I need to figure out why BackboneJS doesn't like jQuery. I've searched around for answers but no luck. Maybe you guys can shed some light.
I think "attributes" is a keyword for Backbone.js, because when I change attributes by attrs it works.
var myAPP = {};
myAPP.attribute = Backbone.Model.extend({
initialize: function () {}
});
myAPP.attributes = Backbone.Collection.extend({
model: myAPP.attribute,
initialize: function () {}
});
myAPP.indexView = Backbone.View.extend({
initialize: function(options) {
$('body').append(this.$el);
this.attributes = options.attrs;
console.log(this.attributes);
}
});
var view = new myAPP.indexView({attrs: new myAPP.attributes()});
Look http://jsfiddle.net/wLTT7/9/

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.

Setting Backbone attributes in a model in a nested collection

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;
}
});

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"
});

Categories

Resources