1

Ok, so I am working on a method to override the fetch method on a model. I want to be able to pass it a list of URL's and have it do a fetch on each one, apply some processing to the results, then update its own attributes when they have all completed. Here's the basic design:

  1. A Parent "wrapper" Model called AllVenues has a custom fetch function which reads a list of URL's it is given when it is instantiated
  2. For each URL, it creates a Child Model and calls fetch on it specifying that URL as well as a success callback.
  3. The AllVenues instance also has a property progress which it needs to update inside the success callback, so that it will know when all Child fetch's are complete.

And that's the part I'm having problems with. When the Child Model fetch completes, the success callback has no context of the Parent Model which originally called it. I've kind of hacked it because I have access to the Module and have stored the Parent Model in a variable, but this doesn't seem right to me. The Parent Model executed the Child's fetch so it should be able to pass the context along somehow. I don't want to hardcode the reference in there.

TL;DR

Here's my jsFiddle illustrating the problem. The interesting part starts on line 13. http://jsfiddle.net/tonicboy/64XpZ/5/

The full code:

// Define the app and a region to show content
// -------------------------------------------
var App = new Marionette.Application();
App.addRegions({
    "mainRegion": "#main"
});

App.module("SampleModule", function (Mod, App, Backbone, Marionette, $, _) {
    var MainView = Marionette.ItemView.extend({
        template: "#sample-template"
    });

    var AllVenues = Backbone.Model.extend({
        progress: 0,
        join: function (model) {
            this.progress++;
            // do some processing of each model
            if (this.progress === this.urls.length) this.finish();
        },
        finish: function() {
            // do something when all models have completed
            this.progress = 0;
            console.log("FINISHED!");
        },
        fetch: function() {
            successCallback = function(model) {
                console.log("Returning from the fetch for a model");
                Mod.controller.model.join(model);
            };
            _.bind(successCallback, this);
            $.each(this.urls, function(key, val) {
                var venue = new Backbone.Model();
                venue.url = val;
                venue.fetch({
                    success: successCallback
                });
            });
        }
    }); 

    var Venue = Backbone.Model.extend({
        toJSON: function () {
            return _.clone(this.attributes.response);
        }
    });

    var Controller = Marionette.Controller.extend({
        initialize: function (options) {
            this.region = options.region;
            this.model = options.model;
            this.listenTo(this.model, 'change', this.renderRegion);
        },
        show: function () {
            this.model.fetch();
        },
        renderRegion: function () {
            var view = new MainView({
                model: this.model
            });
            this.region.show(view);
        }
    });
    Mod.addInitializer(function () {
        var allVenues = new AllVenues();
        allVenues.urls = [
            'https://api.foursquare.com/v2/venues/4a27485af964a52071911fe3?oauth_token=EWTYUCTSZDBOVTYZQ3Z01E54HMDYEPZMWOC0AKLVFRBIEXV4&v=20130811',
            'https://api.foursquare.com/v2/venues/4afc4d3bf964a520512122e3?oauth_token=EWTYUCTSZDBOVTYZQ3Z01E54HMDYEPZMWOC0AKLVFRBIEXV4&v=20130811',
            'https://api.foursquare.com/v2/venues/49cfde17f964a520d85a1fe3?oauth_token=EWTYUCTSZDBOVTYZQ3Z01E54HMDYEPZMWOC0AKLVFRBIEXV4&v=20130811'
        ];
        Mod.controller = new Controller({
            region: App.mainRegion,
            model: allVenues
        });
        Mod.controller.show();
    });
});
App.start();
T Nguyen
  • 3,309
  • 2
  • 31
  • 48

1 Answers1

3

I think you're misunderstanding how _.bind works. _.bind returns the bound function, it doesn't modify it in place. In truth, the documentation could be a bit clearer on this.

So this:

_.bind(successCallback, this);

is pointless as you're ignoring the bound function that _.bind is returning. I think you want to say this:

var successCallback = _.bind(function(model) {
    console.log("Returning from the fetch for a model");
    Mod.controller.model.join(model);
}, this);

Also note that I added a missing var, presumably you don't want successCallback to be global.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • Thanks, that really helped. I guess I was confused by the syntax because its sibling method bindAll() isn't assigned to anything. One small thing you left out is the actual context itself, which is passed in after the function. I've updated the fiddle with some hokey processing on each model just to test if it was working. It is! http://jsfiddle.net/tonicboy/64XpZ/7/ – T Nguyen Aug 11 '13 at 15:26
  • 1
    Thanks for the heads up on the missing `this` argument. When you call `_.bindAll(obj, ...)`, you pass a `this` in the first argument so Underscore can replace properties in `obj` with bound functions. If you take a look at the [implementation](http://underscorejs.org/docs/underscore.html#section-64), you'll see that `_.bindAll` is little more than a `obj[f] = _.bind(obj[f], obj)` loop. – mu is too short Aug 11 '13 at 16:42
  • @TNguyen: This answer might be of interest: [What is the difference between these Backbone/Underscore bind() methods](http://stackoverflow.com/a/7087753/479863). – mu is too short Aug 11 '13 at 17:55