Cannot read property 'length' of undefined error when passing collection of models to a CollectionView in Marionette - backbone.js

I am working in Backbone/Marionette and have a Collection, where I am parsing data, and creating a collection of models, which I then pass to a CollectionView, and a childView is set for that CollectionView.
The collection successfully makes it into the CollectionView, but it seems when my app attempts to display the childView, I get an Uncaught TypeError: Cannot read property 'length' of undefined
The error gets thrown in marionette here:
_filteredSortedModels: function(addedAt) {
var viewComparator = this.getViewComparator();
var models = this.collection.models;
addedAt = Math.min(Math.max(addedAt, 0), models.length - 1);
it appears as though "models" is undefined here.
My app is set up this way:
DrawerCollection:
App.module('Home.Drawer.Collections', function (Collections, App, Backbone, Marionette) {
model: App.Home.Drawer.Models.DrawerModel,
...make a request, parse some data...
responseData.push( new App.Home.Drawer.Models.DrawerModel({
...model properties...
});
var DrawerLayoutCollection = new App.Home.DrawerCollectionView({collection: responseData});
App.mainContainer.show(shelfLayoutCollection);
DrawerView:
App.module('Home', function(Home, App, Backbone, Marionette) {
Home.DrawerComponent = Backbone.Marionette.LayoutView.extend({
...my template, events, ui, etc....
});
});
Collection View (which has the DrawerView set to be its childView):
Home.DrawerCollectionView = Backbone.Marionette.CollectionView.extend({
childView: Home.DrawerComponent
});
Any input would be much appreciated!!!

Few things seem wrong with this line:
var DrawerLayoutCollection = new App.Home.DrawerCollectionView({collection: responseData});
First, DrawerLayoutCollection is not a constructor, but an instance.
Second, it's called a collection but it's actually a view instance.
You are passing responseData as a collection to the collection view instead of an actual backbone collection.
It should be something like
var drawerLayoutCollectionView = new App.Home.DrawerCollectionView({collection: new Backbone.Collection(responseData)});

Related

How to specify filtered Backbone collection for Marionette view

I have a Marionette composite view that displays a collection, which I set in my Application start handler:
App.on('start', function() {
Backbone.history.start({pushState: true});
// I load up this.appsCollection in my before:start handler
var tblView = new this.appsTableView({
collection: this.appsCollection
});
this.regions.main.show(tblView);
});
This works as expected, displaying my entire collection. In my models, I have a state field, and I want to display only models with state 0. I tried:
collection: this.appsCollection.where({state: 0})
but that doesn't work. I actually want to display states in 0 and 1, but I'm trying to just display state in 0 for right now.
What am I missing?
The problem probably resides in that .where() doesn't return a collection, but an array. http://backbonejs.org/#Collection-where This was supposedly to maintain compatibility with underscore.
If you change the line to:
collection: new Backbone.Collection( this.appsCollection.where( { state: 0 } ))
Does that help?
I was able to override the filter method in my Marionette CompositeView:
http://marionettejs.com/docs/v2.4.3/marionette.collectionview.html#collectionviews-filter

How do you properly display a Backbone marionette collection view based on a model javascript array property?

*UPDATE: See final answer code in the last code block below.*
Currently I am having an issue displaying a collection in a collection view. The collection is a property of an existing model like so (pseudo code)
ApplicationVersion { Id: 1, VersionName: "", ApplicationCategories[] }
So essentially ApplicationVersion has a property called ApplicationCategories that is a javascript array. Currently when I render the collection view associated with ApplicationCategories nothing is rendered. If I debug in Chrome's javascript debugger it appears that the categories have not been populated yet (so I assume ApplicationVersion has not been fetched yet). Here is my code as it stands currently
ApplicationCategory Model, Collection, and Views
ApplicationModule.ApplicationCategory = Backbone.Model.extend({
urlRoot:"/applicationcategories"
});
ApplicationModule.ApplicationCategories = Recruit.Collection.extend({
url:"/applicationcategories",
model:ApplicationModule.ApplicationCategory,
initialize: function(){
/*
* By default backbone does not bind the collection change event to the comparator
* for performance reasons. I am choosing to not preoptimize though and do the
* binding. This may need to change later if performance becomes an issue.
* See https://github.com/documentcloud/backbone/issues/689
*
* Note also this is only nescessary for the default sort. By using the
* SortableCollectionMixin in other sorting methods, we do the binding
* there as well.
*/
this.on("change", this.sort);
},
comparator: function(applicationCategory) {
return applicationCategory.get("order");
},
byName: function() {
return this.sortedBy(function(applicationCategory) {
return applicationCategory.get("name");
});
}
});
_.extend(ApplicationModule.ApplicationCategories.prototype, SortableCollectionMixin);
ApplicationModule.ApplicationCategoryView = Recruit.ItemView.extend({
template:"application/applicationcategory-view-template"
});
ApplicationModule.ApplicationCategoriesView = Recruit.CollectionView.extend({
itemView:ApplicationModule.ApplicationCategoryView
});
ApplicationCategory template
<section id="<%=name%>">
<%=order%>
</section>
ApplicationVersion Model, Collection, and Views
ApplicationModule.ApplicationVersion = Backbone.Model.extend({
urlRoot:"/applicationversions"
});
ApplicationModule.ApplicationVersions = Recruit.Collection.extend({
url:"/applicationversions",
model:ApplicationModule.ApplicationVersion
});
ApplicationModule.ApplicationVersionLayout = Recruit.Layout.extend({
template:"application/applicationversion-view-template",
regions: {
applicationVersionHeader: "#applicationVersionHeader",
applicationVersionCategories: "#applicationVersionCategories",
applicationVersionFooter: "#applicationVersionFooter"
}
});
ApplicationModule.ApplicationVersionController = {
showApplicationVersion: function (applicationVersionId) {
ApplicationModule.applicationVersion = new ApplicationModule.ApplicationVersion({id : applicationVersionId});
var applicationVersionLayout = new Recruit.ApplicationModule.ApplicationVersionLayout({
model:ApplicationModule.applicationVersion
});
ApplicationModule.applicationVersion.fetch({success: function(){
var applicationVersionCategories = new Recruit.ApplicationModule.ApplicationCategoriesView({
collection: ApplicationModule.applicationVersion.application_categories
});
applicationVersionLayout.applicationVersionCategories.show(applicationVersionCategories);
}});
// Fake server responds to the request
ApplicationModule.server.respond();
Recruit.layout.main.show(applicationVersionLayout);
}
};
Here is my ApplicationVersion template
<section id="applicationVersionOuterSection">
<header id="applicationVersionHeader">
Your Application Header <%= id %>
</header>
<section id="applicationVersionCategories">
</section>
<footer id="applicationVersionFooter">
Your footer
</footer>
One thing to note I am currently using Sinon to mock my server response, but I don't think this is causing the issues as it is responding with the information as I expect looking through the javascript debugger (and like I said it is displaying ApplicationVersion id correctly). I can provide this code as well if it helps
It is currently displaying the application version id (id in the template), so I know it is fetching the data correctly for normal properties, it just is not rendering my ApplicationCategories javascript array property.
So ultimately I am binding to the success of the fetch for ApplicationVersion, then setting up the view for the ApplicationCategories. Since this isn't working like I expect I am wondering if there is a better way to create this collection view?
Thanks for any help
UPDATE: Working code example that Derek Bailey lead me too.
ApplicationModule.ApplicationVersionController = {
showApplicationVersion: function (applicationVersionId) {
ApplicationModule.applicationVersion = new ApplicationModule.ApplicationVersion({id : applicationVersionId});
var applicationVersionLayout = new Recruit.ApplicationModule.ApplicationVersionLayout({
model:ApplicationModule.applicationVersion
});
ApplicationModule.applicationVersion.fetch();
// Fake server responds to the request
ApplicationModule.server.respond();
Recruit.layout.main.show(applicationVersionLayout);
var applicationVersionCategories = new Recruit.ApplicationModule.ApplicationCategoriesView({
collection: new Backbone.Collection(ApplicationModule.applicationVersion.get('application_categories'))
});
applicationVersionLayout.applicationVersionCategories.show(applicationVersionCategories);
}
};
Marionette's CollectionView requires a valid Backbone.Collection, not a simple array. You need to create a Backbone.Collection from your array when passing it to the view:
new MyView({
collection: new Backbone.Collection(MyModel.Something.ArrayOfThings)
});

Backbone.js error - Uncaught TypeError: Object [object Object] has no method 'set'

My Code:
I am new to Backbone.js and trying to build an app with Backbone.js and PHP. When I am trying to call add in the router, I am getting error:
Uncaught TypeError: Object [object Object] has no method 'set'.
Please help me to find my mistake.
Thanks.
// Models
window.Users = Backbone.Model.extend({
urlRoot:"./bb-api/users",
defaults:{
"id":null,
"name":"",
"email":"",
"designation":""
}
});
window.UsersCollection = Backbone.Collection.extend({
model:Users,
url:"./bb-api/users"
});
// Views
window.AddUserView = Backbone.View.extend({
template:_.template($('#new-user-tpl').html()),
initialize:function(){
this.model.bind("click", this.render, this);
},
render:function(){
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
events:{
"click .add":"saveUser"
},
saveUser:function(){ alert('saveUser');
this.model.set({
name:$("#name").val(),
email:$("#email").val(),
designation:$("#designation").val()
});
if(this.model.isNew()){
this.model.create(this.model);
}
return false;
}
});
// Router
var AppRouter = Backbone.Router.extend({
routes:{
"":"welcome",
"users":"list",
"users/:id":"userDetails",
"add":"addUser"
},
addUser:function(){
this.addUserModel = new UsersCollection();
this.addUserView = new AddUserView({model:this.addUserModel});
$('#content').html(this.addUserView.render().el);
}
});
var app = new AppRouter();
Backbone.history.start();
As suggested in the comments, the problem starts here here:
this.addUserModel = new UsersCollection();
this.addUserView = new AddUserView({model:this.addUserModel});
and finishes here:
saveUser:function(){ alert('saveUser');
this.model.set({
By passing a collection in place of a model you create confusion, and as a result later in the saveUser function you try to call a Backbone.Model method (set) on a Backbone.Collection instance.
Note: As of version 1.0.0 Backbone.Collection now has a set method. In previous versions, such as the one used by the question's author, that method was instead called update.
There are several steps you can take to clarify this code. For starters, I would rename your model and collection classes so that it's clear that the model is the singular form and the collection is the plural form:
window.Users => window.User
window.UsersCollection => window.Users
Next, I would create a new User model, instead of a Users collection, and pass that to your view:
this.addUserModel = new User();
this.addUserView = new AddUserView({model:this.addUserModel});
Finally, I'd remove these lines:
if(this.model.isNew()){
this.model.create(this.model);
}
For one thing, the model will always be new (as you just created it before passing it in), but more importantly you don't need to call the Collection's create method because that method creates a new model, when you already have one created. Perhaps what you should add instead is :
this.model.save();
if your intent is to save the model to your server.
Since you already specified a urlRoot for the model, that should be all you need to create a new model, pass it to your view, have your view fill in its attributes based on DOM elements, and finally save that model's attributes to your server.
I think you are facing problem with object scope. When event fired it send to event object to that function. Just try this it may work
Declare global variable with the current view inside the initialize
initialize : function(){ self = this; }
then change this to self,
saveUser:function(){ alert('saveUser');
self.model.set({
name:$("#name").val(),
email:$("#email").val(),
designation:$("#designation").val()
});
if(self.model.isNew()){
self.model.create(this.model);
}
return false;
}

Backbone collection remove, ReferenceError: el is not defined

I have a view for a collection, and when I invoke its remove method I call its collection remove method as well, and I'm getting a 'ReferenceError: el is not defined' which doesn't make any sense to me, why would a collection need an el.
Invoking code:
try {
myAppModel=backboneApp.views.privateViews.myAppsTabView.myAppsView.views.myAppsPrivateView.collection.get(appId);
backboneApp.views.privateViews.myAppsTabView.myAppsView.views.myAppsPrivateView.remove(myAppModel);
} catch(e) {
console.log("delFromMyAppsCollection: Failed to delete app from collection e= " + e);
}
Remove method within View:
remove : function(modelToRemove) {
alert('Killing!');
console.log("MyAppsPrivateView.remove called with model: ", modelToRemove );
this.collection.remove(modelToRemove);
console.log("MyAppsPrivateView.remove collection: ", this.collection );
this._rendered = false;
}
I guess it may be a better way to delete an element from a collection/view, but still it seems odd that the collection is complaining about not having an el, any ideas?
Thanks in advance.
Just in case,
view definition:
var MyAppsPrivateView = Backbone.View.extend( {
// Reference to this collection's model.
model: PapsCatalog , // don't should be PapModel instead of a collection?
templateId: Epc2G.myAppsTemplateId,
template: jQuery('#' + this.templateId).html(),
view instantiation:
var options = {
className : "MyAppsContainer",
uid : "myAppsPrivateView",
collection : papsCollection,
el : "#myAppsView"
};
var oMyAppsPrivateView = new MyAppsPrivateView(_.clone(options));
Might it relate to Backbone.View already having a remove method, and you’re overriding it?
This sounds like a composite view situation, have you considered having a view for every model in the collection?

Calling invoke on Backbone Collection

I've a Backbone Collection initialized but calling invoke on collection doesn't work. For some reason I'm getting JS error:
var vw = new SomeView(); // A view with function 'refresh'
var col = new Backbone.Collection();
col.add(vw);
...
setTimeout(function(){ col.invoke('refresh'); }, 1000); // Error: Cannot call method 'apply' of undefined
However, invoking method like isEmpty works fine
console.log("Is empty? ", col.isEmpty()); // prints: 'Is Empty? false'
It seems I'm missing something very obvious.
N.B: I'm not interested in calling each function and then invoking refresh on view object because that's just clunky.
A collection in backbone is a list of models. So when you add a view to a collection internally it will call something like this col.add(Backbone.Model.extend(vw)). So it will create a new model with your view as constructor params. I f you wanna store your view in a list just use a JavaScript array or a smarter underscore collection
you try to create a collection by instantiating the collection itself,
you first have to extend from it and tell it what models it's holding
and of what i see above you are trying to put your views in a collection?
that is not possible directly, as a collection holds a list of models, not views.
you can however create a model defining your view.
var myView = Backbone.View.extend({});
var myModel = Backbone.Model.extend({});
var myCollection = Backbone.Collection.extend({ model: myModel });
$(function(){
// creating your view
var vw = new SomeView();
// creating a model for the view
var viewModel = new myModel({ linkedview : vw });
// creating a collection
var modelList = new myCollection();
modelList.add(viewModel);
});
the gist of it is, that you create a model, containing a reference to your view, and not add the view directly into the collection (which will not work)

Resources