Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add custom "fetch" events on Models and Collections #1468

Closed
wants to merge 1 commit into from

8 participants

@spacenick

...found this pretty useful to add "generic" loader views on any collections/models.

Simple example, a generic loader that can be quickly binded to any collection, and will do the job.

var Loader = Backbone.View.extend({

    tagName:'img',
    attributes:{
        src:'loader.gif'
    },

    initialize:function() {
        _.bindAll(this,'hide','show')
        this.collection.on('reset',this.hide);
        this.collection.on('fetch',this.show);
    },

    hide:function() {
        $(this.el).hide();
    },
    show:function() {
        $(this.el).show();
    }

})
@spacenick spacenick Add "fetch" event on Models and Collections to be able to observe it.…
… I found this pretty useful to add "generic" loader views on any collections/models.


Simple example, a generic loader that can be quickly binded to any collection, and will do the job.

var Loader = Backbone.View.extend({

	tagName:'img',
	attributes:{
		src:'loader.gif'
	},

	initialize:function() {
		_.bindAll(this,'hide','show')
		this.collection.on('reset',this.hide);
		this.collection.on('fetch',this.show);
	},

	hide:function() {
		$(this.el).hide();
	},
	show:function() {
		$(this.el).show();
	}

})
2dafb01
@tbranyen
Collaborator

To accompany this, here is a post I wrote recently about a fetch event:

http://tbranyen.com/post/how-to-indicate-backbone-fetch-progress

@braddunbar
Collaborator

I like to do this with a custom sync. I find it to be a bit more generic and usually I want to do the same for any request, not just fetch.

var Model = Backbone.Model.extend({
  sync: function(method, model, options) {
    model.trigger('syncing', model, options);
    Backbone.Model.prototype.sync.apply(this, arguments);
  }
});
@kshaff03

Too funny. I just came here to request this feature and it was right here on the front page. I would like for a generic Fetch event to be added to Model as well. I realize that I can add a callback to the fetch() function but this is not nearly as flexible. Please pull this in.

@tbranyen
Collaborator

@braddunbar that's a much better idea. what are your thoughts on adding this simple event?

@braddunbar
Collaborator

@tbranyen I'd be for it, though I think there would need to be some consideration for multiple in-flight requests. Also, this is likely related, at least tangentially, to #1325.

@conorjpower

I came across this while looking for an event to determine when a model is loaded from the server rather than changed. While 'syncing' is good it would be most helpful to know when the sync was completed successfully. See http://stackoverflow.com/questions/12038192/render-a-view-for-a-model-on-first-load-not-on-every-change/. Extending @braddunbar I used:

Backbone.Model.prototype.sync = function(method, model, options) {

var succ = options.success;
var customSuccess = function(resp, status, xhr) {
     //call original
    succ(resp, status, xhr);
    model.trigger('synced', model, options);
}
options.success = customSuccess;
Backbone.sync(method, model, options);

}

To overwrite with a custom success which triggers the event and then delegates to the original success. Any potential gotchas with this approach?

@kshaff03

After reading conorjpower's post, I went and looked at this pull request again. My primary use case is when I have a model that I need to fetch from the server and once it has been successfully fetched, render it or perform some processing. The way this pull request is written, it fires prior to the request actually succeeding which doesn't help me. conorjpower's suggestion looks like it would satisfy my requirements.

@wookiehangover
Collaborator

