Backbone view inheritance - javascript

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>

Related

Add new collection to parent view from child view

I have a parent view (which is for car engines) that contains a child view which displays a list of options. One of the options is to add a new collection to the parent view.
My child view init function looks like this:
initialize: function (engine) {
this.engine = engine; //parent object
this.valves = engine.valves; //may or may not be empty
};
Then I have this method that adds the collection(valves) when a button is pressed:
addPerformanceValves: function() {
var self = this;
if (this.valves.lentgh == 0) {
this.valves = new ValveCollection();
this.valves.url = function() {
return '/api/engines/auto/performance/parts/' + self.id + '/valves';
}
}
this.$('.performanceParts').show();
}
So now that I created the new collection, how do I add it to the parent?
There are multiple ways to achieve that.
Passing the parent object down the hierarchy
Like you're already doing, you could call a function from the parent object to pass the new collection.
var Child = Backbone.View.extend({
initialize: function(options) {
options = options || {};
this.engine = options.engine; //parent object
this.valves = engine.valves; //may or may not be empty
},
addPerformanceValves: function() {
var self = this;
if (this.valves.lentgh == 0) {
this.valves = new ValveCollection();
this.valves.url = function() {
return '/api/engines/auto/performance/parts/' + self.id + '/valves';
}
// call the parent
this.engine.addNewCollection(this.valves);
}
this.$('.performanceParts').show();
}
});
var Parent = Backbone.View.extend({
addNewCollection: function(collection) {
// do what you want with the collection
this.newCollection = collection;
}
});
Triggering events
One way to avoid strong coupling is to trigger events from the child view, to which the parent is listening.
var Child = Backbone.View.extend({
initialize: function(options) {
options = options || {};
this.valves = options.valves; //may or may not be empty
},
addPerformanceValves: function() {
var self = this;
if (this.valves.lentgh == 0) {
this.valves = new ValveCollection();
this.valves.url = function() {
return '/api/engines/auto/performance/parts/' + self.id + '/valves';
}
// call the parent
this.trigger('child:collection', this.valves);
}
this.$('.performanceParts').show();
}
});
var Parent = Backbone.View.extend({
initialize: function() {
this.child = new Child({ valves: this.valves });
// listen to child view events
this.listenTo(this.child, 'child:collection', this.addNewCollection);
},
addNewCollection: function(collection) {
// do what you want with the collection
this.newCollection = collection;
}
});
Then, the child view only has what it needs and nothing more. It help to keep responsibilities at the right place.

MultiLevel Inheritance in Backbone view

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.

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

Call different functions of different views of backbone.js from a global function [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Call methods of different views in Backbone.js
I am new to backbone.js and I am using backbone.js with ASP.NET MVC 4.
I have a global class called SomeObject in which I have a deleteUser function. This function is binded with one of the click event of a button present in MyView2.
How do I call the following backbone.js different functions present in different views, from this global function/class.
call myMethodB of MyView 2
call myMethodA of MyView 1
call myMethodC of AppView
Please guide me on this. I am still learning backbone.js and might be doing something wrong. Thanks
var SomeObject = function (Id, Name) {
var self = this;
this.Id = Id;
this.Name = Name;
this.deleteUser = function () {
console.log(self.Id, self.Name);
// call myMethodB of MyView 2
// call myMethodA of MyView 1
// call myMethodC of AppView
};
};
var MyModel = Backbone.Model.extends({
});
// View for a Main Grid
var MyView1 = Backbone.View.extend({
...
myMethodA: function(){
// do something with View 1
}
...
});
// View for subgrid in Main Grid
var MyView2 = Backbone.View.extend({
...
myMethodB: function(){
// do something with View 2
}
...
});
var AppView = Backbone.View.extend({
...
myMethodC: function(){
// do something with App View
}
...
});
You should use Backbone events - http://backbonejs.org/#Events
var SomeObject = function (Id, Name) {
var self = this;
this.Id = Id;
this.Name = Name;
this.deleteUser = function () {
console.log(self.Id, self.Name);
viewInstances.MyView2.trigger('eventFormyMethodB')
viewInstances.MyView1.trigger('eventFormyMethodA')
viewInstances.AppView.trigger('eventFormyMethodC')
};
};
var viewInstances = {
MyView2: new MyView2({...}),
MyView1: new MyView1({...}),
AppView: new AppView({...})
}
For example
var AppView = Backbone.View.extend({
...
events: {
'eventFormyMethodC': 'myMethodC'
},
myMethodC: function(){
// do something with App View
}
...
});

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