1

I'm trying to learn Backbone and can't seem to match data from the fetch function into my Underscore template. How can can I get the children array in my JSON and match it to the template?

The Backbone.View looks like this:

var Projects = Backbone.Collection.extend({
    url: '/tree/projects'
});

var Portfolio = Backbone.View.extend({
    el: '.page',
    render: function () {
        var that = this;
        var projects = new Projects();
        projects.fetch({
            success: function (projects) {
                var template = _.template($('#projects-template').html());
                that.$el.html(template({projects: projects.models}));
            }
        })
    }
});

At the url: http://localhost:3000/portfolio/api/tree/projects

The JSON returned looks like this:

{  
   id:"projects",
   url:"http://localhost:8888/portfolio/projects",
   uid:"projects",
   title:"Projects",
   text:"",
   files:[  

   ],
   children:[  
      {  
         id:"projects/example-1",
         url:"http://localhost:8888/portfolio/projects/example-1",
         uid:"example-1",
         title:"Example 1",
         images:"",
         year:"2017",
         tags:"Website",
         files:[  

         ],
         children:[  

         ]
      },
      {  
         id:"projects/example-2",
         url:"http://localhost:8888/portfolio/projects/example-2",
         uid:"example-2",
         title:"Example @"2
         text:"Example 2's text",
         year:"2016",
         tags:"Website",
         files:[  
            {  
               url:"http://localhost:8888/portfolio/content/1-projects/4-example-2/example_ss.png",
               name:"example_ss",
               extension:"png",
               size:244845,
               niceSize:"239.11 kB",
               mime:"image/png",
               type:"image"
            }
         ],
         children:[  

         ]
      },
   ]
}

My Underscore file looks like this:

<script type="text/template" id="projects-template">
<h4>tester</h4>
<div>
    <% _.each(projects.children, function (project) { %>
    <div>
        <div><%= project.get('year') %></div>
        <div><%= project.get('title') %></div>
        <div><%= project.get('tags') %></div>
    </div>
    <% }); %>
</div>
</script>
CalAlt
  • 1,683
  • 2
  • 15
  • 29

2 Answers2

2

You can define a parse method on the collection:

var Projects = Backbone.Collection.extend({
  url: '/tree/projects',
  parse: function(response){
    /* save other data from response directly to collection if needed.
     for eg this.title = response.title; */
    return response.children; // now models will be populated from children array
  }
});
T J
  • 42,762
  • 13
  • 83
  • 138
1

Do not use parse

While I usually agree with TJ, using parse on the collection is more like a hack than a definite solution. It would work only to get the children projects of a project and nothing more.

The parse function shouldn't have side-effects on the collection and with this approach, changing and saving fields on the parent project wouldn't be easily possible.

It also doesn't deal with the fact that it's a nested structure, it's not just a wrapped array.

This function works best when receiving wrapped data:

{
    data: [{ /*...*/ }, { /*...*/ }]
}

Models and collections

What you have here are projects that have nested projects. A project should be a model. You also have files, so you should have a File model.

Take each resource and make a model and collection classes with it. But first, get the shared data out of the way.

var API_ROOT = 'http://localhost:8888/';

File

var FileModel = Backbone.Model.extend({
    defaults: {
        name: "",
        extension: "png",
        size: 0,
        niceSize: "0 kB",
        mime: "image/png",
        type: "image"
    }
});

var FileCollection = Backbone.Collection.extend({
    model: FileModel
});

Project

var ProjectModel = Backbone.Model.extend({
    defaults: function() {
        return {
            title: "",
            text: "",
            files: [],
            children: []
        };
    },
    getProjects: function() {
        return this.get('children');
    },
    setProjects: function(projectArray, options) {
        return this.set('children', projectArray, options);
    },

    getFiles: function() {
        return this.get('files');
    },
    getSubProjectUrl: function() {
        return this.get('url');
    }
});

var ProjectCollection = Backbone.Collection.extend({
    model: ProjectModel,
    url: API_ROOT + '/tree/projects'
});

Project view

Then, make a view for a project. This is a simple example, see the additional information for tips on optimizing the rendering.

var ProjectView = Backbone.View.extend({
    template: _.template($('#projects-template').html()),
    initialize: function(options) {
        this.options = _.extend({
            depth: 0, // default option
        }, options);

        // Make a new collection instance with the array when necessary
        this.collection = new ProjectCollection(this.model.getProjects(), {
            url: this.model.getSubProjectUrl()
        });
    },

    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        this.$projectList = this.$('.list');

        // use the depth option to avoid rendering too much projects
        if (this.depth > 0) this.collection.each(this.renderProject, this);
        return this;
    }

    renderProject: function(model) {
        this.$projectList.append(new ProjectView({
            model: model,
            depth: depth - 1
        }).render().el);
    }
});

With a template like this:

<script type="text/template" id="projects-template">
    <h4><%= title %></h4>
    <span><%= year %></span><span><%= tags %></span>
    <p><%= text %></p>
    <div class="list"></div>
</script>

Using the view:

var model = new ProjectModel({ id: "project" });
model.fetch({
    success: function() {
        var project = new ProjectView({
            model: model,
            depth: 2
        });
    }
});

Additional info

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129