Marionette.js collection is undefined in collection view - javascript

I am fairly new with Marionette.js and seem to be having some trouble rendering a Collection View.
I am receiving the following console error message when trying to show the view: Uncaught TypeError: Cannot read property 'toJSON' of undefined.
It seems that the collection is not binding to the child view. The instance of the Collection View looks OK, meaning I see the childView property and the collection property with the fetched models. The docs seem pretty straightforward on how to construct a collection view so not sure what I'm missing. Thanks.
Child view:
var UserProfile = Marionette.ItemView.extend({
template: '',
initialize: function() {
this.template = Marionette.TemplateCache.get("#userprofile");
},
render: function() {
console.log(this.collection); //undefined
var data = this.collection.toJSON(); //error message here
this.$el.html(this.template({data:data}));
}
});
//Collection view:
var UserView = Marionette.CollectionView.extend({
childView: UserProfile
});
// Fetch collection and show:
var myQuery = new Parse.Query(app.Models.User);
myQuery.limit(200).containedIn(option, uiArray);
var Contacts = Parse.Collection.extend({
model: app.models.User,
query: myQuery
});
var contacts = new Contacts();
contacts.fetch().then(function(contacts) {
var userview = new UserView({collection:contacts});
app.regions.get("content").show(userview);
})

You are asking a "collection" in an ItemView. ItemView operates with an item. So it make sense to write something like this:
render: function() {
console.log(this.model); //undefined
var data = this.model.toJSON(); //error message here
this.$el.html(this.template({data:data}));
}
});

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 view/template fails to load from REST

I have properly coded a simple REST api and several backbone models. My parent model is called Topic and child model called Questions.
I'm trying to call a get method on the REST api and display the received Topic object to the user in a presentable manner. I am receiving the json (can be seen in the network tab on Chrome), but it is not getting sent to the view correctly.
Model:
var Topic = Backbone.Model.extend({
urlRoot: ROOT + '/topic',
idAttribute: 'topicId',
initialize: function () {
this.questions = new Questions([], {parent: this});
},
toJSON: function () {
var json = Backbone.Model.prototype.toJSON.call(this);
json.questions = this.questions.toJSON();
return json;
}
});
var Topics = Backbone.Collection.extend({
model: Topic,
url: ROOT + 'topic',
parse: function (response) {
return response.results;
}
})
REST URL:
http://localhost/Project/index.php/rest/resource/topic/
Backbone View: This is where I think the error is...(console log below prints an empty object)
var TopicListView = Backbone.View.extend({
el: '.page',
render: function () {
var that = this;
var topics = new Topics();
topics.fetch({
success: function (topics) {
console.log(topics);
var template = _.template($('#topic-list-template').html(), {topics: topics.models});
that.$el.html(template);
}
})
}
});
Using the above functions:
var topic = new Topic();
topic.fetch();
topicListView = new TopicListView();
var Router = Backbone.Router.extend({
routes: {
"": "home"
}
});
var router = new Router;
// render topic list for 'home'
router.on('route:home', function () {
topicListView.render();
});
Edit: Solution: Overriding the parse function in the collection proved to be the error. I wonder why...
The argument topics in your success handler is shadowing the variable topics.
The argument contains the parsed JSON response, not the Backbone Collection. You don't need that, so you can remove the argument.
The reference to topics will now be to your Collection, so topics.models will have the value you expect.
topics.fetch({
success: function () { // argument removed here so `topics` is no longer shadowed
var template = _.template($('#topic-list-template').html(), { topics: topics.models });
that.$el.html(template);
}
})

Backbone Collection not getting models

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

Rendering with Backbone

