I'm trying to reach a collection instance with the this keyword, when iterating through its models. Here is my test code:
myModel = Backbone.Model.extend({
url: 'model.com'
});
myCollection = Backbone.Collection.extend({
model: myModel,
url: 'collection.com',
iterateAll: function(){
this.each(function(item){
console.log(this.url);
console.log(item.url);
});
}
});
mod = new myModel();
col = new myCollection(mod);
col.iterateAll();
This code outputs:
undefined
model.com
How can I reference the collection correctly when using this.url? I want the output to read collection.com not undefined.
this is pretty weird in JavaScript.
iterateAll: function() {
var self = this; // _this is also common
this.each(function(item) {
console.log(self.url);
console.log(item.url);
});
}
Basically, JavaScript doesn't formally distinguish between methods and functions. As a result, every function invocation -- even simple anonymous callbacks like these -- gets its own this value. But this very frequently isn't...what anyone wants ever. So you can hold onto the "useful" value of this in a temporary variable and reference it in the callback.
This is so common that it's a feature of CoffeeScript via the => syntax, and it's going to be a feature of ES6. Until then...temp variables are we've got.
Do this instead:
...
iterateAll: function(){
var me = this;
me.each(function(item){
console.log(me.url);
console.log(item.url);
});
}
...
More info on this scope. http://javascriptplayground.com/blog/2012/04/javascript-variable-scope-this/
Related
Below you can see four basic requireJS files. How can I have multiple Backbone.js views that will all share one collection which has been initiated and fetched elsewhere?
Please note
I am aware I can pass the collection in App.js however I would like to refrain from doing so since I will probably have many collections that will need to be used in many views, and I don't want to pass each of them in App.js.
Collection.js
return Backbone.Collection.extend({
url: '...'
});
App.js
var collection = new Collection();
$.when(collection.fetch()).done(function(){
new View1();
new View2();
});
View1.js
define(['Collection'], function(Collection){
return Backbone.View.extend({
initialize: function(){
console.log('Need the initiated, fetched collection here...');
});
});
});
View2.js
define(['Collection'], function(Collection){
return Backbone.View.extend({
initialize: function(){
console.log('Need the initiated, fetched collection here...');
});
});
});
Quick answer: RequireJS runs function body code once and the return statement alone multiple times. You can, hence, create a Collection Instance and return that to every person who requires it.
Collection.js
var CollectionConstructor = Backbone.Collection.extend({
url: '...'
});
var CollectionInstance = new CollectionConstructor();
return CollectionInstance;
Alternatively, if you want more than one instance of CollectionInstance running around, and don't want to pass it to the views, I don't believe anything short of global variables will help. That would look like:
Globals.js
var AppGlobals = AppGlobals || {};
AppGlobals.oneSet = new Collection ();
AppGlobals.anotherSet = new Collection ();
You can now have your views depend on Globals.js and access them from here. Depending on your use, either of these two should work. Keep in mind that in the second approach, your Collection.js is unmodified.
var app = {};
var FoodModel = Backbone.Model.extend({
url: "http://localhost/food"
});
var FoodCollection = Backbone.Collection.extend({
model: FoodModel,
url: "http://localhost/food"
});
var FoodView = Backbone.View.extend({
render: function(){
console.log(this.model.toJSON());
}
});
app.FoodModel = new FoodModel();
app.FoodCollection = new FoodCollection;
app.FoodCollection.fetch();
var myView = new FoodView({model: app.FoodModel})
In this piece of code, the console.log always returns null for data in this.model
If console.log the collection it is full of data, how can i get this.model inside the view to reflect the data in the collection?
I'm not sure where your app is triggering FoodView.render(). It won't happen automatically when you instantiate FoodView and it won't happen when the json response triggers a callback to app.FoodCollection.fetch() unless you are manually calling that in a success callback. If you are doing this, your context may have been lost.
Also, there are a few typos in your code (app.FoodCollection = new FoodCollection();). If not, then can you provide the exact code? Please include whatever code is calling render()
Also, I'm not sure your view is being associated with your model. Try:
var FoodView = Backbone.View.extend({
model: FoodModel,
render: function(){
console.log(this.model.toJSON());
}
});
Otherwise, Backbone has no way of knowing what model you are referring to.
I'm clearly missing the obvious here, but it's been a long day already.
The following code creates an infinite loop in the browser:
M = Backbone.Model.extend({
localStorage: new Backbone.LocalStorage("ModelName"),
initialize: function() {
this.on("change", this.save, this);
}
});
While the following code works fine:
M = Backbone.Model.extend({
localStorage: new Backbone.LocalStorage("ModelName"),
initialize: function() {
this.on("change", this.modelChanged, this);
},
modelChanged: function() {
this.save();
}
});
What's the difference?
(Yes, I'm using local storage for a model rather than a collection, but the model is a singleton that doesn't exist in a collection.)
The change event passes arguments to its handler, and if save is called with arguments, it applies them as new attributes to the model, and causes a change event (which passes attributes to save... which causes a change... etc)
demo fiddle (with problem) http://jsfiddle.net/mjmitche/UJ4HN/19/
I have a collection defined like this
var Friends = Backbone.Collection.extend({
model: Friend,
localStorage: new Backbone.LocalStorage("friends-list")
});
As far as I'm aware, that's all I need to do to get local storage to work (in addition to including it below backbone.js)
One thing I wasn't sure about, does the name "friends-list" have to correspond to a DOM element? I'm trying to save the "friends-list" so I called it that in local storage, however, localstorage doesn't seem to require passing a class or an id.
Here's a fiddle where you can see what I'm trying to do http://jsfiddle.net/mjmitche/UJ4HN/19/
On my local site, I'm adding a couple friends, refreshing the page, but the friends are not re-appearing.
Update
I've also done the following in my code on my local site
console.log(Backbone.LocalStorage);
and it's not throwing an error.
My attempt to debug
I tried this code (taken from another SO answer) in the window.AppView but nothing came up in the console.
this.collection.fetch({}, {
success: function (model, response) {
console.log("success");
},
error: function (model, response) {
console.log("error");
}
})
From the fine manual:
Quite simply a localStorage adapter for Backbone. It's a drop-in replacement for Backbone.Sync() to handle saving to a localStorage database.
This LocalStorage plugin is just a replacement for Backbone.Sync so you still have to save your models and fetch your collections.
Since you're not saving anything, you never put anything into your LocalStorage database. You need to save your models:
showPrompt: function() {
var friend_name = prompt("Who is your friend?");
var friend_model = new Friend({
name: friend_name
});
//Add a new friend model to our friend collection
this.collection.add(friend_model);
friend_model.save(); // <------------- This is sort of important.
},
You might want to use the success and error callbacks on that friend_model.save() too.
Since you're not fetching anything, you don't initialize your collection with whatever is in your LocalStorage database. You need to call fetch on your collection and you probably want to bind render to its "reset" event:
initialize: function() {
_.bindAll(this, 'render', 'showPrompt');
this.collection = new Friends();
this.collection.bind('add', this.render);
this.collection.bind('reset', this.render);
this.collection.fetch();
},
You'll also need to update your render to be able to render the whole collection:
render: function() {
var $list = this.$('#friends-list');
$list.empty();
this.collection.each(function(m) {
var newFriend = new FriendView({ model: m });
$list.append(newFriend.render().el);
});
$list.sortable();
return this;
}
You could make this better by moving the "add one model's view" logic to a separate method and bind that method to the collection's "add" event.
And a stripped down and fixed up version of your fiddle: http://jsfiddle.net/ambiguous/haE9K/
let's say I have :
var Book = Backbone.Model.extend();
var Collection = Backbone.Collection.extend({
model: Book,
url: '/books',
initialize: function(){
this.fetch();
})
})
How can I change the Collection's url when instantiating a new collection ?
var AdventureBooks = new Books({ url: '/books/adventure' }) does not work
var AdventureBooks = new Books({ category: 'adventure' })
and in the Collection definition:
url : '/books/' + this.category does not work either.
Thanks.
The following should work:
var AdventureBooks = new Books();
AdventureBooks.url = '/books/adventure';
var Book = Backbone.Model.extend({
"url": function() {
return '/books/' + this.get("category");
}
});
For some reason the parameters passed to Collection constructor (for example "url") are not set to the object. The collection uses only few of those (model and comparator).
If you want to pass the url via constructor you need to create initialize method that copies the necessary parameters to the object:
var Book = Backbone.Model.extend({
initialize: function(props) {
this.url = props.url;
}
}
var book = new Book({url: "/books/all"});
Like Daniel Patz pointed out , the problem lies in how you're instantiating the collection. I just struggled with this for a bit right now, so I thought I'd update this, even though the question is somewhat old.
The first argument is expected to be an array of models, with the options coming after. This should work:
var AdventureBooks = new Books([], { url: '/books/adventure' })
If you want a dynamic URL, then Raynos' answer might be the way to go.
If you want to have dynamic urls for your collection, try this (tested with backbone 1.1.2):
Create an instance of your backbone collection and pass the dynamic url parameter as an option (the options object needs to be the the second argument as the first one is an optional array of models):
var tweetsCollection = new TweetsCollection(null, { userId: 'u123' });
Then inside of your collection, create a dynamic url function that uses the value from the options object:
var TweetsCollection = Backbone.Collection.extend({
url: function() {
return '/api/tweets/' + this.options.userId;
},
model: TweetModel
});
The best solution for me is the initialize method, look at this example:
Entities.MyCollection = Backbone.Collection.extend({
model: Entities.MyModel,
initialize: function(models,options) {
this.url = (options||{}).url || "defaultURL";
},
}
use it as follows:
var items = new Entities.MyCollection(); //default URL
var items = new Entities.MyCollection([],{url:'newURL'}); //changed URL
I know that this a late reply, but I had a similar although slightly more complicated situation, and the selected answer didn't really help me.
I have a Conditions collection, and each Experiment model has multiple conditions, and I needed my url to be /api/experiments/:experimentId/conditions, but I didn't know how to access the experimentId from the empty Conditions collection.
In my Conditions collection url function, I did a console.log(this.toJSON()) and discovered
that Backbone inserts a single dummy model in the empty collection with whatever attributes you passed in at it's creation time.
so:
var Conditions = new ConditionsCollection({
experimentId: 1
});
I somehow doubt that this would be considered a best practice, hopefully someone else will respond with a better solution, but here's how I defined my Collection:
var ConditionsCollection = Backbone.Collection.extend({
model: Condition,
url: function(){
var experimentId = this.at(0).get("experimentId");
return "/api/experiments/" + experimentId + "/conditions";
}
});
This work for me (tested with backbone 1.2.1):
var serverData = Backbone.Collection.extend({
url: function() {
return '//localhost/rest/' + this.dbname;
},
constructor: function(a) {
if(a.dbname){
this.dbname = a.dbname;
}
Backbone.Collection.apply(this, arguments);
}
});
use it as follows:
var users = new serverData({dbname : 'users'});