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();
});
Related
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();
});
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
}});
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.
I have a backbone collection. I fetch. The server comes back with a JSON. How do I populate the collection witht he fresh data? Here's my code:
var Todos = Backbone.Collection.extend({
url: "server/todos-service.php", // this does what it is supposed to do so no worries!
model: Todo,
initialize: function(attributes, options) {
// IN HERE WHAT DO I HAVE TO DO?
// WHAT EVENT SHALL I BIND TO TO REACT THE DATA DELIVERY?
// AND WHAT SHALL I DO NEXT TO POPULATE THE DAMN THINGS
}
});
// CLIENT CODE
new Todos().fetch();
Can anyone tell me how this is supposed to be done?
Cheers
The fetch will start the ajax call to your server. When the data is returned it is automatically put the data into your collection, and the collection will fire a reset being like "I'm done fetching data bro, you can use it now."
You would usually call fetch() from inside your view, and have the view listen to the reset event. When the reset event is trigger, the view renders the collection.
var Todo = Backbone.Model.extend({
// my model
});
var TodoList = Backbone.Collection.extend({
url: myurl
model: Todo
// my collection (usually nothing in the initialize function)
});
var AppView = Backbone.View.extend({
initialize: function() {
// instantiate the collection
this.collection = new TodoList();
//listen to the reset event on the collection. When the reset event trigger, call the render function.
this.collection.on('reset', this.render, this);
// get the data from the backend
this.collection.fetch();
},
render: function() {
// render the collection like a boss
}
});
//instantiate the view
var App = new AppView;
Also as a resource, I found this tutorial to be really helpful in understanding the basics of backbone http://net.tutsplus.com/sessions/build-a-contacts-manager-using-backbone-js/
I have the following problem…
MyView which is connected to two views: TaskModel and UserModel
TaskModel = {id: 1, taskName: "myTask", creatorName: "myName", creator_id: 2 },
UserModel = {id: 2, avatar: "someAvatar"}
The view should display
{{taskName}}, {{creatorName}}, {{someAvatar}}
As you can see the fetch of TaskModel and UserModel should be synchronized, because the userModel.fetch needs of taskModel.get("creator_id")
Which approach do you recommend me to display/handle the view and the two models?
You could make the view smart enough to not render until it has everything it needs.
Suppose you have a user and a task and you pass them both to the view's constructor:
initialize: function(user, task) {
_.bindAll(this, 'render');
this.user = user;
this.task = task;
this.user.on('change', this.render);
this.task.on('change', this.render);
}
Now you have a view that has references to both the user and the task and is listening for "change" events on both. Then, the render method can ask the models if they have everything they're supposed to have, for example:
render: function() {
if(this.user.has('name')
&& this.task.has('name')) {
this.$el.append(this.template({
task: this.task.toJSON(),
user: this.user.toJSON()
}));
}
return this;
}
So render will wait until both the this.user and this.task are fully loaded before it fills in the proper HTML; if it is called before its models have been loaded, then it renders nothing and returns an empty placeholder. This approach keeps all of the view's logic nicely hidden away inside the view where it belongs and it easily generalizes.
Demo: http://jsfiddle.net/ambiguous/rreu5jd8/
You could also use Underscore's isEmpty (which is mixed into Backbone models) instead of checking a specific property:
render: function() {
if(!this.user.isEmpty()
&& !this.task.isEmpty()) {
this.$el.append(this.template({
task: this.task.toJSON(),
user: this.user.toJSON()
}));
}
return this;
}
That assumes that you don't have any defaults of course.
Demo: http://jsfiddle.net/ambiguous/4q07budc/
jQuery's Deferreds work well here. As a crude example:
var succesFunction = function () {
console.log('success');
};
var errorFunction = function () {
console.log('error');
};
$.when(taskModel.fetch(), userModel.fetch()).then(successFunction, errorFunction);
You could also pipe the request through using the crude data (remember that fetch, save, create are really just wrappers around jQuery's $.ajax object.
var taskModelDeferred = taskModel.fetch();
var userModelDeferred = taskModelDeferred.pipe(function( data ) {
return userModel.fetch({ data: { user: data.userId }});
});
note: Backbone returns the collection and model in the success / error functions by default on collections and models so if you need this be sure have a reference handy.
I've run into this very same issue with a complex layout that used two models and multiple views. For that, instead of trying to synchronize the fetches, I simply used the "success" function of one model to invoke the fetch of the other. My views would listen only to the change of the second model. For instance:
var model1 = Backbone.Model.extend({
...
});
var model2 = Backbone.Model.extend({
...
});
var view1 = Backbone.View.extend({
...
});
var view2 = Backbone.View.extend({
...
});
model2.on("change",view1.render, view1);
model2.on("change",view2.render, view2);
Then...
model1.fetch({
success : function() {
model2.fetch();
}
});
The point to this is you don't have to do any sophisticated synchronization. You simply cascade the fetches and respond to the last model's fetch.