Backbone model doesn't fetch response instant but renders view first - backbone.js

I've model with a parse function
var File = Backbone.Model.extend({
...
parse: function(response) {
console.log('Parsing response')
}
});
var FileView = Backbone.View.extend({
...
initialize: function(id) {
this.file = new File({id: id.id});
console.log('Fetching object')
this.file.fetch();
this.render();
},
render: function() {
console.log('Rendering view');
this.$el.html(this.template(this.file.JSON()));
}
})
The expected result would be:
Fetching object
Parsing response
Rendering view
But this is what I got:
Fetching object
Rendering view
Parsing response
Why is this? According to the docs:
parse is called by Backbone whenever a collection's models are
returned by the server, in fetch.
Why isn't the parse function called directly after fetching the model? How force it to do this?

Yeah, but you're making an async call to the server to fetch the model, so render is actually being called before you get answer. You have to do, something like this:
this.file.fetch({success: function () {
//call render here
}});

Related

fetch collection using rest api in backbone

In that rest api url, i am getting an json array and have fetch it with EmployeeList collection. using the fetch() only call the rest api. If I didnt use the fetch, the rest api call doesnt work, it tested using the log in api-code. while fetching I get all the details, but I am getting the error,
Uncaught TypeError: this.model is not a constructor backbone-min.js:24
I am new to the backbonejs. Whats the error, why this error will happens. My code is below,
var app = {};
app.Employee = Backbone.Model.extend();
app.employee = new app.Employee();
app.EmployeeList = Backbone.Collection.extend({
model: app.employee,
url:'/api/employ',
parse: function(response) {
return response;
}
})
app.employeeList = new app.EmployeeList();
app.employeeList.fetch();
app.AppView = Backbone.View.extend({
el: '#emp',
initialize: function(){
this.render();
console.log(app.employeeList);
},
render: function(){
this.$el.html('sathish');
}
});
app.appView = new app.AppView();
Change
app.EmployeeList = Backbone.Collection.extend({
model: app.employee,
.....
})
To
app.EmployeeList = Backbone.Collection.extend({
model: app.Employee,
...
})
the model param should be a model class(a constructor function in js) like app.Employee.
You should provide app.Employee() instead of new app.Employee() as a model parameter of the collection.

Add model to collection after fetching it

Im having trouble figuring out how to populate a model's attributes from the server and then add the populated model to a collection and have that collection rendered by a view. Here's the code I have:
var movieDetails = new cinephile.Models.MovieDetailsModel({ id: movie.get('id') });
this.collection.add(movieDetails);
Inside of the MovieDetailsModel:
cinephile.Models.MovieDetailsModel = Backbone.Model.extend({
url: function()
{
return '/cinephile/api/index.php?action=getMovieDetails&movieId=' + this.id;
},
initialize: function()
{
this.fetch();
}
});
And this.collection is just a collection with the model set to be a cinephile.Models.MovieDetailsModel
I am listening for items to be added to the collection and when they are, the following is executed:
displayMovie: function(movie)
{
var view = new cinephile.Views.MovieView({
model: movie,
className: 'movie clearfix',
template: JST['app/scripts/templates/MovieView.ejs'],
});
this.$("#my-movies").append(view.el);
},
MovieView looks like this:
cinephile.Views.MovieView = Backbone.View.extend({
initialize: function(options)
{
this.template = options.template;
this.render();
},
render : function()
{
this.$el.html(this.template(this.model.attributes));
return this;
},
});
The problem I have is that the template I'm using is trying to access an attribute of the model that is undefined. Im pretty sure it's undefined because the MoveDetailsModel hasn't finished fetching before the model is added to the collection and subsequently rendered to the view.
How can I solve this issue? I'd like to be able to create a MovieDetailsModel that takes in an id, use that id to get the movie details from the server and then add the populated model to a collection and then render that collection to the screen.
Any help is appreciated.
Backbone fetch returns a jqXHR object, which is a Deferred objects Promise.
When fetch is called, the attributes are not populated yet. Promise objects have a don
ejqXHR function, where a callback can be passed to be executed once the request is done.
I would recommend moving the fetch into another method not the constructor, because there You can return the jqXHR object and access its done function.
Here is an example:
var movieDetails = new cinephile.Models.MovieDetailsModel({ id: movie.get('id') });
var promise = movieDetails.fetch();
promise.done(function() {
var view = new cinephile.Views.MovieView({model: movieDetails});
view.render();
});

