fetch triggers second time - backbone.js

the problem actually started, when template was loaded before fetch was complited. So I had to add
$.when(this.model.fetch()).done(function () {
that.$el.html(that.template({
model: that.model.toJSON()
}));
that.fjskdflsfk();
that.sdjfksfj();
});
inside render function.
and now I have to fetch data from another url in some cases( after initialization, so first load goes fine)
this.model.fetch({
url: getUrl() + changeableUrl,
success: function () {
console.log("success");
that.render();
},
error: function (model, response) {
showMessage("error", response.responseText);
}
});
Success trigers render, and initial fetch trigers in render, which is unwanted in this case.
Maybe someone has a solution?

You shouldn't call model.fetch from your view's 'render'. Instead, have your view listen to the model's sync event and react by rendering.
// in view:
initialize: function() {
this.listenTo(this.model, 'sync', this.render);
}
If this is the only trigger for calling the view's render, it won't be called until the model has data, and you won't get extra fetch calls because you can remove that $.when stuff from the view's render.

Related

Disconnect Reflux listenTo method

So, I have two stores. First pageStore serves business logic of specific page, and second globalStore logic of Android/iOS global events.
When user enters specific page React.componentDidMount calls
pageEntered: function () {
this.listenTo(globalStore, this.locationUpdated);
},
so from this my pageStore started to listen global storage for GPS updates. But is there any way to disconnect listenTo on React.componentWillUnmount ?
There's an example how to unsubscribe from a store listening (taken from the official examples):
var Status = React.createClass({
getInitialState: function() { },
onStatusChange: function(status) {
this.setState({
currentStatus: status
});
},
componentDidMount: function() {
this.unsubscribe = statusStore.listen(this.onStatusChange);
},
componentWillUnmount: function() {
this.unsubscribe();
},
render: function() {
// render specifics
}
});
Here's one way to think about what's happening in the example above:
var myFunc = function(){
console.log("This gets fired immediately");
var randomNumber = Math.ceil(Math.random()*10);
return function() {
return randomNumber;
}
}
var a = myFunc(); //Console log fires IMMEDIATELY, a is the returned function
a(); //some integer from 1 to 10
Since myFunc is invoked when we assign it to a variable, the console.log fires immediately-- it is like this.unsubscribe = statusStore.listen(this.onStatusChange); which "turns on" the listener immediately once componentDidMount happens.
In the componentDidMount lifecycle method, we are attaching a listener to using .listen. That's invoked. Just for convenience, we are assigning the result of the function to this.unsubscribe.
If you look at lines 60-68 of this gist (https://gist.github.com/spoike/ba561727a3f133b942dc#file-reflux-js-L60-L68) think of .listen returning a function that that removes the event listener.
In componentWillUnmount, we invoke this.unsubscribe which removes the listener. You can think of .listen as returning a function that removes the 'listener' and when the componentWillUnmount lifecycle happens we invoke that function and kill the listener.
Tl;dr: Imagine .listen attaches a listener AND returns a function that turns off the listener-- when you first invoke it the listener on and when you invoke the function that gets returned it turns off the listener

Backbone render after initialize - initialize get's triggered to early?

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.

Backbone model not having attribute after fetch

So I'm having a problem after I'm fetching a model and trying to render it to a template in this view below. I've searched around and found out that I have to do a _.bindAll etc, but still it doesn't work. Inside the first console.log, where I'm trying to get the User's name, it returns undefined. I tried putting this.render() (due to the async nature of fetch) in the success callback but it didn't work. When I inspect what console.log(data) yields, I do see the values I want, but it seems that nothing is being passed to the template.
define([
'jquery',
'underscore',
'backbone',
'models/UserModel',
'text!/assets/templates/dashboard.html'
], function($, _, Backbone, UserModel, dashboardTemplate) {
window.DashboardView = Backbone.View.extend({
el: $(".content"),
initialize: function() {
_.bindAll(this, 'render');
this.user = new UserModel();
this.user.fetch({
success: console.log(this.user.get("name")),
error: function(model, response) {
console.log("error fetching model");
console.log(model);
console.log(response);
}
});
},
render: function() {
console.log(this);
var data = {
user: this.user,
_: _
};
console.log(data);
var compiledTemplate = _.template(dashboardTemplate, data);
$(this.el).html(compiledTemplate);
}
});
return DashboardView;
});
Could anyone please shed some light?
Your first issue is that your console.log runs right away, not on success.
this.user.fetch({
success: console.log(this.user.get("name")),
// ...
means that you are calling log and then passing the return value of that as the success callback. You need to pass an anonymous function.
var view = this;
this.user.fetch({
success: function(){
console.log(view.user.get("name")),
view.render();
},
// ...
Second of all, when you render the template, you need to pass it the model's attributes, but currently you are passing the model itself. For this you can use toJSON to convert the model to a standard object.
var data = {
user: this.user.toJSON(),
// ...
You might want to check what the value of this is inside of your success callback, I doubt that it is the View as you expect which is why you are getting an undefined. Inside your template you can call console.log for extra debugging.
The main problem I see with your code is that _.template() returns a function not static content. Therefore you should be calling $(this.el).html(compiledTemplate());.
Passing data in the compiledTemplate setting will embed the data and make your template static. You generally should only pass your template code to _.template and then call the compiled function like so with the current data: compiledTemplate(this.user.toJSON());

Render a view for a model on first load, not on every change

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.

The best way to trigger an event when the model.fetch is complete

I am looking for the best way to trigger an event when the fetch is completed.
This code works but I am curious to know if there is a better way to accomplish the following task.
var myApp = new Marionette.Application();
myApp.on("initialize:before", function () {
this.currentUser = new UserModel();
this.currentUser.fetch({
complete: function () {
myApp.vent.trigger('currentUser');
}
});
});
A successful fetch triggers a "change" event:
fetch model.fetch([options])
[...] A "change" event will be triggered if the server's state differs from the current attributes.
So if the fetch does anything, there will be a "change" that you can listen for:
myApp.on("initialize:before", function () {
this.currentUser = new UserModel();
this.currentUser.on('change', function() {
myApp.vent.trigger('currentUser');
});
this.currentUser.fetch();
});
That will also trigger a "currentUser" event if this.currentUser is changed in some other way and that might or might not be what you want. If you only want your function called once then your current success handler is probably the easiest thing to do; you could still use on and unbind the handler when it is called:
myApp.on("initialize:before", function () {
var triggerCurrentUser = _.bind(function() {
myApp.vent.trigger('currentUser');
this.currentUser.off('change', triggerCurrentUser);
}, this);
this.currentUser = new UserModel();
this.currentUser.on('change', triggerCurrentUser);
this.currentUser.fetch();
});
You could also use _.once but that would leave a no-op callback function kicking around and there's no need for that.

Resources