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
}