Question

When developing Backbone applications, I often find myself instantiating models in views when dealing with nested data. Here's some example data:

{
  name: Alfred,
  age 27,
  skills: [
    {
      name: 'Web development',
      level: 'Mediocre'
    },
    {
      name: 'Eating pizza',
      level: 'Expert'
  ]
}

Say I have some view PersonView that takes a person object PersonModel as its model (where Alfred would be an instance). And let's say I want to render the person's skills as subviews. At this point I create a new view and a new model for dealing with the nested skills data. And this is where I suspect I'm doing something wrong, or at least something suboptimal.

var SkillModel = Backbone.Model.extend();

var SkillView = Backbone.View.extend({
  tagName: 'li',
  initialize: function() {
    this.render();
  },
  render: function() {
    this.$el.html(someTemplate(this.model));
  }
});

var PersonView = Backbone.View.extend({
  el: ...
  initialize: ...
  renderSkills: function() {
    _.each(this.model.get('skills'), function(skill) {
      var skillModel = new SkillModel(skill);
      var skillView = new SkillView({ model: skillModel });

      self.$el.append(skillView.el);
    })
  }
});

Should I really be doing this or is there some better pattern, that does not involve third-party libraries, that solves the problem? I feel like I'm not really decoupling the views and models in the best way. If you want a concrete question it's: Is this an anti-pattern?

Was it helpful?

Solution

Views are for view logic, Models for data logic.

Consider to have a Model like this:

var SkillModel = Backbone.Model.extend({
});
var SkillsCollection = Backbone.Collection.extend({
    model : SkillModel
});
var PersonModel = Backbone.Model.extend({

    /*
    ** Your current model stuff
    */

    //new functionality 

    skills : null,

    initialize : function(){
        //set a property into the model that stores a collection for the skills
        this.skills = new SkillsCollection;
        //listen to the model attribute skills to change
        this.on('change:skills', this.setSkills);
    },

    setSkills : function(){
        //set the skills of the model into skills collection
        this.skills.reset( this.get('skills') );
    }

});

So in your view renderSkills would be something like this:

var PersonView = Backbone.View.extend({
  renderSkills: function() {
     //this.model.skills <- collection of skills
    _.each(this.model.skills, function(skill) {
      var skillView = new SkillView({ model: skillModel });

      self.$el.append(skillView.el);
    })
  }
});

I did my pseudo code trying to adapt to your sample code. But if you get the point, basically you could have a nested model or collection into your Model, so in the view there is not needed data interaction / set, everything is in the model. Also I answer as your requested, handling things without a third party. However don't mind taking a look to the source of this kind of plugins also: https://github.com/afeld/backbone-nested

Update: per comments I provided a setup of the Model example:

var m = new PersonModel();
//nested collection is empty
console.log(m.skills.length);
m.set({skills : [{skill1:true}, {skill1:true}]});
//nested collection now contains two children
console.log(m.skills.length);

As you can see the Model usage is as "always" just a set of attributes, and the model is the one handling it. Your view should use skills as how views use any other collection. You could do a .each iterations or worth better to listen for events like add, reset, etc. on that collection(but that is other topic out of the scope of this question).

Example: http://jsfiddle.net/9nF7R/24/

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top