Backbone render before fetch - javascript

I am using the backbone LayoutManager to manage my views.
I am having an issue fetching the model data before render is called, which obviously throws an error since the Ajax success callback is not done yet.
One way to fix this would be to fetch the model within the router and put app.useLayout("main").render(); into the success method. Is that the right way to do it or is there a better solution?
Router code :
app.useLayout("main").setViews({
".place-detail": new Place.Views.Show({
model: new Place.Model({ place_id: place_id })
})
});
app.useLayout("main").render();
View code :
Views.Show = Backbone.View.extend({
initialize: function(options) {
_.bindAll(this,"render");
this.model.on("change", this.render, this);
this.model.fetch();
});
});

Backbone's fetch accepts success and error callbacks, so if you want to wait for the fetching to finish before executing more code, put it in the success callback:
Views.Show = Backbone.View.extend({
initialize: function(options) {
var _this = this;
_.bindAll(this,"render");
this.model.fetch({
success: function(model, response) {
_this.render();
_this.model.on("change", _this.render, _this);
_this.someOutsideFunctionCall();
},
error: function(model, response) {
console.log("Error Fetching.");
}
});
};
});
I've been coding a lot in CoffeeScript so I'm not sure if I got all the ({});s correct, but that's the basic gist of it. Note the declaration of _this = this, since if you try to reference this inside the callback function, it won't know what you're talking about.

Related

Can not figure out how to modify fetch?

I need to add a authentication header, so far I have this but I am stuck here.
initialize: function(options) {
_.extend(this, _.pick(options, 'col'));
this.listenTo(this.model, 'change', this.render);
window.dispatcher.trigger('queryBox:close');
this.model.collectionName = this.col;
if (this.model.id) {
this.model.fetch();
} else {
this.renderNew();
this.delegateEvents();
}
},
Never used backbone before and I am clueless how I need to return the result so backbone can render it like the first one?
The result itself is correct, just need to pass it to backbone. In the first version it uses the default way to do it but can not figure it out how to do it manually.
fetch: function() {
var self = this;
Backbone.ajax({
url: self.url(),
headers: {'Authorization':jwt}
})
.done(function(res){
console.log('Help')
})
.fail(function(jqXHR, textStatus) {
tiedotApp.notify('danger', 'Error loading document: ' + jqXHR.responseText, 8000);
})
},
I will not suggest you to change the default implementation of fetch, cause its behavior can be simply configured with options hash. But if you need to let's clarify few things.
model.fetch uses jQuery.ajax() for interactions with servers and all options passed to fetch will be passed to $.ajax() as well (a few of them can be rewritten in the middleware, but not for your case).
model.fetch needs to return promise to use it for done and fail callbacks chain.
You could implement your task in a few ways.
1. If model fetched in one place:
Then you can pass change it like:
model.fetch({
headers: {'Authorization':jwt},
success: function () { consoele.log("Help") ;},
error: function(jqXHR, textStatus, errorThrown) {}
});
2. If model fetched in many places and you need to be DRY
Rewrite fetch in this way:
fetch: function(options) {
options = options || {};
options.headers = options.headers || {};
options.header['Authorization'}] = jwt;
options.success = yourSuccessFunction;
options.error = yourErrorFunction;
return this.constructor.__super__.fetch.call(this, options);
},
This way you will not break native implementation.

Not fetching correct url issue

