Backbone js not populating a model with data using fetch() - javascript

I am using Backbone.js and trying to populate my model using fetch(). The problem I am having is that the returned data is not populating my model. I have found a similar question here. The difference is that inside of my success function I am not seeing any data changes nor is a 'change' event being fired.
The code:
Model
window.Company = Backbone.Model.extend({
urlRoot: "/api/company",
defaults:{
"id":null,
"name":"",
"address":"",
"city":"",
"state":"",
"phone":""
},
events: {
'change': 'doChange'
},
doChange: function(event) {
alert('company changed');
}
})
The Router
var AppRouter = Backbone.Router.extend({
routes:{
"":"home",
"company/:id":"companyDetails"
},
initialize:function () {
var user = new User();
this.headerView = new HeaderView({
model: user
});
$('.header').html(this.headerView.el);
console.log("router initialized.");
},
companyDetails: function (id) {
var company = new Company({
id: id
});
company.fetch({
success: function(){
console.log('company.id is ' + company.id);
console.log('company.name is ' + company.name);
console.log('company.address is ' + company.address);
$("#content").html(new CompanyView({
model: company
}).el);
}
});
}
});
JSON
{"address":"555 Main St","name":"Confused Technologies","id":"8dc206cc-1524-4623-a6cd-97c185a76392","state":"CO","city":"Denver","zip":"80206","phone":"5551212"}
The name and address are always undefined. I have to be overlooking something simple???
Edit
Including the view that erroneously left out passing the model to the template.
View
window.CompanyView = Backbone.View.extend({
initialize:function () {
this.render();
console.log('CompanyView initialized');
},
render:function (eventName) {
$(this.el).html(this.template());
return this;
}
})

The attributes are not stored directly on the model. They are stored in an attributes hash, so you would access them through company.attributes, though company.get(attribute) is the way it's usually done. Along the same lines, you would pass company.toJSON() to your template function, as that returns a cloned hash of the model's attributes.
As for your change event not firing, I assume you mean the change: doChange in the model's events hash. Backbone Models do not actually do anything with an events hash. That's for delegating DOM events on Backbone Views. I bet if you put company.on("change", function (model) { console.log(model.toJSON()); }) before your fetch call and removed the success callback, you'd see your model in the console.
Also, I don't think your $("#content").html... line is going to work like you expect. I'd rewrite your router callback like this:
companyDetails: function (id) {
var company = new CompanyView({
el: "#content",
model: new Company({ id: id })
});
// This line would be better in your view's initialize, replacing company with this.
company.listenTo(company.model, "change", company.render);
company.model.fetch();
}
CompanyView#render would typically pass this.model.toJSON() to a template function that returns html, and pass that to this.$el.html(). So something like this.$el.html(this.template(this.model.toJSON()));

