0
votes

I know how to filter a collection from within my view using listeners, but I can't figure out how to do it using the backbone router and the url.

I can't access my views or collections from the router, as they're not instantiated yet. By the I mean I can't just add a filter method to my collection and then call app.PurchaseList.filter. I could create a new collection, containing the filtered items, but how do I relay this to my view?

Some questions were answered using PubSub, but I thought there'd be a more straight forward way. Or is it preferred not to use url routes for such a task and trigger events from my views directly (in that case the entire filter code could stay within my AppView view, making things a lot easier).

And yes, I am a beginner, so much should be obvious ;)

js/routes/backboneRouter.js

var app = app || {};
app.Router = Backbone.Router.extend({
    routes: {
        'filter/:filter': 'filter'
    },

    filter: function(filter) {
        //...
    }
});

js/app.js

var app = app || {};

$(function() {
    app.router = new app.Router();
    Backbone.history.start();
    app.appView = new app.AppView();
});

js/views/appView.js

var app = app || {};
app.AppView = Backbone.View.extend({
    el: '#app',

    initialize: function() {
        this.collection = new PurchaseList();
        this.collection.fetch({reset: true});
        this.render();
        this.listenTo(this.collection, 'add', this.renderPurchases);
        this.listenTo(this.collection, 'reset', this.render);
    },

    events: {
        'click #add-purchase': 'addPurchase',
    },

    filter: function(filter) {
        console.log(filter);
    },

    addPurchase: function(event) {
        //...
    },

    render: function() {
        this.collection.each(function(item) {
            this.renderPurchases(item);
        }, this);
    },

    renderPurchases: function(item) {
        var purchaseView = new PurchaseView({
            model: item
        });
        this.$('#list').append(purchaseView.render().el);
    }
});

js/collections/purchases.js

var app = app || {};
app.PurchaseList = Backbone.Collection.extend({
    model: PurchaseItem,
    url: 'api/purchase',
});
2
"I can't access my views or collections from the router, as they're not instantiated yet" and then you're doing app.appView.filter('reset'); from the router.. isn't that a view..? Generally we initialize the things depending on routes from the router and pass in the url params... If you don't have access to anything maybe you should consider redesigning..? - T J
Sorry that was a leftover function call that didn't work (filter undefined), from when I was experimenting. - Vey

2 Answers

0
votes

Generally you'd let the collection do the filtering:

app.PurchaseList = Backbone.Collection.extend({
    model: PurchaseItem,
    url: 'api/purchase',
    by: (attribute, value) => {
        let filter = {}
        filter[attribute] = value
        return new app.PurchaseList(this.where(filter));
    }
});

Untested!

0
votes

I realize now that I should not have posted this at 1am in the morning, as things are a lot clearer now.

My first problem was that the collection was only initialized from within my AppView. As far as I can tell, no other function had access to the collection, as it was confined to the scope of AppView.

I moved the initialization of the collection to the global scope by adding it to my app's namespace object (var app = app || {}), so that it was accessible as app.purchaseList throughout the entire app.

Additionally, calling a view's or collection's functions directly is (please correct me if I am wrong) not a good practice. Therefore, the router now triggers a 'filter' event on my collection, to which my view is listening. The filter value (/filter/:filter) is sent to the collection (or wherever I need it) via the second argument of the trigger.

Router

app.Router = Backbone.Router.extend({
    routes: {
        'filter/:filter': 'filter'
    },

    filter: function(value) {
        console.log('router filter');
        app.purchaseList.trigger('filter', value);
    }
}); 

App

var app = app || {};

$(function() {
    app.appView = new app.AppView();
    app.router = new app.Router();
    Backbone.history.start();
});

Collection

var app = app || {};
app.PurchaseList = Backbone.Collection.extend({
    model: PurchaseItem,
    url: 'api/purchase',
});
app.purchaseList = new app.PurchaseList();

View

var app = app || {};
app.AppView = Backbone.View.extend({
    el: '#app',

    initialize: function() {
        //...
        this.listenTo(app.purchaseList, 'filter', this.filterList);
    },

    events: {
        'click #add-purchase': 'addPurchase',
    },

    filterList: function(value) {
        console.log('appView filter');
        console.log(value);
    },

    addPurchase: function(event) {
        //...
    },

    render: function() {
        this.collection.each(function(item) {
            this.renderPurchases(item);
        }, this);
    },

    renderPurchases: function(item) {
        var purchaseView = new PurchaseView({
            model: item
        });
        this.$('#list').append(purchaseView.render().el);
    }
});

Going to localhost:8080/#/filter/:test now outputs

Navigated to http://localhost:8080/
backboneRouter.js:8 router filter
list.js:19 appView filter
list.js:20 test

Which is what I wanted to achieve.

PS: I apologize for the vague question and not doing enough research (as I was able to solve my own question). I'll be sure to not make this mistake again.