How to retrieve an object from Parse.com after saving it

I'm creating an application for Phonegap using Backbone framework and Parse.com as backend service. I create an object with Parse.com (corresponding to Backbone models).
This object has a saveDraftToP() method that calls the Parse.com function save().
After this method is called from a view, I'd like to retrieve the updated object.
To do so I'm binding the 'change' event to the model but the Parse assigned ID is undefined.
Here is the code of the model:
var Match = Parse.Object.extend("Match", {
states: {'DRAFT': 0, 'RUNNING': 1, 'ENDED': 2},
saveDraftToP: function () {
var self = this;
this.save({
user: Parse.User.current(),
ACL: new Parse.ACL(Parse.User.current()),
state: self.states.DRAFT
}, {
success: function (result) {
self = result;
},
error: function (e) {
}
});
}
});`
And here is the code for the view:
var vmNuovaPartita = Parse.View.extend({
template: Handlebars.compile(template),
model: new Match(),
collection: new HintCollection(),
initialize: function () {
this.bind("change:model", console.log(this.model.id) , this);
},
render: function (eventName) {
var match = this.model.toJSON();
$(this.el).html(this.template(match));
return this;
}
});
I'm not quite sure why you have a save function wrapped in another save-like function. :-)
Say you have something like myMatch which is an object.
Through your UI, a button click saves the object data. You can just use myMatch.save({attr:val, ...}) straight out of the box. Backbone (and Parse) by default are optimistic. That means, you it will set the values of the model with the expectation that persisting to the server will succeed.
Thus, you don't need to retrieve anything extra. You already have the model in it's most current state.
To have a model view that responds to these changes, I'd design the view a little differently.
var vmNuovaPartita = Parse.View.extend({
template: Handlebars.compile(template),
initialize: function () {
this.model.on('change', this.render);
},
render: function (eventName) {
var match = this.model.toJSON();
$(this.el).html(this.template(match));
return this;
}
});
var myView = new vmNuovaPartita({
model: myModel
});
I'd initialize the model outside of the view, then pass it in as an option when you generate a new view. When you pass a model in as an option, it's special and will be attached directly to the view ... view.model which you can refer inside your view code as this.model
In the init we place a listener on the model for change events, then fire off a rerender of the view. Or a nicer way to go about this sort of thing is to throw in the newer Backbone Events with the .listenTo() method.

Can't access attributes model backbone js

I am creating my "Hello world" app in backbone js. I am stuck at the very basic.
var gs = {
documentRoot: ""
}; // create namespace for our app
gs.Test = Backbone.Model.extend({
url: gs.documentRoot+'/test.php',
initialize: function(){
this.fetch();
}
});
gs.TestView = Backbone.View.extend({
render: function(){
console.log(this.model);
console.log(this.model.get('testId'));
}
});
var testM = new gs.Test();
var test = new gs.TestView({model: testM});
test.render();
Here when I log model in the console, it shows fetched attributes from the server but I can't access those attributes from test.get('attribute'). I tried logging test.attributes, it gives empty object but when I log test, it shows those attributes in attributes object.
model#fetch method has a success and error callback options that can be passed to fetch. The success callback gets called when the response from the server has come.
Right way to test the fetched attributes of a model is
test.fetch({
success: function(model){
// model here and test are same
console.log(model);
console.log(test.toJSON());
// access your attribute with name `attributeName`
console.log(test.get('attributeName'));
}
});
fetch is async method, so you have to wait some time.
The best solution in this case is promises:
test.fetch().done(function() {
console.log(test);
});
Your updated model:
initialize: function() {
// save link to promise
this.deferred = this.fetch();
}
And your render function:
render: function() {
// use promise to render view after model will be fetched
// use `bind` to save context of this view
this.model.deferred.done(_.bind(function () {
// model is fetched
// all operations goes here
console.log(this.model.get('testId')); // <- proper value
}, this));
console.log(this.model.get('testId')); // <- undefined
}
More about ajax you can read here http://api.jquery.com/jQuery.ajax
var TestModel = Backbone.Model.extend({
url : '/test.php'
});
var test = new TestModel();
// `context` context to be passed to any callback function
test.fetch({context:test}).done(function () {
// `this` is equals to `test` (`context` option)
// In case if you want to get all model data:
// the best way to get model data for read-only mode.
// this metod return a copy of the model's attributes
console.log(this.toJSON());
// you can also use `this.attributes` but this is not recommended
console.log(this.attributes());
// In case if you want to get some model data:
console.log(this.get('some_attribute'));
// If you want to get `c` from this model ({a:{b:{c:1}}}):
console.log(this.get('a').b.c);
});
For those who are stuck with the same problem, here is the solution from the library itself.
Use model's in-built 'sync' event to get the model attributes after fetch()/save() calls.
testM.on('sync',function(){
test.render();
});

Backbone.js fetch unknown error

I'm facing an issue with my backbone.js app: I'm trying to fetch data from a JSON webservice, the GET HTTP request is successfull (i had a look in the developer console of chrome) but backbone fetch trigger an error and doesn't update the model.
You can have a look here to the code:
https://github.com/tdurand/faq-app-client-mobile
And you can run the app and try to debug here:
http://tdurand.github.com/faq-app-client-mobile/
The JSON Feed is like this
[
{
"title":"Probleme ou Bug",
"desc":"Pour les problemes ou les bugs rencontrés",
"entries":[
{
"title":"testdqs",
"desc":"testqsdqs"
}
]
}
]
My collection model is:
var Categories = Backbone.Collection.extend({
url:"http://cryptic-eyrie-7716.herokuapp.com/faq/fr",
model:Category,
parse:function(response) {
console.log("test")
console.log(response);
return response;
},
sync: function(method, model, options) {
var params = _.extend({
type: 'GET',
dataType: 'jsonp',
url: model.url,
processData:false
}, options);
return $.ajax(params);
},
});
And my view:
var IndexView = Backbone.View.extend({
el: '#index',
initialize:function() {
Categories.fetch({
success: function(m,r){
console.log("success");
console.log(r); // => 2 (collection have been populated)
},
error: function(m,r) {
console.log("error");
console.log(r.responseText);
}
});
Categories.on( 'all', this.render, this );
},
//render the content into div of view
render: function(){
//this.el is the root element of Backbone.View. By default, it is a div.
//$el is cached jQuery object for the view's element.
//append the compiled template into view div container
this.$el.html(_.template(indexViewTemplate,{categories:Categories}));
//Trigger jquerymobile rendering
$("#index").trigger('pagecreate');
//return to enable chained calls
return this;
}
});
return IndexView;
Thanks a lot for your help
From what I see, you're not making an instance of your collection anywhere. Javascript isn't really object oriented and it has this weird functions-as-classes and prototype -inheritance type of deal that can confuse anyone coming from the OO -world.
var Categories = Backbone.Collection.extend...
What happens above is that you extend the Backbone Collection's (which is a function) prototype properties with the object (or dictionary) you define. To be able to instantiate this 'class' to an object, you need to use the new keyword. You don't do this, so you are calling the function fetch of the 'class' Categories, which will produce unexpected results.
So instantiate your collection before fetching:
initialize:function() {
this.collection = new Categories(); // instantiate the collection
// also set event handlers before producing events
this.collection.on( 'all', this.render, this );
this.collection.fetch({
success: function(m,r){
console.log("success");
console.log(r); // => 2 (collection have been populated)
},
error: function(m,r) {
console.log("error");
console.log(r.responseText);
}
});
}
Hope this helps!
I found my mistake.
My backend server (with play! framework), was not rendering JSONP properly
This is the code i now use to do it if someone has the same issue.
//Render JSONP
if (request.params._contains("callback")) {
Gson gson = new Gson();
String out = gson.toJson(categoryJSON(categories,lang));
renderText(request.params.get("callback") + "(" + out + ")");
} else {
renderJSON(categoryJSON(categories,lang));
}

Resources