Backbone is not using the model specified for the collection. I must be missing something.
App.Models.Folder = Backbone.Model.extend({
initialize: function() {
_.extend(this, Backbone.Events);
this.url = "/folders";
this.items = new App.Collections.FolderItems();
this.items.url = '/folders/' + this.id + '/items';
},
get_item: function(id) {
return this.items.get(id);
}
});
App.Collections.FolderItems = Backbone.Collection.extend({
model: App.Models.FolderItem
});
App.Models.FolderItem = Backbone.Model.extend({
initialize: function() {
console.log("FOLDER ITEM INIT");
}
});
var folder = new App.Models.Folder({id:id})
folder.fetch();
// later on change event in a view
folder.items.fetch();
The folder is loaded, the items are then loaded, but they are not FolderItem objects and FOLDER ITEM INIT is never called. They are basic Model objects.
What did I miss? Should I do this differently?
EDIT:
Not sure why this works vs the documentation, but the following works. Backbone 5.3
App.Collections.FolderItems = Backbone.Collection.extend({
model: function(attributes) {
return new App.Models.FolderItem(attributes);
}
});
the problem is order of declaration for your model vs collection. basically, you need to define the model first.
App.Models.FolderItem = Backbone.Model.extend({...});
App.Collections.FolderItems = Backbone.Collection.extend({
model: App.Models.FolderItem
});
the reason is that backbone objects are defined with object literal syntax, which means they are evaluated immediately upon definition.
this code is functionality the same, but illustrates the object literal nature:
var folderItemDef = { ... };
var folderItemsDef = {
model: App.Models.FolderItem
}
App.Models.FolderItem = Backbone.Model.extend(folderItemDef);
App.Collections.FolderItems = Backbone.Collection.extend(folderItemsDef);
you can see in this example that folderItemDef and folderItems Def are both object literals, which have their key: value pairs evaluated immediately upon definition of the literal.
in your original code, you defined the collection first. this means App.Models.FolderItem is undefined when the collection is defined. so you are essentially doing this:
App.Collection.extend({
model: undefined
});
By moving the model definition above the collection definition, though, the collection will be able to find the model and it will be associated correctly.
FWIW: the reason your function version of setting the collection's model works, is that the function is not evaluated until the app is executed and a model is loaded into the collection. at that point, the javascript interpreter has already found the model's definition and it loads it correctly.
Related
While performing a fetch() on my backbone collection, and instantiating models as children of that collection, I want to add one more piece of information to each model.
I thought that I could do this using set in the model initialize. (My assumption is that fetch() is instantiating a new model for each object passed into it. And therefore as each initialize occurs the extra piece of data would be set.
To illustrate my problem I've pasted in four snippets, first from my collection class. Second the initialize function in my model class. Third, two functions that I use in the initialize function to get the needed information from the flickr api. Fourth, and finally, the app.js which performs the fetch().
First the collection class:
var ArmorApp = ArmorApp || {};
ArmorApp.ArmorCollection = Backbone.Collection.extend({
model: ArmorApp.singleArmor,
url: "https://spreadsheets.google.com/feeds/list/1SjHIBLTFb1XrlrpHxZ4SLE9lEJf4NyDVnKnbVejlL4w/1/public/values?alt=json",
//comparator: "Century",
parse: function(data){
var armorarray = [];
var entryarray = data.feed.entry;
for (var x in entryarray){
armorarray.push({"id": entryarray[x].gsx$id.$t,
"Filename": entryarray[x].gsx$filename.$t,
"Century": entryarray[x].gsx$century.$t,
"Date": entryarray[x].gsx$date.$t,
"Country": entryarray[x].gsx$country.$t,
"City": entryarray[x].gsx$city.$t,
"Type": entryarray[x].gsx$type.$t,
"Maker": entryarray[x].gsx$maker.$t,
"Recepient": entryarray[x].gsx$recipient.$t,
"Flickrid": entryarray[x].gsx$flickrid.$t,
"FlickrUrl": "", //entryarray[x].gsx$flickrurl.$t,
"FlickrUrlBig": ""//entryarray[x].gsx$flickrurlbig.$t,
});
}
return armorarray;
}
});
Second, the initialization in my model.
initialize: function(){
//console.log("A model instance named " + this.get("Filename"));
item = this;
var flickrapi = "https://api.flickr.com/services/rest/?&method=flickr.photos.getSizes&api_key=<my_apikey>&photo_id=" + this.get("Flickrid") + "&format=json&jsoncallback=?";
sources = getFlickrSources(flickrapi);
sources.then(function(data){
sourceArray = parseFlickrResponse(data);
FlickrSmall = sourceArray[0].FlickrSmall;
console.log (FlickrSmall);
item.set("FlickrUrl", FlickrSmall);
console.log(item);
});
Notice here how I'm getting the "Flickrid" and using to get one more piece of information and then trying to add it back into the model with item.set("FlickrUrl", FlickerSmall);
console.log confirms that the property "FlickrUrl" has been set to the desired value.
Third, these are the functions my model uses to get the information it needs for the flicker api.
var getFlickrSources = function(flickrapi){
flickrResponse = $.ajax({
url: flickrapi,
// The name of the callback parameter, as specified by the YQL service
jsonp: "callback",
// Tell jQuery we're expecting JSONP
dataType: "jsonp"})
return flickrResponse;
}
var parseFlickrResponse = function(data){
flickrSourceArray = []
if (data.stat == "ok"){
sizeArray = data.sizes.size;
for (var y in sizeArray){
if (sizeArray[y].label == "Small"){
flickrSourceArray.push({"FlickrSmall": sizeArray[y].source});
}
else if (sizeArray[y].label == "Large"){
flickrSourceArray.push({"FlickrLarge": sizeArray[y].source});
}
}
}
return flickrSourceArray
}
But, fourth, when I try to perform the fetch and render the collection, I only get objects in my collection without the FlickrUrl property set.
//create an array of models and then pass them in collection creation method
var armorGroup = new ArmorApp.ArmorCollection();
armorGroup.fetch().then(function(){
console.log(armorGroup.toJSON());
var armorGroupView = new ArmorApp.allArmorView({collection: armorGroup});
$("#allArmor").html(armorGroupView.render().el);
});
var armorRouter = new ArmorApp.Router();
Backbone.history.start();
The console.log in this last snippet prints out all the objects/models supposedly instantiated through the fetch. But none of them include the extra property that should have been set during the initialization.
Any ideas what is happening?
What is this function ? getFlickrSources(flickrapi)
Why are you using this.get in the initialize function. Honestly it looks over-complicated for what you are trying to do.
If you want to set some parameter when you instantiate your model then do this var model = new Model({ param:"someparam", url:"someurl",wtv:"somewtv"});
If the point is to update your model just write an update function in your model something like update: function (newparam) { this.set;... etc and call it when you need it.
If I read you well you just want to set some params when your model is instantiated, so just use what I specified above. Here is some more doc : http://backbonejs.org/#Model-constructor
I hope it helps.
edit:
Put your call outside your model, you shouldn't (imo) make call inside your model this way it seems kinda dirty.
Sources.then(function(flickrdata) {
var mymodel = new Model({flicker:flickrdata.wtv});
});
It would be cleaner in my opinion.
When I set idAtribute of a model I expect it to be used in url. Is it the right thing to expect or am I missing something? I see that backbone model sees 'id' attribute on itself and uses it to build url, but I explicitly told it to use '_id' property. It doesn't look like the right behavior to me.
var model = new Backbone.Model();
model.set('id', 1); // if you remove this line everything works properly
model.set('_id', 2);
model.idAttribute = '_id';
model.urlRoot = 'models';
model.url(); // returns "models/1"
I would do that this way.
var someModel = Backbone.Model.extend({
initialize: function(attr,params) {
this.id = attr.id;
this.url= "somerooturl/"+params.id;
}
});
var model = new someModel(attr,params);
model.set ...
I have been running into the problem of stale collection references. So, I have the following model:
ProcessModel = Backbone.Model.extend({
initialize: function() {
this.set('steps', new StepsCollection());
}
...
});
When the ProcessModel is fetched from the server, the StepsCollection is returned as well. Previously, I had the following parse method:
parse: function(response) {
...
response.steps = new StepsCollection(response.steps, {parse: true});
}
...however, this was creating a brand new collection object, rather than reusing the existing one. This was causing a view which was bound to the previous "steps" collection to become stale.
I've tried the following:
response.steps = this.get('steps').reset(response.steps);
But I get a long stacktrace in Object.Marionette.bindEntityEvents. What am I doing wrong?
Try this. This will create single collection and then we'll reset same collection instance with new data-set inside parse method.
ProcessModel = Backbone.Model.extend({
initialize: function() {
this.myCollection = new StepsCollection();
this.set('steps', this.myCollection);
...
},
parse: function(response) {
this.myCollection.reset(response.steps);
this.set('steps', this.myCollection);
...
}
});
If I have a model named "Book" and a collection named "Library" defined as below:
Book
app.Book = Backbone.Model.extend({
defaults: {
title: 'No title',
author: 'Unknown'
}
});
Library
app.Library = Backbone.Collection.extend({
model: app.Book,
url: '/api/books'
});
When I call BookInstance.save() how does it come up with the URL to use? Does it derive it from the collection?
In Backbone.model there are 2 options: url and urlRoot. What is the purpose and difference between these?
In Backbone.collection, there is a single parameter url. Is this always the GET request for the RESTFUL api?
Basically, there are 3 possibilities to construct a model's url:
If the model object exists in a collection then its url method will return an address composed of the collection.url and model.id: [collection.url]/[id].
If you don't want to use a model inside the collection, then model.urlRoot's value can be used instead of the collection.url fragment, resulting in the following pattern: [urlRoot]/[id].
Finally, if you're NOT planning to persist more that one model of a given type to the server or will be defining URLs for each model upon their creation, you can directly assign a value to model.url.
Collections send only GET requests — to get an array of models' JSON data. For saving, removing, and updating, the individual model's save() (POST/PUT/PATCH) and destroy() (DELETE) methods are used.
Here's the source code of Backbone.Model.url, which should help you:
url: function() {
var base =
_.result(this, 'urlRoot') ||
_.result(this.collection, 'url') ||
urlError();
if (this.isNew()) return base;
var id = this.get(this.idAttribute);
return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
}
In model
urlRoot is used for the Model.
url is used for the instance of the Model.
So if urlRoot exists
then book.fetch() will fetch the data given id, for example
var Book = Backbone.Model.extend({urlRoot: 'books' });
var book = new Book({id: 1});
book.fetch(); // will get /books/1
var Book = Backbone.Model.extend({});
var book = new Book({url: 'books/1'});
book.fetch(); // will get /books/1
var Books = Backbone.Collection.extend({model: Book});
var books = new Books({ /*....*/ });
books.fetch(); // will get /books/
You can refer the backbone Model urlRoot source code here
I hope it makes sense to you, good luck.
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;
}