Scott
Scott

Reputation: 1342

How can I pass a backbone.js collection to a view and iterate over the collection, then render the view?

In the past, what I've been doing is when I want to render and display a collection with a template is just call the "each" method on the collection. And for each model, I make a new single view for each model and append it to the $el. But In this case this won't work. I have an input type text that is in my template and I don't want to display a new input for each model. I would rather somehow pass the collection to the template and iterate over the collection in the template. Also worth noting is that my collection is passed in through my main app view when in create a new instance of this view:

// clientsview.js

define(
[
    'backbone',
    'ClientView',
    'Clients'
], function(Backbone, ClientView, Clients) {

ClientsView = Backbone.View.extend({

    tagName: '#page',
    template: _.template( $('#allClientsTemplate').html() ),


    initialize: function() {
        this.render();
    },

    render: function() {

        // var that = this;
        console.log('collection');
        console.log(this.collection);// successfully returns collection
        console.log('template');
        console.log(this.template);// successfully returns template function


        // This causes an error: 
        //Uncaught TypeError: Cannot call method 'toJSON' of undefined 
        var tmpl = this.template({ clients: this.collection.toJSON() });

        return this;

    }

});
return ClientsView;
});

my template in index.html

// index.html
<script id="allClientsTemplate" type="text/template">
// I do not want create an input for every single model
// but I need the input to be part of the view.
<input type="text" class="table-search" id="search" autocomplete="off"   placeholder="Search Clients…">
<table class="table" id="tblData">
    <thead>
        <tr>
            <th>First Name</th>
            <th>Last Name</th>
            <th>Status</th>
        </tr>
    </thead>
    <tbody id="tblDataBody">

        <% _.each(clients, function(client) { %>

            <tr>
                <td><a href=""><%- client.first_name %></a></td>
                <td><a href=""><%- client.last_name %></a></td>
                <td><a href=""><%- client.status %></a></td>
            </tr>

        <% }); %>

    </tbody>
</script>

I'm instantiating my clients view from within my main app view:

define(
    [
        'jquery',
        'underscore',
        'backbone',
        'AddClientView',
        'ClientsView',

    ], function( $, _, Backbone, AddClientView, ClientsView ) {

    AppView = Backbone.View.extend({

        initialize: function() {

            // var addClientView = new AddClientView({ collection: this.collection });
            var clientsView   = new ClientsView({ collection:this.collection });


        }

    });
    return AppView;
});

And I'm instantiating my main app view from my initialize method:

define('App', [
    'jquery',
    'underscore',
    'backbone',
    'Router',
    'bootstrap',
    'Clients',
    'AppView'
], function($, _, Backbone, Router, bootstrap, Clients, AppView) {

    function initialize() {

        // for custom global events
        window.vent = _.extend( {}, Backbone.Events )

        var app = new Router();
        Backbone.history.start();


        // probably more appropriate to do this in app view
        var clients = new Clients;// Get collection of clients


        clients.fetch().then( function() {// fetch client collection from db
            new AppView({ collection: clients });
        });

    }
    return {
        initialize: initialize
    };
});

My initialize method is being called from main.js:

// main.js
require.config({
    shim: {
        'underscore': {
         exports: '_'
        },
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        },
        'bootstrap': {
            deps: ['jquery'],
            exports: 'bootstrap'
        }
    },
    paths: {
        'text'          : 'lib/require/text',
        'jquery'        : 'lib/jquery/jquery-1.9.1',
        'underscore'    : 'lib/underscore/underscore',
        'backbone'      : 'lib/backbone/backbone',
        'bootstrap'     : 'lib/bootstrap/bootstrap',
        'App'           : 'app',
        'Router'        : 'router',
        'AppView'       : 'views/app/appview',
        'Client'        : 'models/clients/client',
        'Clients'       : 'collections/clients/clients',
        'ClientView'    : 'views/clients/clientview',
        'ClientsView'   : 'views/clients/clientsview',
        'AddClientView' : 'views/clients/addclientview'
    }
});
require(['App'], function(App) {
    App.initialize();
});

I'm getting an error when I try this approach (Uncaught TypeError: Cannot call method 'toJSON' of undefined). How can I make this work?

Upvotes: 0

Views: 8662

Answers (2)

Elise Chant
Elise Chant

Reputation: 5196

Your Backbone is correct. Your problem here is that you're not passing the data through the promise created by fetch when you instantiate your AppView:

your code:

clients.fetch().then( function() {// fetch client collection from db
     new AppView({ collection: clients });
});

it should be:

clients.fetch().then( function(resp) {// fetch client collection from db
     new AppView({ collection: resp.data });
});

Upvotes: 1

hnafar
hnafar

Reputation: 611

You have to instantiate your collection, add items to it, then instantiate the view and pass the collection to it:

var clients= new Clients();
clients.add({FirstName: 'Joe', LastName: 'Blow', Status: 'Active' });

var clientsView = new ClientsView({collection: clients});

You also need to change your template:

<% _.each(collection, function(client) { %>
<tr>
    <td><a href=""><%- client.get('FirstName') %></a></td>
    <td><a href=""><%- client.get('LastName') %></a></td>
    <td><a href=""><%- client.get('Status') %></a></td>
</tr>
<% }); %>

Upvotes: 2

Related Questions