Search code examples
javascriptbackbone.jsbackbone-relational

Backbone js code smell - better way to embed a sub-view?


I'm building a backbone app using backbone-relational models (but that shouldn't matter for this question).

Basically, I have an edit button that will display a hidden div. Inside the hidden div id a sub-view (called DetailsView) that renders table elements to populate a list of users. My model (for the whole app) looks roughly like this:

{ 'id': 'foo',
  'users': 
   {
   'username': 'bobby',
   'password': 'peanuts'
   },
   {
   'username': 'sally',
   'password': 'watermellon'
   }
}

In my main view (that is fed by a collection of the above models), when the user clicks the edit button, this is triggered:

edit: function(){
      var userModels = this.model.get('users'),
          detailViewContainer = this.$('tbody.users');
      console.log(name + ' has ' + userModels.length + ' models');
      //clear out eveything in tbody.users to prevent dupes
      detailViewContainer.html('');
      //check if there are actually user models
      if(userModels.length > 0){
        userModels.forEach(function(user) {
         var details = new DetailsView({model: user});
         details.render();
         detailViewContainer.append(details.el);
         });
}

The code smell comes from the fact that I have to declare that detailViewContainer explicitly.

Originally, in my forEach loop would call another function in the view that contained the code to declare and render the DetailsView. However, I would loose the context of this.

My original code looked something like this:

   edit: function() {
      var userModels = this.model.get('users'),
          detailViewContainer = this.$('tbody.users');
      console.log(name + ' has ' + userModels.length + ' models');
      //clear out eveything in tbody.users to prevent dupes
      detailViewContainer.html('');
      //check if there are actually user models
      if(userModels.length > 0){
        userModels.forEach(function(user) {
         this.renderDetailsView;
         });
      }
    },

    renderDetailsView: function(user) {
       var details = new DetailsView({model: user});
       this.$('tbody.users').append(details.render());
     },

In the renderDetailsView, I would loose context of this and could not append the DetailsView to the proper DOM element (the view would append to all of the tbody.users DOM elements , as the this context became the window since it was in a loop).

Having to explicitly declare a detailsViewContainer seems hacky to me, and I'd like to be able to keep the this context pointing to the main view, not the entire window.

The DetailsView template just a set of <tr><td></td></tr> elements. Is there a better way to embed this view without having to resort to creating the detailViewContainer?

(One possible option was having the DetailView loop through the collection returned from this.model.get('users') all by itself... is that a good idea?)


Solution

  • If you're doing what you're doing because of the loss of 'this,' you can pass your context to forEach.

    userModels.forEach(function(user) {
         this.renderDetailsView();
    },this);
    

    Now you have the proper 'this' available to you. Hope this helps.