I have a backboneJS app that has a router that looks
var StoreRouter = Backbone.Router.extend({
routes: {
'stores/add/' : 'add',
'stores/edit/:id': 'edit'
},
add: function(){
var addStoresView = new AddStoresView({
el: ".wrapper"
});
},
edit: function(id){
var editStoresView = new EditStoresView({
el: ".wrapper",
model: new Store({ id: id })
});
}
});
var storeRouter = new StoreRouter();
Backbone.history.start({ pushState: true, hashChange: false });
and a model that looks like:
var Store = Backbone.Model.extend({
urlRoot: "/stores/"
});
and then my view looks like:
var EditStoresView = Backbone.View.extend({
...
render: function() {
this.model.fetch({
success : function(model, response, options) {
this.$el.append ( JST['tmpl/' + "edit"] (model.toJSON()) );
}
});
}
I thought that urlRoot when fetched would call /stores/ID_HERE, but right now it doesn't call that, it just calls /stores/, but I'm not sure why and how to fix this?
In devTools, here is the url it's going for:
GET http://localhost/stores/
This might not be the answer since it depends on your real production code.
Normally the code you entered is supposed to work, and I even saw a comment saying that it works in a jsfiddle. A couple of reasons might affect the outcome:
In your code you changed the Backbone.Model.url() function. By default the url function is
url: function() {
var base =
_.result(this, 'urlRoot') ||
_.result(this.collection, 'url') ||
urlError();
if (this.isNew()) return base;
return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
},
This is the function to be used by Backbone to generate the URL for model.fetch();.
You added a custom idAttribute when you declared your Store Model to be like the one in your DB. For example your database has a different id than id itself, but in your code you still use new Model({ id: id }); when you really should use new Model({ customId: id });. What happens behind the scenes is that you see in the url() function it checks if the model isNew(). This function actually checks if the id is set, but if it is custom it checks for that:
isNew: function() {
return !this.has(this.idAttribute);
},
You messed up with Backbone.sync ... lots of things can be done with this I will not even start unless I want to make a paper on it. Maybe you followed a tutorial without knowing that it might affect some other code.
You called model.fetch() "a la" $.ajax style:
model.fetch({
data: objectHere,
url: yourUrlHere,
success: function () {},
error: function () {}
});
This overrides the awesomeness of the Backbone automation. (I think sync takes over from here, don't quote me on that).
Reference: Backbone annotated sourcecode

How can I set context of the error handler of Backbone model fetch?

I'm using a Router to organise my Backbone app. In my edit route I'm calling fetch on a model instance to get the model's data for the edit form:
App.Router = Backbone.Router.extend({
routes: {
"": "index",
"edit(/:id)": "edit"
},
index: function () {
_.bindAll(this, "getItemByID", "onModelFetchError", "showError");
},
edit: function (id) {
this.formEditItem = new App.Views.FormEditItem({model: this.getItemByID(id), parent: this});
},
getItemByID: function(id) {
var item = new App.Models.Item({id: id});
item.fetch({
success: function(model, response, options) {
console.log('Success');
return model;
},
error: this.onModelFetchError
})
},
onModelFetchError: function (model, response, options) {
this.showError(response.responseText);
},
showError: function(msg) {
this.error = new App.Views.Error({parent: this, message: msg});
}
});
The fetch call works fine, but I'm having trouble handling errors. I want to instantiate an Error view and display the message in it. But when I try this code, I get "Uncaught TypeError: Object [object global] has no method 'showError'". It seems that assigning onModelFetchError as the handler for fetch errors puts it in the global scope, even when I bind it to the Router with _.bindAll.
Is there any simple way to ensure onModelFetchError remains in the Router scope?
You are calling to _.bindAll() inside of the index function which is fired by the route "", so if you don't fire that route they'll never get bind to the context object. I would suggest to create an initialize method for your router so you can bind all the functions for any route inside of it.
initialize: function() {
_.bindAll(this, "getItemByID", "onModelFetchError", "showError");
}
Try with
getItemByID: function(id) {
var item = new App.Models.Item({id: id});
var self = this; // this changed
item.fetch({
success: function(model, response, options) {
console.log('Success');
return model;
},
error: self.onModelFetchError // this changed
})
},

Backbone Collection not getting models

Basically, I'm trying to send a GET request to my Node server, so that I can get back blog posts to create links. I do a collection.fetch, which successful completes the GET request (the Node server logs that it's sending the right objects). The model successfully parses the right data, but when I try to use the collection, it says that it's empty. Here's the code:
var mdm = mdm || {};
// MODEL
mdm.Post = Backbone.Model.extend({
parse: function( response ) {
response.id = response._id;
console.log(response); // logs the two documents
return response;
}
});
// COLLECTION
mdm.Posts = Backbone.Collection.extend({
model: mdm.Post,
url: '/api/posts'
});
// MODEL VIEW
mdm.LinkView = Backbone.View.extend({
template: _.template( $('#link_template').html() ),
render: function() {
this.$el.html( this.template( this.model.toJSON() ));
return this;
}
});
// COLLECTION VIEW
mdm.LinksView = Backbone.View.extend({
el: '#link_list',
initialize: function() {
this.collection = new mdm.Posts();
this.collection.fetch({reset: true});
// makes the request properly, but collection is empty
this.render();
// never gets called because the collection is empty
console.log(this.collection.length);
// logs a length of 0
},
render: function() {
// renders collection
}
});
$(function() {
new mdm.LinksView();
});
The data is being sent and is parsed in the models, so I'm not sure what the collection ends up being empty. Any help would be greatly appreciated.
The most likely reason you are not seeing the models in your view is because the render is happening before the asynchronous fetch is complete.
Something like below would work better:
mdm.LinksView = Backbone.View.extend({
el: '#link_list',
initialize: function() {
this.collection = new mdm.Posts();
this.listenTo(this.collection, 'reset', this.render);
this.collection.fetch({reset: true});
}
The above code sets a listener for the reset event on the collection and executes the render function when that happens.
Also, you could passing in success and error handlers into fetch and call the render function manually as well.
this.collection.fetch({
success: _.bind(function() {
this.render(); }, this)
});
Hope this helps!
Per #fbynite's comment, the problem was related to fetch being asynchronous. I made the following changes to the collection view, and it did the trick:
initialize: function() {
var self = this;
this.collection = new mdm.Posts();
this.collection.fetch({reset: true,
success: function() {
self.render();
console.log(self.collection.length);
}
});
},
The code is a modification from a Backbone tutorial, so other users may encounter a similar problem. http://addyosmani.github.io/backbone-fundamentals/#exercise-2-book-library---your-first-restful-backbone.js-app

Backbone Collection Not firing Add Event

So i have a problem whereby I have a backbone collection that am using to create function to save data to a REST API. The data is saved to the server and a model is added to the current collection but then the add event for the collection is not fired.Below are snippets of the code
The views initialize function
intialize : function() {
this.listenTo(this.collection, 'add', this.updateList);
},
The updateList function only does a console log.The views function that saves data using the collection is:
cards = this.collection;
debugger
cards.create(data, {
success : function(model, response) {
console.log("success on saving card");
console.log(response);
console.log("Updating list");
console.log(cards);
},
error : function(model, response) {
console.log("error on saving card");
console.log(model);
console.log("response");
console.log(response);
}
})
return false;
Try this code in your view:
initialize: function() {
this.collection.on('add', this.updateList, this);
}
Or:
var someCollection = new SomeCollection();
var view = new SomeView({collection: someCollection});
view.listenTo(someCollection, 'add', view.updateList);
why don't you just use: this.model.on('change', doAction, this); inside the view? if I understood correctly, this is a better solution since your model is changing.
plus, I couldn't find anywhere a place where the ListenTo() is mandatory over the On() function, can you tell me where you saw it?

Categories