0

I have a multiple instances of a view which share single instance of model among themselves.

During rendering of view, I want to call a function inside model which makes server call to fetch some data only once.

As these views are instances of same view, they all are triggering the function inside model. Hence making multiple server call.
Any idea how i can trigger this function inside model only once.

Prannoy Mittal
  • 1,525
  • 5
  • 21
  • 32
  • 1
    Why don't you fetch the model outside the view? If you don't want every view instance to execute some code, there is no need to have that call inside the view? – T J Jan 04 '17 at 17:27
  • its part of view only...but i want to be called only once...and used across all instances of view. – Prannoy Mittal Jan 04 '17 at 17:37
  • as your code is not working as you want to is a clear indication that you are doing something at wrong place and you need to move the code to some other place. You can achieve the effect if you can fetch the data before rendering any of the views in controller for example – nikhil mehta Jan 05 '17 at 09:03

2 Answers2

1

Assuming you are calling fetch on the Model. This call will return the request (actually a jqXHR object). So a pattern which can be very useful is:

fetchOnce: function() {
    if (!this.fetchRequest || this.fetchRequest.readyState == 4 && this.fetchRequest.status >= 400) {
        this.fetchRequest = this.fetch.apply(this, arguments);
    }
    return this.fetchRequest;
},

This will save the request when fetch is called and avoid any additional calls while the current request is in-progress or if it has completed successfully.

Because the jqXHR object is a Deferred Promise object, anytime fetchOnce is called, callbacks can always be added (like deferred.done):

model.fetchOnce().done(function() { console.log('model fetched!'); });
mikeapr4
  • 2,830
  • 16
  • 24
0

Extending mikeapr4's answer, I made a simple model which overrides the fetch function to only fetch once (optionally every X hours).

It uses jQuery's Deferred .state() function to determine if the request is pending or completed.

Note that I used MomentJS to calculate time differences but it could be achieved easily with JavaScript native dates.

var FetchOnceModel = Backbone.Model.extend({
    fetchDelay: 8, // hours before forcing another fetch,
    /**
     * False if the promise is pending, or the last fetch was within the delay.
     * Force a new fetch if the lang has changed since the last fetch.
     * @return {Boolean} fetch is needed
     */
    isFetchDue: function() {
        var lastFetch = this.lastFetch,
            promise = this.promise,
            // use the jQuery deferred `state` function
            isPending = promise && promise.state() === "pending";

        return !isPending && !lastFetch || // not fetched yet?
            (this.fetchDelay && moment().diff(lastFetch, 'hours') > this.fetchDelay); // is delay passed?
    },

    fetch: function() {
        if (this.isFetchDue()) {
            this.promise = this.fetch({
                context: this,
                success: this._onSync,
                error: this._onError
            });
        }
        return this.promise;
    },

    _onSync: function() {
        this.lastFetch = moment();
        this.onSync.apply(this, arguments);
    },
    _onError: function() {
        this.lastFetch = null;
        this.onError.apply(this, arguments);
    },

    // left to override by the child model
    onError: _.noop,
    onSync: _.noop
});

Then it's transparent for the view and it can call fetch any number of times it want.

A simple view using it:

var View = Backbone.View.extend({
    initialize: function() {
        // use the model in case it has been already fetched
        this.useModel();
        // then fetch anyway to ensure it's fetched
        this.listenTo(this.model, 'sync', this.onModelSync);
        this.model.fetch();
    },
    useModel: function() {
        // ...use the model data, maybe render here.
    }
    onModelSync: function() {
        // things that need to be done only when model sync succeeded.
        this.useModel();
    }
});
Community
  • 1
  • 1
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129