I can't seem to figure out which event to listen to when fetching data for a model. Usually when I'm doing it for a collection, I listen to the sync event. However, it seems like that doesn't work for models.
So, how do I know when my model is done fetching? Which event does it trigger?
Edit: Here's the beginning part of my view that is using the model:
var HomeContent = BaseView.extend({
initialize: function(options) {
self = this;
this.academyID = this.options.parent.academyID;
this.model = new AcademyModel({academyID: this.academyID});
this.model.on('sync', function() {
console.log('sync');
});
this.model.fetch();
}
fetch returns a jQuery promise. Just use something like:
this.model.fetch().done(function() {
...
}
Another solution is in the docs:
Accepts success and error callbacks in the options hash, which are both passed (model,response, options) as arguments.
Related
Say I have Backbone view like so:
var Footer = Backbone.View.extend({
model:null,
collection:null,
initialize: function () {
this.listenTo(this.model,'change',this.render);
this.listenTo(this.collection,'reset',this.render);
}
render: function(){
}
});
if this.model and this.collection are null at the time of the this.listenTo calls, I know that no error is thrown (that's good) but will the listeners get registered if the model/collection is null at the time of the listenTo call and only later are instantiated?
The short answer is no, if the object you are trying to listen to is null nothing is bound, you can see this by having a look at the annotated source for listenTo
Events.listenTo = function(obj, name, callback) {
if (!obj) return this;
Notice how backbone just returns from the function.
I have a div#game.
In here I want some data from the server to appear.
So when I use the initialize function I request data from my server in my game model with an ajax call.
Game = Backbone.View.extend(
{
id: 'game',
initialize: function ()
{
this.model.on('getGame', function() {});
this.model.getGame();
}
}
Because a callback doesn't seem to work in backbone a trigger has to be made.
So the app listens to the trigger getGame which is triggerd when the data from the server has been returned and saved into a variable in the model.
So far so good, this all works. The only problem now is that I want my div#game to fadeIn when it's done appending all data from the getGame function.
But I think because off the model.on(trigger) the initialize function 'thinks' it's ready after running the getGame() function, without actually having the data from the server appended yet.
So the parent div#all running the following:
this.$el.append(new Game().el);
also 'thinks' the div#game is ready and appends the div#game to itself and gives it a fadeIn.
div#game doesn't contain the server data yet, so when it actually does come back the images and text pop into existence instead of fadeIn in nicely...
Does some one know how to resolve this problem?
Thanks in advance!
======================================================
SOLVED
The problem was in the asynchronous property of the $.get and $.post functions. This is why the initialize was ready before the results had come back from the server. I changed it into a $.ajax and made the async: false. Now it all loads in the order I programmed/want.
Thank you for your detaild explaination Tallmaris, it will come in handy for sure since I'll be using a lot of triggers!
Rather than having the div#all render and append the view to itself, you can pass it as a parent to the View:
this.gameView = new Game({parent: this});
Then in your Game view:
initialize: function (options)
this.parent = options.parent
{
this.model.on('getGame', function() { this.$el.appendTo(this.parent); });
this.model.getGame();
}
Alternatively you could use a global Backbone event object like this:
window.vent = $.extend({}, Backbone.Events);
Now you have a global object (which you can name and put in any namespace as you like, don't just do it as in the code above), that you can use like this:
MAIN VIEW:
initialize: function () {
vent.on("GameReady", function ()
{
// fade in game view
});
}
GAME VIEW:
initialize: function (options)
this.parent = options.parent
{
this.model.on('getGame', function() { vent.trigger("GameReady"); });
this.model.getGame();
}
You can also pass parameters around. Read here for more documentation.
There's a new behaviour in the latest version of Backbone (1.0.0 in which the reset event is no longer triggered by default after fetching a Collection.
http://backbonejs.org/#changelog
Renamed Collection's "update" to set, for parallelism with the similar
model.set(), and contrast with reset. It's now the default updating
mechanism after a fetch. If you'd like to continue using "reset", pass
{reset: true}.
The problem is that I want to capture the event when the collection has been finally fetched (pretty common case, indeed!)
I could listen to add, remove and change event, but if the collection is empty I don't know how to catch the event.
So, what would be the new, recommended way to catch when the collection request has finalized, or is it passing a { reset = true } the only way to achieve it???
ps: here's the original question, BTW can't catch Backbone Collection reset event
From Backbone.sync doc,
Whenever a model or collection begins a sync with the server, a
"request" event is emitted. If the request completes successfully
you'll get a "sync" event, and an "error" event if not.
For example,
var C = Backbone.Collection.extend({
url: '/echo/json/'
});
var c = new C();
c.on('sync', function() {
console.log('sync');
});
c.fetch();
And a demo http://jsfiddle.net/nikoshr/GLATm/
We can pass a method as a success handler when we call fetch on collection and as you said you just want to do something when everything[add,remove,update or reset] has happened, you can do inside this success handler.
collection.fetch({
success: function() {
// Do Something
// This is called when all add, remove and update operations have been done
}
});
Note: success handler is always executed irrespective of you have passed reset:true or not. Irrespective of your collection gets empty or not and It will be called at the last step when all the add,remove and update events have occurred.
Let me know if it does not solve your problem.
My own solution is indeed pretty simple. I already have a BaseCollection with added features, so in there I just set as default { reset: true }. The code should be something like this (my own BaseCollection has a lot of stuff that is not pertinent here):
var BaseCollection = Backbone.Collection.extend({
fetch: function(options) {
options = options || {};
options.reset = (options.reset === undefined ? true : options.reset);
// just call super.fetch
return Backbone.Collection.fetch.call(this, options);
};
});
Using promises...
// you could use promises as well
// P.S.: pardon jquery promises :)
var C = Backbone.Collection.extend({
url: '/echo/json/'
});
var c = new C();
// c.fetch() returns jqXHR object that you can listen too
$.when( c.fetch() )
.done(successFn)
.fail(failFn)
.always(alwaysFn);
I have a number of backbone models, organized in collections and connected to their corresponding views and/or collections of views. Some of these models that do not belong to the same collection need to trigger an event which is of interest to another model (and maybe more than one).
The recommended way to deal with this is, I think, the "global event dispatcher/aggregator" as described here and other places.
However, I also happen to be using require.js, which seems to go against the idea of attaching the dispatcher/aggregator to the application's namespace object -- or am I wrong here?
So my question is: using require.js how can I have a number of different backbone models trigger an event that will be handled by another model?
A similar solution to what #Andreas proposed but without Backbone.Marionette (heavily inspired nonetheless, see the article linked in the question).
All you have to do is to define a module that creates a singleton of an event listener and require this object in the modules where you want to trigger an event or listen to this event.
Let's say you have app/channel.js defining your channel
define(['backbone', 'underscore'], function (Backbone, _) {
var channel = _.extend({}, Backbone.Events);
return channel;
});
You can then use it as a listener via
require(['app/channel'], function (channel) {
channel.on('app.event', function () {
console.log('app.event');
});
});
and you can trigger an event on this channel via
require(['app/channel'], function (channel) {
channel.trigger('app.event');
});
We using Marionettes app.vent (which is the global event transmitter for our application), allong with require js and it works really well.
app
define(, function(){
return new Backbone.Marionette.Application();
})
Model1
define(['app'], function(app){
return Backbone.Marionette.Model.extend({
initialize: function(){
this.bindTo('app.vent', 'create:model2', this.toSomething, this);
}
})
})
Model2
define(['app'], function(app){
return Backbone.Marionette.Model.extend({
initialize: function(){
app.vent.trigger('create:model2', this);
}
})
})
I would like to render a view for a model when the model is first fetched but not on every change.
My setup is as follows:
var m = new $.model.Customer({id: customer});
var v = new $.view.GeneralEditView({el: $("#general"), model: m});
m.fetch();
Then in the view initialize I bind the change event to the render method to render when the model is loaded:
this.model.bind('change', this.render);
The problem is that the view then renders on every change. I'd like to only render after the fetch. Unfortunately I'm not aware of any event that's fired after a fetch for a model other than change.
Is there something like 'reset' for collections that I can bind to?
EDIT:
Perhaps to put it more succinctly, for Backbone models is there a way to distinguish when a model is loaded from the server versus changed locally?
There are a bunch of different ways to approach this (these all assume var view = this; somewhere in your view code):
Call .fetch() with a one-time success callback:
m.fetch({
success: function() {
view.render();
}
});
Bind to change but unbind in the handler:
function handle() {
view.render();
view.model.off('change', handle);
}
this.model.bind('change', handle);
Use _.once to limit handler calls:
this.model.bind('change', _.once(function() {
view.render();
}));
Use a .ready() pattern for your models - example here. I like this option in cases where multiple views need to know when the model is loaded, and when you need to want to be able to write the same code without worrying about whether your model is loaded yet. The downside of this is that it requires you to add a model method like .isFullyLoaded() to test every time; the upside is that using a test function, rather than setting a flag, allows models to be loaded in bulk as part of a collection without having to change your code.
Models
You can make the change event specific to a certain key changing, such as the uniqueId (if you have one):
this.model.bind('change:id', this.render, this);
By default, fetch does not fire any event directly, but indirectly fires the change event once new data is loaded using set
If that is not an option, you can always trigger an event in your fetch function:
initialize: function () {
this.model.bind("fetch", this.update, this);
}
fetch: function () {
// do stuff
this.model.trigger("fetch", this);
}
update: function () {
// your refresh stuff here
}
I may have a general solution from https://github.com/documentcloud/backbone/pull/1468#issuecomment-6766096. I overwrote the sync method on Backbone as follows:
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 save the original success method as I don't want to mess that unless I need to, pass the custom success method. When the custom success method is invoked the custom event is triggered as suggested by #Austin and then the original success method in invoked.