Backbone: No data added to model on fetch() - javascript

I have problems when i try to fetch data to my Backbone model from the server. You get a response in JSON from from the server which I think looks to be right formatted. Can you see anything wrong with it? It looks like:
[{"id":"1","name":"Fawad Hassan","email":"fawad#test.com"},{"id":"2","name":"Bill
Gates","email":"bill#test.com"},{"id":"3","name":"Steve
Jobs","email":"steve#test.com"},{"id":"4","name":"Naveed
Ahmad","email":"naveed#test.com"},{"id":"5","name":"Mr Zee","email":"zee#test.com"}]
My code for the Backbone project looks like this, and I can't really find the problem there either.
window.AppModel = Backbone.Model.extend({
url: function() {
return 'http://dev.local/ci/index.php/site/userpost';
}
});
window.AppContr = Backbone.Collection.extend({
model: AppModel,
initialize: function() {
this.model = new AppModel;
}
});
window.App = new AppContr({name: "Markus"});
window.AppView = Backbone.View.extend({
el: $("#content"),
initialize: function() {
this.render();
},
render: function() {
console.log(App.model.toJSON());
}
});
App.model.fetch();
window.View = new AppView;

You are doing a fetch on a Model, but returning Collection in response. That is main problem.
Second problem is that you are calling render on AppView totally random, i.e. it does not have anything to do with model or collection. Maybe there would be nothing in model when you render view. You should bind rendering to collection or model with bind. Than whenever you call fetch your view will re-render, which is one of main benefits of Backbone :)
Here comes the code :)
window.Person = Backbone.Model.extend({});
window.Addressbook = Backbone.Collection.extend({
url: 'http://dev.local/ci/index.php/site/userpost',// declare url in collection
model: Person
}
window.Addresses = new AddressBook();
window.AppView = Backbone.View.extend({
el: $('#content'),
initialize: function() {
Addresses.bind('reset', this.render); // bind rendering to Addresses.fetch()
},
render: function(){
console.log(Addresses.toJSON());
}
});
window.appview = new AppView();
Addresses.fetch();

Related

each() function is not defined on this.collection

I am trying to make ajax request to server. I am using fetch() method on collection. It successfully retrieves data from server after monitoring google chrome network tab.
Here is scenario.
I have Model below
var Model = Backbone.Model.extend({
urlRoot: '/contacts',
});
collection below
var Collection = Backbone.Collection.extend({
model: Model,
url: '/contacts'
});
ViewOne below
var ViewOne = Backbone.View.extend({
initialize: function () {
var collection = new Collection().fetch(); // also tried with these options { wait:true, reset: true, async: false }
new ViewTwo({ collection: collection });
}
});
Below is ViewTwo
var ViewTwo = Backbone.View.extend({
initialize: function () {
this.collection.each(this.render, this); // Uncaught TypeError: undefined is not a function
},
render: function () {
console.log(this.model);
}
});
As you can see I am getting Uncaught TypeError: undefined is not a function error.
this.render exist so that is not a problem. The problem is each function on this.collection. How can I solve this proble?
simply create new view in success callback of fetch
var ViewOne = Backbone.View.extend({
initialize: function () {
var collection = new Collection();
collection.fetch({
success:function(){
new ViewTwo({ collection: collection });
}
});
}
});
Backbone API is not fluent, which means that Collection.fetch does not return itself, it returns the Ajax object used in fetch.
Try
var ViewOne = Backbone.View.extend({
initialize: function () {
var collection = new Collection();
collection.fetch();
new ViewTwo({ collection: collection });
}
});
And note that the collection.fetch is asynchronous, this.collection will be probably empty in ViewTwo.initialize. See Backbone.js - data not being populated in collection even though fetch is successful for example.

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 not populating a model with data using fetch()

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.

Scope error using Backbone.js?

I believe my problem relates to scope somehow, as I'm a js newbie. I have a tiny backbone.js example where all I am trying to do is print out a list of items fetched from the server.
$(function(){
// = Models =
// Video
window.Video = Backbone.Model.extend({
defaults: function() {
return {
title: 'No title',
description: 'No description'
};
},
urlRoot: 'api/v1/video/'
});
// VideoList Collection
// To be extended for Asset Manager and Search later...
window.VideoList = Backbone.Collection.extend({
model: Video,
url: 'api/v1/video/'
});
// = Views =
window.VideoListView = Backbone.View.extend({
tagName: 'ul',
render: function(eventName) {
$(this.el).html("");
_.each(this.model.models, function(video) {
$(this.el).append(new VideoListRowView({model:video}).render().el);
}, this);
return this;
}
});
// VideoRow
window.VideoListRowView = Backbone.View.extend({
tagName: "li",
template: _.template("id: <%= id %>; title: <%= title %>"),
className: "asset-video-row",
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
// Router
var AppRouter = Backbone.Router.extend({
routes:{
"":"assetManager"
},
assetManager:function() {
this.assetList = new VideoList();
this.assetListView = new VideoListView({model:this.assetList});
this.assetList.fetch();
$('#content').html(this.assetListView.render().el);
}
});
var app = new AppRouter();
Backbone.history.start();
// The following works fine:
window.mylist = new VideoList();
window.mylistview = new VideoListView({model:window.mylist});
});
If I access mylist.fetch(); mylist.toJSON() from the console, mylist populates fine. I can tell that this.assetList.fetch() is accurately fetching the data from the backend, but it doesn't appear to be adding the objects to this.assetList.
The fetch method on Backbone collections is asynchronous:
Fetch the default set of models for this collection from the server, resetting the collection when they arrive. [...] Delegates to Backbone.sync under the covers, for custom persistence strategies.
And Backbone.sync says:
Backbone.sync is the function that Backbone calls every time it attempts to read or save a model to the server. By default, it uses (jQuery/Zepto).ajax to make a RESTful JSON request.
So fetch involves an (asynchronous) AJAX call and that means that you're trying to use the collection before fetch has retrieved the data from the server. Note that fetch supports success and error callbacks so you can do this instead:
var self = this;
this.assetList.fetch({
success: function(collection, response) {
$('#content').html(self.assetListView.render().el);
}
});
Or you could bind a callback to the collection's reset event as fetch will reset the collection. Then render your assetListView when the collection's reset event is triggered.
Also, your assetList is a collection so you should be doing:
this.assetListView = new VideoListView({collection: this.assetList});
and:
window.VideoListView = Backbone.View.extend({
tagName: 'ul',
render: function(eventName) {
$(this.el).html("");
_.each(this.collection.models, function(video) {
// ...

How do I bind an event to a model that isn't loaded yet?

So I've got a pretty simple backbone app with a model, a collection, and a couple of views. I'm fetching the actual data from the server by doing a collection.fetch() at page load.
My problem is that one of my views is a "detail" view, and I want to bind it to a particular model - but I don't have the model yet when the page loads. My code looks a lot like this:
window.App = {
Models: {},
Collections: {},
Views: {},
Routers: {}
}
App.Models.Person = Backbone.Model.extend({
urlRoot: '/api/people'
});
App.Collections.People = Backbone.Collection.extend({
model: App.Models.Person,
url: '/api/people'
});
people = new App.Collections.People()
App.Views.List = Backbone.View.extend({
initialize: function() {
this.collection.bind('reset', this.render());
},
render: function() {
$(this.el).html("We've got " + this.collection.length + " models." )
}
});
listView = new App.Views.List({collection: people})
App.Views.Detail = Backbone.View.extend({
initialize: function() {
this.model.bind('change', this.render());
},
render: function() {
$(this.el).html("Model goes here!")
}
});
App.Routers.Main = Backbone.Router.extend({
routes: {
'/people': 'list',
'/people/:id': 'detail'
},
list: function() {
listView.render();
},
detail: function(id) {
detailView = new App.Views.Detail({model: people.get(id)})
detailView.render()
}
})
main = new App.Routers.Main();
Backbone.history.start();
people.fetch();
But if I start with the detail route active, the people collection is empty, so people.get(id) doesn't return anything, so my new view has this.model undefined, and won't let me bind any events relating to it. The error is:
Uncaught TypeError: Cannot call method 'bind' of undefined
If I start with the list route active, then by the time I click on an item to bring up the detail view people is populated, so everything works.
What's the right way to bind model-related events for a "detail" view when you're fetching the data after page load?
You have a part of the answer here: Backbone.js Collections not applying Models (using Code Igniter)
Indeed, you need to wait that people.fetch finishes its ajax request before to call Backbone.history.start(); and trigger the actual route.
Your code should look like:
// [...]
main = new App.Routers.Main();
peoples.fetch({
success: function (collection, response) {
// The collection is filled, trigger the route
Backbone.history.start();
}
});
You can add a loader on the page and hide it when the collection is loaded.

Categories

Resources