Afternoon all, I'm relatively new to backbone and have been stumped for 3 days with this error which I have not seen before.
I have a collection 'TestCollection' which defines it's model as a function. When the collection is loaded I get an error the first time it attempts to make a model with class 'TestModel'.
The error I get is:
Uncaught TypeError: TestModel is not a constructor
at new model (testCollection.js:14)
at child._prepareModel (backbone.js:913)
at child.set (backbone.js:700)
at child.add (backbone.js:632)
at child.reset (backbone.js:764)
at Object.options.success (backbone.js:860)
at fire (jquery.js:3143)
at Object.fireWith [as resolveWith] (jquery.js:3255)
at done (jquery.js:9309)
at XMLHttpRequest.callback (jquery.js:9713)
I believe I have given both the collection and the model all of the code they should need to work. It feels like something has gone wrong with the loading, but when I put a console.log at the top of the model file I could see that it is definitely being loaded before the collection attempts to use it.
Any help would be massively appreciated.
TestCollection:
define([
'backbone',
'models/testModel'
], function(Backbone, TestModel) {
var TestCollection = Backbone.Collection.extend({
model: function(attrs) {
switch (attrs._type) {
case 'test':
console.log('making a test model')
return new TestModel();
}
},
initialize : function(models, options){
this.url = options.url;
this._type = options._type;
this.fetch({reset:true});
}
});
return TestCollection;
});
TestModel:
require([
'./testParentModel'
], function(TestParentModel) {
var TestModel = TestParentModel.extend({
urlRoot: 'root/url',
initialize: function() {
console.log('making test model')
}
});
return TestModel;
});
File where TestCollection is made:
define(function(require) {
var MyProjectCollection = require('collections/myProjectCollection');
var TestCollection = require('collections/testCollection');
Origin.on('router:dashboard', function(location, subLocation, action) {
Origin.on('dashboard:loaded', function (options) {
switch (options.type) {
case 'all':
var myProjectCollection = new MyProjectCollection;
myProjectCollection.fetch({
success: function() {
myProjectCollection.each(function(project) {
this.project[project.id] = {};
this.project[project.id].testObjects = new TestCollection([], {
url: 'url/' + project.id,
_type: 'test'
});
});
}
});
}
});
});
I've had a look around stack overflow, it does not appear to be the issue below (which seems to be the most common issue).
Model is not a constructor-Backbone
I also do not think I have any circular dependencies.
Any help would be massively appreciated as I am completely stumped. I've tried to include only the relevant code, please let me know if additional code would be useful.
Thanks
I can't say for other parts of the code but an obvious problem you have is misunderstanding what data is passed to the model creator function.
var TestCollection = Backbone.Collection.extend({
model: function(attrs) {
switch (attrs._type) { // attrs._type does exist here
console.log( attrs ); // Will print { foo: 'bar' }
case 'test': // This will always be false since attrs._type does not exist
console.log('making a test model')
return new TestModel();
default:
return new Backbone.Model(); // Or return some other model instance,
// you MUST have this function return
// some kind of a Backbone.Model
}
},
initialize : function(models, options){
this.url = options.url;
this._type = options._type;
this.fetch({reset:true});
}
});
new TestCollection([ { foo: 'bar' }], {
url: 'url/' + project.id,
_type: 'test' // This will NOT be passed to the model attrs, these are
// options used for creating the Collection instance.
})
To re-iterate. When you instantiate a Collection you pass an array of plain objects [{ foo: 'bar'}, { foo: 'baz'}] ( or you get them via fetch like you're doing ). That object will be passed as the attrs parameter in the model function, and the model function MUST return at least some kind of a Backbone.Model instance so you need a fallback for your switch statement.
Related
Seems like this should be obvious, but there seem to be so many different examples out there, most of which cause errors for me, making me think they are out of date. The basic situation is that I have a MessageModel linked to a MessageView which extends ItemView, MessageCollection linked to a MessageCollectionView (itemView: MessageView). I have a slightly unusual scenario in that the MessageCollection is populated asynchronously, so when the page first renders, it is empty and a "Loading" icon would be displayed. Maybe I have things structured incorrectly (see here for the history), but right now, I've encapsulated the code that makes the initial request to the server and receives the initial list of messages in the MessageCollection object such that it updates itself. However, I'm not clear, given this, how to trigger displaying the view. Obviously, the model shouldn't tell the view to render, but none of my attempts to instantiate a view and have it listen for modelChange events and call "render" have worked.
I have tried:
No loading element, just display the CollectionView with no elements on load, but then it doesn't refresh after the underlying Collection is refreshed.
Adding modelEvents { 'change': 'render' } to the view --> Uncaught TypeError: Object function () { return parent.apply(this, arguments); } has no method 'on'
I also tried this.bindTo(this.collection..) but "this" did not nave a bindTo method
Finally, I tried, in the view.initialize: _.bindAll(this); this.model.on('change': this.render); --> Uncaught TypeError: Object function () { [native code] } has no method 'on'
Here is the code
Entities.MessageCollection = Backbone.Collection.extend({
defaults: {
questionId: null
},
model: Entities.Message,
initialize: function (models, options) {
options || (options = {});
if (options.title) {
this.title = options.title;
}
if (options.id) {
this.questionId = options.id;
}
},
subscribe: function () {
var self = this; //needed for proper scope
QaApp.Lightstreamer.Do('subscribeUpdate', {
adapterName: 'QaAdapter',
parameterValue: this.questionId,
otherStuff: 'otherstuff',
onUpdate: function (data, options) {
console.log("calling sync");
var obj = JSON.parse(data.jsonString);
self.set(obj.Messages, options);
self.trigger('sync', self, obj.Messages, options);
}
});
},
});
Views.MessageCollectionView = Backbone.Marionette.CollectionView.extend({
itemView: Views.MessageView,
tagName: 'ul',
// modelEvents: {
// 'change': 'render'
// },
onAfterItemAdded: function (itemView) {
this.$el.append(itemView.el);
}
});
var Api = {
subscribe: function (id) {
var question = new QaApp.Entities.Question(null, { id: id });
question.subscribe();
var questionView = new QaApp.Views.QuestionView(question);
QaApp.page.show(questionView);
}
};
I am very grateful for all the help I've received already and thanks in advance for looking.
Try this:
var questionView = new QaApp.Views.QuestionView({
collection: question
});
In this code...
_.each(this.photos, function(element,index,list) {
console.log('element...');
console.log(element);
var photoView = new PhotoView({photo:element});
self.$el.append(photoView.render());
});
element is the entire this.photos collection. Why is not just one photo element of the 10 in the collection?
EDIT: Here is my method that populates the photos collection....
loadPhotos: function(memberId) {
var self = this;
this.photos = new PhotosCollection([]);
this.photos.on('error', this.eventSyncError, this);
this.photos.fetch({
url: this.photos.urlByMember + memberId,
success: function(collection,response,options) {
console.log('Fetch photos success!');
self.render();
}
});
},
The collection loads with models just fine. In the Chrome console, I can see the collection of models. I'm not sure what's wrong. I cannot iterate the collection with any of the methods recommended by posters below.
You are using the _.each method incorrectly. The underscore methods needs to called directly on the collection:
this.photos.each(function(element,index,list) {
console.log('element...');
console.log(element);
var photoView = new PhotoView({photo:element});
self.$el.append(photoView.render());
});
Or you if want to use the _.each from you need to pass in the models property and not the collection object itself as the list:
_.each(this.photos.models, function(element,index,list) {
console.log('element...');
console.log(element);
var photoView = new PhotoView({photo:element});
self.$el.append(photoView.render());
});
One should use this.photos.each(function(elt, index, list){...}) instead of _.each(this.photos,...) because this.photos is not an underscorejs _.chain object.
Thank you for your suggestions! I would never have figured this out without all your advice above. So here was the problem...
In the parent view, this loads up photo records for a particular member...
loadPhotos: function(memberId) {
var self = this;
this.photos = new PhotosCollection([]);
this.photos.on('error',this.eventSyncError,this);
this.photos.fetch({
url: this.photos.urlByMember + memberId,
success: function(collection,response,options) {
self.render();
}
});
},
Still in the parent view, Backbone.Subviews uses this to call each child view when it renders. Note how I'm passing this.photos to the subvw-photos...
subviewCreators: {
"subvw-profile": function() {
var options = {member: this.member};
// do any logic required to create initialization options, etc.,
// then instantiate and return new subview object
return new ProfileView( options );
},
"subvw-photos": function() {
var options = {photos: this.photos};
return new PhotosView( options );
},
"subvw-comments": function() {
var options = {};
return new CommentsView( options );
}
},
This is in the subvw-photos child view. Note how the intialize is accepting the collection as a parameter. See this problem?...
initialize: function(photos) {
Backbone.Courier.add(this);
this.photos = photos;
},
render: function() {
console.log('rendering photosview now...');
var self = this;
this.photos.each(function(element,index,list) {
var photoView = new PhotoView({photo:element});
$(self.el).append(photoView.render());
});
return this;
},
I was passing an object wrapping the photos collection in to initalize but then treating it like it was just a ref to the photos collection. I had to change the subvw-photos initialize to the following...
initialize: function(args) {
Backbone.Courier.add(this);
this.photos = args.photos;
},
Then of course all the other code magically began working :-/
Thank you again for your tips! You definitely kept me on track :-)
I've a model listen on the vent for a event update:TotalCost, which is triggered from (unrelated) Collection C when any model M belonging to collection C changes.
This event is coded in the initialize method as below. On receiving the event I get the following error:
TypeError: this.set is not a function
this.set({ "totalsale": value});
CostModel = Backbone.Model.extend({
defaults: {
totalSale: 0,
totalTax: 0
},
initialize: function(attrs, options) {
if(options) {
if(options.vent) {
this.vent = options.vent;
}
}
this.vent.on("update:TotalCost", function(value) {
this.set({ "totalSale": value}); **//ERROR HERE**
});
}
});
It is highly possible you've forgot to add the new keyword before your model for example you have:
var user = UserModel();
// instead of
var user = new UserModel();
Have you tried using a closure?
CostModel = Backbone.Model.extend({
defaults: {
totalSale: 0,
totalTax: 0
},
initialize: function(attrs, options) {
var self = this;
if(options) {
if(options.vent) {
this.vent = options.vent;
}
}
this.vent.on("update:TotalCost", function(value) {
self.set({ "totalSale": value});
});
}
});
Perhaps you want this to refer to current CostModel instance, to do so you need to pass this to this.vent.on call so event callback will be executed in context of model:
this.vent.on("update:TotalCost", function(value) {
this.set({ "totalSale": value});
}, this);
it may be due to 'set' works on model not on object. so you can, first convert your object in to model then try..
in example:
new Backbone.Model(your_object).set('val', var);
Another cause of this error can be if you try to create a new model without using the "new" keyword
I was getting this mysterious error when using it with Parse. I had:
Parse.User().current().escape("facebookID")
... when I should have had:
Parse.User.current().escape("facebookID")
Removed the extra () and it works fine now.
Another cause:
// render() method in view object
setInterval(this.model.showName, 3000);
// showName() method in model object
showName: function(){
console.log(this.get('name')); // this.get is not a function
}
i am new to backbone.js and need a little help sending data to a template. Im using a model with fetch, and a collection. here is the code :
(function($) {
var UserModel = Backbone.Model.extend({
urlRoot : '/users',
defaults : {
name : '',
email : ''
},
initialize : function() {
_.bindAll(this);
this.fetch();
},
parse : function(res) {
return JSON.stringify(res);
},
});
var users_coll = Backbone.Collection.extend({
//model: UserModel
initialize : function() {
var u = new UserModel();
this.model = u;
}
});
var displayView = Backbone.View.extend({
initialize : function() {
this.collection = new users_coll();
//_.each(this.collection.models, alert);
//console.log(this.collection);
//alert(JSON.stringify(this.collection.models));
this.render();
},
render : function() {
var tmpl = _.template($("#data-display-tpl").html());
this.$el.html(tmpl);
}
});
var view = new displayView({
el : $("#data-display")
});
})(jQuery);
it's working fine upto the model part. In the parse function of the model, i have used console.log() and everything seems fine. i get a properly formated json, and the fetch works fine too.
however in my collection i get nothing when i try console.log(user_coll.models).
i think i am probably missing something really small. not sure what, maybe the flow of things is all wrong.
I tried to modify your code just a bit to get poin trough...hope it helps clarify few basics.
I also didn't try provided example, but in theory it should work ;)
Here is how his example should be done...
Let's imagine Twitter app for example. Twitter app has only one model that represents one user in system. That's UserModel
var UserModel = Backbone.Model.extend({
urlRoot : '/user', // this is just for modifying one specific user
defaults : {
name : '',
email : ''
},
initialize : function() {
_.bindAll(this);
//this.fetch(); // WRONG: This call was "wrong" here
// fetch() should be done on Collection not model
},
parse : function(res) {
return JSON.stringify(res);
},
});
Now, you can have many lists of users on Twitter right. So you have two lists. In one list you have Friends users, and in other Family users
var UsersFriendsCollection = Backbone.Collection.extend({
model: UserModel // you tell Collection what type ob models it contains
url: '/users/friends',
initialize : function() {
// jabadaba whatever you need here
}
});
var UsersFamilyCollection = Backbone.Collection.extend({
model: UserModel // you tell Collection what type ob models it contains
url: '/users/family',
initialize : function() {
// jabadaba whatever you need here
}
});
...
var displayView = Backbone.View.extend({
initialize : function() {
this.collection = new UsersFriendsCollection();
this.collection.fetch(); // so you call fetch() on Collection, not Model
console.log(this.collection); // this should be populated now
//_.each(this.collection.models, alert);
//alert(JSON.stringify(this.collection.models));
this.render();
},
render : function() {
// collection data is avail. in templating engine for iteration now
var tmpl = _.template($( "#data-display-tpl" ).html(), this.collection);
this.$el.html(tmpl);
}
});
A collection's model attribute is meant for specifying what type of model the collection will contain and if specified you can pass the collection an array of raw objects and it will add and create them. From the docs
Override this property to specify the model class that the collection
contains. If defined, you can pass raw attributes objects (and arrays)
to add, create, and reset, and the attributes will be converted into a
model of the proper type
So when in your code you have
var u = new UserModel();
this.model = u;
You aren't actually adding the model to the collection. Instead you can use the collections add or fetch methods.
I'am redesigning my backbone application based on the answer of #20100 to this question The best way to fetch and render a collection for a given object_id.
Please read the comment on the code because I think is more clear, and my question looks better in smaller sizes.
// My View
define([
"js/collections/myCollection",
"js/models/myFeed"
], function (MyCollection, MyModel) {
var MyView = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection = new MyCollection();
this.collection.on('add', this.onAddOne, this);
this.collection.on('reset', this.onAddAll, this);
// when I make myView = new MyView(_.extend( {el:this.$("#myView")} , this.options));
// myView.render is not called
// in order to trigger the render function I make the following… but probably there is a better way …
var that = this;
this.collection.fetch({
success: function () {
that.render();
}
});
}
});
return MyView;
});
// MyCollection
define([
"js/models/myModel"
], function (MyModel) {
var MyCollection = Backbone.MyCollection.extend({
model: MyModel, // add this
url: function () {
var url = "http://localhost/movies";
return url;
// if I look to the GET request the url is without idAttribute
// how can I attach the idAttribute to this url?
// should bb takes care of this?
}
});
return MyCollection;
});
//MyModel
define([
], function () {
var MyModel = Backbone.MyModel.extend({
idAttribute: 'object_id'
});
return MyModel
});
There's two paths you want to explore
Pre-populate your collection with your model data
In your example you're already doing this, but you're fetching a collection, the collection URL is http://localhost/movies, if you want an individual model take a look at the next point
Fetch each individual model only when you need it
In the assumption that you're trying to get an ID on a collection that is not pre-populated and are loading 1 model at a time, you will have to approach this a bit in a custom way by adding a method to your collection somewhat similarly to this
getOrFetch: function(id, options)
{
var model;
if (this.get(id))
{
model = this.get(id);
}
else
{
model = new this.model({
id: id
});
this.add(model);
model.fetch(options);
}
return model;
}
or add the function as Backbone.Collection.prototype.getOrFetch so you can use it on every Backbone Collection if you need it.