Backbone Collection not getting models - javascript

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

Related

How to render a collection with a Marionette's ItemView?

I'm trying to render the response from an API (JSON) with Backbone.Marionette.ItemView. Not sure why it is not working.
I'm using marionette v2.4.7 (on purpose);
Here is the handlebars template:
<script id="feed-post" type="text/x-handlebars-template">
{{#each posts}}
<img src="{{author.image_url}}" alt="">
<p>{{author.name}}</p>
<span>TODO TIMESTAMP</span>
<p>{{body}}</br>{{topic_type}}</p>
{{/each}}
</script>
Here is my full app.js (all Backbone logic in this file);
// Model
var Post = Backbone.Model.extend({
defaults: {
authorPic: 'Unknown',
authorName: 'Unknown',
timestamp: 'Unknown',
body: 'Not available',
comments: '0'
}
});
// Collection
var Posts = Backbone.Collection.extend({
model: Post,
url: 'http://localhost:4321/blogposts',
initialize: function(){
this.fetch();
}
});
// View
var PostView = Marionette.ItemView.extend({
el: '#content',
template: Handlebars.compile($("#feed-post").html()),
});
//Config
var chunkPosts = new Posts();
var myview = new PostView({collection: chunkPosts});
Also, I tried to console.log the view and it looks like the models are in there.
This answer is tailored to Marionette v2.4.7. LayoutView and ItemView were merged and renamed to View back in v3.0.0.
From the doc on ItemView:
Rendering this view will convert the someCollection collection in to
the items array for your template to use.
You are using posts in your template while the doc says it will be called items.
As a reference, here's the exact code doing that in the ItemView source:
// Serialize the model or collection for the view. If a model is
// found, the view's `serializeModel` is called. If a collection is found,
// each model in the collection is serialized by calling
// the view's `serializeCollection` and put into an `items` array in
// the resulting data. If both are found, defaults to the model.
// You can override the `serializeData` method in your own view definition,
// to provide custom serialization for your view's data.
serializeData: function() {
if (!this.model && !this.collection) {
return {};
}
var args = [this.model || this.collection];
if (arguments.length) {
args.push.apply(args, arguments);
}
if (this.model) {
return this.serializeModel.apply(this, args);
} else {
return {
items: this.serializeCollection.apply(this, args)
};
}
},
The last lines show that for a collection, a new object with items as the only attribute is returned.
It's mentioned that you can override the serializeData function, more information and examples are available in the doc.
You still need to call render on the view and since the collection's fetch is async, you won't have items out of the box so you should wire a listener.
First, don't fetch in the initialize of a collection, it makes the collection pretty much useless for any other use-case.
var Posts = Backbone.Collection.extend({
model: Post,
url: 'http://localhost:4321/blogposts',
});
Listen for the collection sync event, then fetch within the view instead.
var PostView = Marionette.ItemView.extend({
el: '#content',
template: Handlebars.compile($('#feed-post').html()),
initialize: function () {
this.listenTo(this.collection, 'sync', this.render);
this.collection.fetch();
},
});
Marionette even offers collectionEvents:
var PostView = Marionette.ItemView.extend({
// ...snip...
collectionEvents: {
"sync": "render"
}
// ...snip...
});

Backbone Model not passing to view

I am writing a new backbone application, and I am getting some errors on code that I have working in other applications. I am using the newest version of backbone for this application, so I do not know if there have been changes that make what I am doing here break, or what. Google has not been any help to me all day.
I have a collection that I am breaking apart, and sending the individual models to views. The views report that this.model is undefined.
Code:
The view:
DigitalAnalytics.View.TopPage = Backbone.Model.extend({
tagName: 'tr',
className: 'db-toppage-tr',
template: DigitalAnalytics.Helper.template('db-analytics-toppages-template'),
initialize: function() {
console.log(this.model); // prints 'undefined'
},
render: function() {
console.log(this.model); // prints 'undefined'
// This throws an error saying that it can not call html() on undefined
this.$el.html(this.template( this.model.toJSON()) );
return this;
},
events: {}
});
The collection view:
DigitalAnalytics.View.Collection.TopPages = Backbone.View.extend({
el: $('#db-analytics-toppages'),
template: DigitalAnalytics.Helper.template(''),
initialize: function() {
this.listenTo(this.collection, 'reset', this.render); // Never fires even though the collection is returned
this.collection.fetch(); // Works just fine
},
render: function() {
this.collection.each( function(page, index) {
console.log(page.toJSON()); // writes the object to console just fine
var topPage = new DigitalAnalytics.View.TopPage({model: page});
this.$el.append( topPage.render().el );
}, this);
}
});
You probably have extended Backbone.Model where you should have extended Backbone.View
Change Backbone.Model.extend in your code to Backbone.View.extend, create a Backbone model using Backbone.Model then pass this model to view when you initiate it.

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?

Backbone reset a collection on fetch

This is my problem:
I have a container view that holds a collection.
On page load I get some models, populate this collection with them, then render the models
I fire and event
When this event fires, I want to make a call to my api (which returns models based on input parameters)
I then want to remove all existing models from the collection, repopulate with my new models, and then render the models
This is how I set up my model/collection/view
var someModel = Backbone.Model.extend({});
var someCollection = Backbone.Collection.extend({
model: someModel,
url: "api/someapi"
});
var someView = Backbone.View.extend({
events: {
"click #refresh": "refreshCollection"
},
initialize: function () {
this.collection.bind("reset", this.render, this);
},
render: function () {
// render stuff
},
refreshCollection: function (e) {
this.collection.fetch({data: {someParam: someValue});
this.render();
}
});
var app = function (models) {
this.start = function () {
this.models = new someCollection();
this.view = new someView({collection: this.models});
this.view.reset(models);
};
};
My point of interest is here:
refreshCollection: function (e) {
this.collection.fetch({data: {someParam: someValue});
this.render();
}
I pass in some paramaters, and my api returns a json array of models. I want to get rid of all existing models in the collection, and put all of my returned models into the collection, then update the view (with render())
I understand this is possible with collection.set, or collection.reset. Both of these take in an array of models. I don't have an array of models to pass in.
I tried:
this.collection.fetch({
data: {someParam: someValue},
success: function (response) {
doSomethingWith(response.models)
}
});
But I don't know what to do with the models when I get them.
Any pushed in the right direction would be appreciated!
From the fine manual:
fetch collection.fetch([options])
[...] When the model data returns from the server, it uses set to (intelligently) merge the fetched models, unless you pass {reset: true}, in which case the collection will be (efficiently) reset.
So you just need to include reset: true in the options and fetch will call reset to replace the collection's contents with the fetched models:
this.collection.fetch({
data: { ... },
reset: true
});

Backbone returning length of 0

I'm fairly new to backbone and I'm trying to build a simple app. This is what I have so far
var Election = Backbone.Model.extend();
var Elections = Backbone.Collection.extend({
model: Election,
url: '/assets/data.json',
initialize: function() {
console.log('init col');
this.render();
return this;
},
render: function() {
console.log('rendering the collection');
return this;
},
// return this
});
var router = Backbone.Router.extend({
routes: {
'': 'root'
},
root: function(){
var collection = new Elections();
collection.fetch();
console.log(collection.length); //returns 0
}
});
var r = new router();
Backbone.history.start();
The log is this
> init col
> rendering the collection
> 0
But when I create a new collection manually in the console, it shows the right length and everything, I assume that for some reason the router call is happening too early, not sure though. This is a sample of data.json
[
{
"year": 1868,
...
},
{
"year": 1872,
...
},
fetch performs an asynchronous HTTP (Ajax) request, so you should pass fetch a success callback:
collection.fetch({
success: function(){
console.log(collection.length);
}
});
expanding on CD's answer a little bit,
a better approach would be calling fetch and then using listenTo to call the render method on change
in your initialize method do this
_.bindAll(this, 'render');
this.listenTo(this, 'change', this.render);
and you can have the fetch outside if you wish
collection.fetch()
and it will automatically update on change

Categories