I am trying to learn backbone and I was following along the code school backbone.js course to build my own backbone app. So far I have this code but I am having problems with rendering anything.
var PostsApp = new (Backbone.View.extend({
Collections: {},
Models: {},
Views: {},
start: function(bootstrap){
var posts = new PostsApp.Collections.Posts(bootstrap.posts);
var postsView = new PostsApp.Views.Posts({collection: posts});
this.$el.append(postsView.render().el);
}
}))({el : document.body});
PostsApp.Models.Post = Backbone.Model.extend({});
PostsApp.Collections.Posts = Backbone.Collection.extend({});
PostsApp.Views.Post = Backbone.View.extend({
template: _.template("<%= name %>"),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
}
});
PostsApp.Views.Posts = Backbone.View.extend({
render: function(){
this.collection.forEach(this.addOne, this);
},
addOne: function(post){
var postView = new PostsApp.Views.Post({model:post});
this.$el.append(postView.render().el);
}
});
var bootstrap = {
posts: [
{name:"gorkem"},
{name: "janish"}
]
}
$(function(){
PostsApp.start(bootstrap);
});
I am just trying to create a very simple backbone app, CodeSchool is great but it not good at combining the pieces together and when I try to do that myself I am having problems.
So far the error I am getting is "Uncaught TypeError: Cannot read property 'el' of undefined" in the addOne function of the Posts View. Any help would be much appreciated.
edit: The answer below solved my initial problem, but I also set up an express server to send data to the front end with the code :
app.get('/tweet', function(req,res){
res.send([{ name: 'random_name' }, {name: 'diren_gezi'}] );
});
and then I am trying to fetch this data to my collection like this :
var PostsApp = new (Backbone.View.extend({
Collections: {},
Models: {},
Views: {},
start: function(bootstrap){
var posts = new PostsApp.Collections.Posts(bootstrap.posts);
posts.url = '/tweet';
posts.fetch();
var postsView = new PostsApp.Views.Posts({collection: posts});
postsView.render();
this.$el.append(postsView.el);
}
}))({el : document.body});
But in the page the initial data (name: gorkem and name: janish) is displayed instead of the recently fetched data..
This is the problem line (I see it in a few spots).
this.$el.append(postsView.render().el);
Try changing it to
postsView.render();
this.$el.append(postsView.el);
Render function doesn't return a function to self (the object with a reference to el).

backbonejs: Cannot call method 'bind' of undefined

I am currently learning backbone.js via a screencast tutorial, somewhere along the way, my code seems to stop working, throwing the error Cannot call method 'bind' of undefined in Chrome's javascript console. The erroneous line is contained in the initialize function:
window.PlaylistView = Backbone.View.extend({
tag: 'section',
className: 'playlist',
initialize: function() {
_.bindAll(this, 'render');
this.template = _.template($('#playlist-template').html());
this.collection.bind('reset', this.render); //<<<<<< THIS LINE
this.player = this.options.player;
this.library = this.options.library;
},
render: function() {
$(this.el).html(this.template(this.player.toJSON()));
this.$('button.play').toggle(this.player.isStopped());
this.$('button.pause').toggle(this.player.isPlaying());
return this;
}
});
I don't know what this.collection means, why does the view have a collection, isn't collections for models? this.collection.bind() used in other views did not seem to throw any errors. In window.LibraryAlbumView which calls this.collection.trigger('select', this.model); and extends window.AlbumView, I dont see any collection defined anywhere in window.AlbumView, yet no error is thrown. This seems to be confusing me.
JSFIDDLE
EDIT:
The error has been fixed because instead of
window.Player = Backbone.Model.extend({
defaults: {
'currentAlbumIndex': 0,
'currentTrackIndex': 0,
'state': 'stop'
},
initialize: function() {
this.playlist = new Playlist();
},
I had
window.Player = Backbone.Model.extend({
defaults: {
'currentAlbumIndex': 0,
'currentTrackIndex': 0,
'state': 'stop'
},
initialize: function() {
playlist = new Playlist(); // <<< this line changed!
},
Also previously this.collection refered to the collection here,
window.BackboneTunes = Backbone.Router.extend({
routes: {
'': 'home',
'blank': 'blank'
},
initialize: function() {
this.playlistView = new PlaylistView({
collection: window.player.playlist, // <<<< THIS ONE!
player: window.player,
library: window.library
});
this.libraryView = new LibraryView({
collection: window.library
});
},
Backbone Views contain a collection or a model, because views are meant for presenting the data contained within a model or a collections of models.
This example throws an error because this.collection has not been defined yet. To do that you need to initialize some collection and then pass it on to your view.
new PlayListView({collection: someCollection});

Categories