1

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 
}
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
serdar.sanri
  • 2,217
  • 1
  • 17
  • 21
  • Here's different ways to [make a good base class with Backbone](http://stackoverflow.com/a/40982556/1218980). It looks like a lot of responsibilities are not handled in the right class. I never needed to pass a parent class down to its children, It's always better for the parent to handle the children and to listen to events so the child class can stay focused and reusable. – Emile Bergeron May 05 '17 at 21:28
  • Thank you. Yes and I agree on parent-child relation you mentioned. But with in atomic design concept. Child inheritance may have additional stuff. Think about something like old ExtJs. original base class is Ext.Element, which creates an empty div element, id etc attributes. Then Ext.Panel extends Ext.Element, and with Ext.Elements functionality, adds panel header, body, design etc, then Ext.Window extends Ext.Panel using pretty much same functionality but floating etc. they all have initialize methods and gets called in order to get to main element. – serdar.sanri May 05 '17 at 21:41
  • But at the same time before Ext.Window is initialized with caller's config, Ext.Element gets created first. What I am trying to accomplish is similar. BaseView should be a wrapper content setup that handles most of pre-initialize functionality, then when creating new objects out of it, it attaches itself to parent object, so when parent object is destroyed, all children can go with it. Otherwise, children will be orphan when parent is removed. – serdar.sanri May 05 '17 at 21:44

1 Answers1

0

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>
Community
  • 1
  • 1
gforce301
  • 2,944
  • 1
  • 19
  • 24
  • Yes and no. I want to call parent's initialize method first, to prepare some attributes ( template, etc ) then continue on child's initialize. Because also child can be extended. This way it will override it imo – serdar.sanri May 05 '17 at 21:13
  • I added an example of what you say in your comment. I think this is what you want. – gforce301 May 08 '17 at 20:54
  • thank you for the example. I see what you did. But as I mentioned in my question, I don't want to, also can't call parent prototype in child constructors since they can be in different container's or extended from different types of classes. That's the main reason I have parent and base parameters so, parent is responsible for disposing children when removed. otherwise when I remove all children from a container, i will have to handle garbage collection manually, and with in the team of devs I am, it may got forgotten and may cause memory leaks easily. – serdar.sanri May 09 '17 at 02:44