@kshaff03 did you know that you can use the deferred object issued by $.ajax (if you're using jQuery, obvs) to chain callbacks and have a definite state of a given fetch? I've used this pattern a lot for situations when I need to bind one-off functionality to a fetch, usually while initializing a model or collection.

Usually, it's something like this:

var model = Backbone.model.extend({
  url: '/some/resource',
  initialize: function(){
    this.dfd = this.fetch();
    this.dfd.done(function(){
      // your one-time binding to fetch()
    });
  }
});

The other benefit of assigning the fetch() deferred as a model property is that you can easily access & bind to it from other places in your code. Check out this post for more details on this technique.

@kshaff03

@wookiehangover Thanks. I didn't know you could do this, however, I would still prefer an event driven approach. The approach above assumes that you want the fetch to be called from within the model itself. In most cases, my models are controlled by a parent object and it will be that parent object that will decide when that fetch is executed. I love the event driven approach because of its flexibility (can be handled either within or outside of the model) and allows me to decouple my code and keep my business logic in my models and my view logic in the views. Naturally, I could take your code above and wrap it within my own fetch method and call it myFetch() or something like that and raise a custom event but then I have to do this to every other model as well. Way easier to just tweak backbone or extend it.

There are several ways to do something similar to this and I've managed to make things work on a case by case basis. However, I would think that having an event that signals that your model object has been successfully retrieved from the server would be a reasonable request...

@conorjpower

An additional update on the code above:

var succ = options.success;
var customSuccess = function(resp, status, xhr) {
    //edit here
    model.trigger('synced', model, options);
    //call original
    succ(resp, status, xhr);
}
options.success = customSuccess;
Backbone.sync(method, model, options);

I found that it was better to trigger the event before calling the original success method. This is important if you want the synched to be fired before the change event.

@jashkenas
Owner

If we add this -- what's the best name for the event? syncing/sync/error is a bit of an awkward trinity, but I guess it works...

@braddunbar
Collaborator

I've been using syncing, though maybe request, send, or busy would be better? I particularly like request because of the way is sounds like a sentence. "On request, do the following..."

@spacenick

It seems that people using this little patch are using the "syncing" word a lot, as what I've seen on some blogs. "request" sounds good to me aswel but can be kinda disturbing if you use another persistence strategy (websockets for instance)

@akre54
Collaborator

@braddunbar this is great as long as there's some way to determine the current sync state of the model, since I often times need to check the sync status (is it unsynced? syncing? error'd? synced?) outside of an evented system similar to a finite-state machine. It'd be easy enough to implement (see Chaplin's implementation)

@jashkenas jashkenas closed this pull request from a commit
@jashkenas Fixes #1468 -- add a 'request' event that allows folks to watch for s…
…pinners etc. Passes the XHR on which you can .then, .error, and so on.
ec97a1c
@jashkenas jashkenas closed this in ec97a1c
@ssolomon ssolomon referenced this pull request from a commit
@jashkenas Fixes #1468 -- add a 'request' event that allows folks to watch for s…
…pinners etc. Passes the XHR on which you can .then, .error, and so on.
078b5ba
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 3, 2012
  1. @spacenick

    Add "fetch" event on Models and Collections to be able to observe it.…

    spacenick authored
    … I found this pretty useful to add "generic" loader views on any collections/models.
    
    
    Simple example, a generic loader that can be quickly binded to any collection, and will do the job.
    
    var Loader = Backbone.View.extend({
    
    	tagName:'img',
    	attributes:{
    		src:'loader.gif'
    	},
    
    	initialize:function() {
    		_.bindAll(this,'hide','show')
    		this.collection.on('reset',this.hide);
    		this.collection.on('fetch',this.show);
    	},
    
    	hide:function() {
    		$(this.el).hide();
    	},
    	show:function() {
    		$(this.el).show();
    	}
    
    })
This page is out of date. Refresh to see the latest.
Showing with 8 additions and 0 deletions.
  1. +8 −0 backbone.js
View
8 backbone.js
@@ -336,6 +336,10 @@
model.trigger('sync', model, resp, options);
};
options.error = Backbone.wrapError(options.error, model, options);
+
+ // Trigger "fetch" events
+ this.trigger('fetch',this,options);
+
return this.sync('read', this, options);
},
@@ -780,6 +784,10 @@
collection.trigger('sync', collection, resp, options);
};
options.error = Backbone.wrapError(options.error, collection, options);
+
+ // Trigger "fetch" events
+ this.trigger('fetch',this,options);
+
return this.sync('read', this, options);
},
Something went wrong with that request. Please try again.