OK. The problem with not updating my model was as far as I can tell an async issue. I updated the success callback to include the data parameter like so:
success: function (data) {
$('#content').html(new CompanyView({
model: data
}).el);
}
Note that I am not passing the company object as the model rather the raw returned data. This solved my model problem.
I mentioned in a comment that this started with my underscore template variables `<%= name %>' etc... being empty. I changed my view to this:
window.CompanyView = Backbone.View.extend({
initialize:function () {
this.render();
console.log('CompanyView initialized');
},
render:function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
})
Those to things got both my model updated and variables propagating to the template.

Related

Emulating socket event how do I update the relevant view with received model data?

I am trying to emulate multiple socket responses listening for each response in my view and updating the model, right now however I am managing to update each view with the same data. Can anyone advise what I would need to have in place in order to update the view relating to the data, right now I'm very confused about how this all works like should there be unique data in the response, should I check this in the view or the model etc?
Sample JS
function outputData(id, name) {
return {
id: id,
name: name
}
};
var View = Backbone.View.extend({
className: 'view',
template: Handlebars.compile( $('.tmpl-view').html() ),
initialize: function() {
this.listenTo(Backbone.Events, 'data:recieved', function(response) {
// Check if this model data is related to this view then set?
this.model.set(response);
this.render();
}.bind(this), this)
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var viewOne = new View({
model: new Backbone.Model()
});
var viewTwo = new View({
model: new Backbone.Model()
});
$('body').append(
viewOne.render().el,
viewTwo.render().el
);
Backbone.Events.trigger('data:recieved', outputData(1, 'Data for viewOne'));
setTimeout(function() {
Backbone.Events.trigger('data:recieved', outputData(2, 'Data for viewTwo'));
}, 400);
JS Fiddle
http://jsfiddle.net/9kf9qvdg/
I would take a slightly different approach. Your view should only listen to changes on the one model it is backed by. This way each view doesn't need to parse every socket message:
initialize: function() {
this.listenTo(this.model, 'change', this.render);
}
Instead you would have separate logic that handles updating your models appropriately when you receive data. This might look like:
function updateData(id, msg) {
var data = outputData(id, msg);
var modelToUpdate = collection.findWhere({id: data.id});
if(modelToUpdate) {
modelToUpdate.set(data);
}
}
Here is a fiddle showing the above in action: http://jsfiddle.net/xwmx64y3/

Model rendering inside view: toJSON is not a function

I have added a model attribut inside a view like so:
app.ActorView = Backbone.View.extend({
modelImg:'', ....
I'm skipping to the rendering part as everything else is ok:
render: function () {this.$el.html(this.template({
modImg: this.modelImg.toJSON(),
mod: this.model.toJSON(),
movies: this.collection.toJSON()}
Every model in the view (model, collection and modelimg) is correctly fetched in the rooter part of my project:
modActor.fetch().done(function () {
modActorMovies.fetch().done(function () {
modImgActor.fetch().done(function () {
var actorView = new app.ActorView({
modelImg: modImgActor,<--problematic model
model: modActor,
collection: modActorMovies
});
My modImgActor definition is the following:
app.ActorImg= Backbone.Model.extend({
url: "http://umovie.herokuapp.com/unsecure/actors/272994458/movies",
parse: function (response) {
return response.results[0];
}
});
The problem is when I use the toJson() function on the modelImg. There is the following error: this.modelImg.toJSON is not a function
Can it be how the model is defined with its url?
modImg is not a standard option for Backbone.View. So backbone will just ignore it.
You have to manually handle the custom properties that you pass along with the options.
So you're view definition should be
app.ActorView = Backbone.View.extend({
initialize: function(options){
this.modelImg = options.modelImg;
}
}):

How to write data received from the server after .fetch() in model and passed to template?

Good day! I'm new in backbone and i writing a simple little application based backbone + jquerymobile.
When I get data from the server, I need to properly transfer the data to the view, where they are passed to the template. Because the .fetch() asynchronous, i cant just pass in render my model. How can I do so that would after .fetch() data received from the server, written in the model and then passed to the template?
//template
<script type="text/html" class="template" id="profile-form">
<div data-role="header">
<h3>Step 4</h3>
Home
logout
</div>
<div data-role="content">
<h2 class="ui-li-heading"><%= username %></h2>
<p class="ui-li-desc"><strong><%= phone %></strong></p>
</div>
</script>
// model
var UserInfo = Backbone.Model.extend({
url: appConfig.baseURL + "users/",
});
// routes
profileInfo: function () {
var user = new UserInfo()
this.changePage(new ProfilePageView({ model: user }));
},
// view
var ProfilePageView = Backbone.View.extend({
initialize: function () {
this.template = $.tpl['profile-form'];
},
render: function (eventName) {
$(that.el).html(that.template());
return this;
}
});
I trying to add this part in my render in view. Its works, but my styles are not working.
I'm not quite sure that I did the right thing by putting fetch in render, can advise how to do correctly?
var that = this
this.model.fetch({
data: $.param({email: localStorage.getItem('user_email')}),
type: 'POST',
success: function (response) {
$(that.el).html(that.template(JSON.parse(JSON.stringify(response))));
}
});
Use the built-in events to decouple everything. Fetching is one step, updating is distinct. In your view do:
initialize: function () {
this.template = $('#profile-form').html();
this.listenToOnce(this.model, 'sync', function(){
this.render();
//this.listenTo(this.model, 'change', this.render, this);
}, this);
},
Every time the model has the set method called (and some attribute changes) it will trigger a change event. The listenTo method will run a callback when that happens. Another event you might want is sync which is called after a successful fetch. Sometimes you might need listenToOnce if you only want to render on the first sync and then control it yourself after that.
Your template probably needs its parameters passed in too:
render: function(){
$(this.el).html(_.template(this.template, this.model.toJSON()));
return this;
}
In terms of when to fetch, that's up to you a bit. You could fetch regularly or only when someone routes to that page. In the code you give I would do something like:
profileInfo: function () {
var user = new UserInfo();
user.fetch();
this.changePage(new ProfilePageView({ model: user }));
}

The sort-button does not work in Backbone.js

I have a JSON file, that I need to parse it into collection and render it to HTML pageand then I need to add a button, that will sort this collection and redraw it on page.
That the code, that I made:
That's the part about model, collection and sorting:
var Profile = Backbone.Model.extend();
var ProfileList = Backbone.Collection.extend({
model: Profile,
url: 'profiles.json',
selectedStrategy: "count",
comparator: function (property){
return selectedStrategy.apply(model.get(property));
},
strategies: {
count: function (model) {return model.get("count");},
name: function (model) {return model.get("name");}
},
changeSort: function (sortProperty) {
this.comparator = this.strategies[sortProperty];
},
initialize: function () {
this.changeSort("count");
},
});
It's the View and the Button:
var ProfileView = Backbone.View.extend({
el: "body",
template: _.template($('#profileTemplate').html()),
Sort: null,
initialize: function() {
this.Sort = new ReSortView();
this.bind('all', this.render());
},
render: function() {
_.each(this.model.models, function(profile){
var profileTemplate = this.template(profile.toJSON());
$(this.el).append(profileTemplate);
}, this);
return this;
},
ReSort: function (){
console.log("111");
this.model.changeSort("name");
},
events: {
"click .Sort": "ReSort",
//"click.NSort": "NSort"
},
});
var ReSortView = Backbone.View.extend({
el: $("#Sort")
});
var AppView = Backbone.View.extend({
el: "body",
initialize: function() {
var profiles = new ProfileList();
var profilesView = new ProfileView({
model: profiles
});
profiles.bind('all', function () {
profilesView.render();
});
profiles.fetch({success: function (model,resp) { console.log(resp);}});
}
});
var App = new AppView();
});
The question is why when I run it, everything seems to be ok, but the sorting does't work, and FireBug saying nothing and Button just writing into the consol.
P.S. I'm new in WEB developing and exactly in JS\Backbone.js
Just changing the comparator:
changeSort: function (sortProperty) {
this.comparator = this.strategies[sortProperty];
}
won't re-sort the collection, the collection has no way of know that the comparator has changed unless you tell it. You need to force the issue by calling sort:
changeSort: function (sortProperty) {
this.comparator = this.strategies[sortProperty];
this.sort();
}
And a few other things while I'm here:
Your initial comparator:
comparator: function (property){
return selectedStrategy.apply(model.get(property));
}
is invalid (unless you have a global selectedStrategy defined somewhere), you should probably just leave it out and let initialize set it up by calling changeSort.
this.bind('all', this.render()); does nothing useful, bind wants a function as the second argument but this.render() calls the render method. You probably don't want a this.bind call there at all and if you do, you'd want to say this.bind('all', this.render).
Views handle the collection option similarly to how the handle the model option in their constructor:
There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, tagName and attributes.
so, if your view is collection-based, you'd want to say new View({ collection: ... }) and use this.collection instead of this.model to avoid confusion.
Collections have various Underscore functions built-in so don't say:
_.each(this.model.models, ...
when you can say this instead:
this.collection.each(...
View's have a jQuery wrapped version of el built in so you can use this.$el instead of $(this.el) (which rebuilds the jQuery wrapper each time you call it).
You are calling the changeSort method on the model but that method is on your collection (as it should be)

Backbone.js render called before content is fetched

I found an example of some Backbone.js code that I then adopted to my needs.
The render function of CommentListView is called before any content is fetched. It seems that it not called again when there are content to render.
The backend returns two results, so that is not the problem.
// Models
window.Comment = Backbone.Model.extend();
window.CommentCollection = Backbone.Collection.extend({
model:Comment,
url:"/api/comments/cosmopolitan"
});
// Views
window.CommentListView = Backbone.View.extend({
tagName:'ul',
initialize:function () {
this.model.bind("reset", this.render, this);
},
render:function (eventName) {
console.log(this.model.models);
_.each(this.model.models, function (comment) {
console.log(comment);
$(this.el).append(new CommentListItemView({model:comment}).render().el);
}, this);
return this;
}
});
window.CommentListItemView = Backbone.View.extend({
tagName:"li",
template:_.template($('#tpl-comment-list-item').html()),
render:function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
// Router
var AppRouter = Backbone.Router.extend({
routes:{
"":"list"
},
list:function () {
this.commentList = new CommentCollection();
this.commentListView = new CommentListView({model:this.commentList});
this.commentList.fetch();
$('#sidebar').html(this.commentListView.render().el);
}
});
var app = new AppRouter();
Backbone.history.start();
The behavior of fetch has changed a bit in Backbone 1.0.0. From the 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}.
And Collection#fetch says:
fetch collection.fetch([options])
Fetch the default set of models for this collection from the server, setting them on the collection when they arrive. [...] When the model data returns from the server, it uses set to (intelligently) merge the fetched models, unless you pass {reset: true},
Your initialize just binds to "reset":
this.model.bind("reset", this.render, this);
You can either bind to the "add", "remove", and "change" events that Collection#set will generate or you can explicitly ask for a "reset" event when you fetch:
this.commentList.fetch({ reset: true });
A couple other things while I'm here:
Since your CommentListView view is using a collection rather than a model, you might want to call it collection:
this.commentListView = new CommentListView({collection: this.commentList});
and then refer to this.collection inside the view. See View#initialize for details on how view constructors handle their arguments.
Collections have various Underscore methods mixed in so you can say this.collection.each(function(model) { ... }) instead of _.each(this.model.models, ...).
Views maintain a cached version of the jQuery-wrapped el in $el so you can say this.$el instead of $(this.el).
Be careful with things like console.log(this.model.models). The console usually grabs a live reference so what shows up in the console will be the state of this.model.models when you look rather than when console.log is called. Using console.log(this.model.toJSON()) is more reliable when faced with timing and AJAX issues.
You might want to switch to listenTo instead of bind (AKA on) as that is less susceptible to memory leaks.
Usually is use to create a listener for the fetch, when fetch is complete and change the model or collection there is a callback. try this:
var AppRouter = Backbone.Router.extend({
routes:{
"":"list"
},
list:function () {
this.commentList = new CommentCollection();
this.commentListView = new CommentListView({model:this.commentList});
this.listenTo(this.commentList,'change', this.makeRender);
this.commentList.fetch();
},
makeRender: function(){
$('#sidebar').html(this.commentListView.render().el);
}
});

Categories

Resources