Skip to content
This repository

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

Closed
wants to merge 1 commit into from

8 participants

Nicolas Kermarc Tim Branyen brad dunbar Kevin Shaffer Conor Power Sam Breed Jeremy Ashkenas Adam Krebs
Nicolas Kermarc

...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();
    }

})
Nicolas Kermarc 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
Tim Branyen
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

brad dunbar
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);
  }
});
Kevin Shaffer

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.

Tim Branyen
Collaborator

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

brad dunbar
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.

Conor Power

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?

Kevin Shaffer

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.

Sam Breed
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.

Kevin Shaffer

@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...

Conor Power

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.

Jeremy Ashkenas
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...

brad dunbar
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..."

Nicolas Kermarc

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)

Adam Krebs
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)

Jeremy Ashkenas jashkenas closed this pull request from a commit
Jeremy Ashkenas 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
Jeremy Ashkenas jashkenas closed this in ec97a1c
Steve Solomon ssolomon referenced this pull request from a commit in ssolomon/backbone
Jeremy Ashkenas 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

Showing 1 unique commit by 1 author.

Jul 03, 2012
Nicolas Kermarc 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
This page is out of date. Refresh to see the latest.

Showing 1 changed file with 8 additions and 0 deletions. Show diff stats Hide diff stats

  1. 8  backbone.js
8  backbone.js
@@ -336,6 +336,10 @@
336 336
         model.trigger('sync', model, resp, options);
337 337
       };
338 338
       options.error = Backbone.wrapError(options.error, model, options);
  339
+      
  340
+      // Trigger "fetch" events
  341
+      this.trigger('fetch',this,options);
  342
+      
339 343
       return this.sync('read', this, options);
340 344
     },
341 345
 
@@ -780,6 +784,10 @@
780 784
         collection.trigger('sync', collection, resp, options);
781 785
       };
782 786
       options.error = Backbone.wrapError(options.error, collection, options);
  787
+      
  788
+      // Trigger "fetch" events
  789
+      this.trigger('fetch',this,options);
  790
+      
783 791
       return this.sync('read', this, options);
784 792
     },
785 793
 
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.