var Text = Backbone.Model.extend({});
Texts = Backbone.Collection.extend({
model: Text,
url: '/data.json',
});
var TextsView = Backbone.View.extend({
initialize: function() {
_.bindAll(this);
this.render();
},
el: "#Texts",
template: _.template($('#TextTemplate').html()),
render: function(e){
_.each(this.model.models, function(Text){
var TextTemplate = this.template(Text.toJSON());
$(this.el).append(TextTemplate);
}, this);
return this;
}
})
var Texts = new Texts();
Texts.fetch();
var TextView = new TextsView({collection: Texts});
this gives me Uncaught TypeError: Cannot read property 'models' of undefined and does not display anything on the page.
This this.model.models should be this.collection
In your render method in your view, you should use this.collection.each instead of _.each function.
render: function(e){
this.collection.each(function(Text){
var TextTemplate = this.template(Text.toJSON());
$(this.el).append(TextTemplate);
}, this);
return this;
}
If you want to use _.each function, then you will need to access the models array directly in your collection as #dfsq pointed out. This can be done by using this.collection.models.
render: function(e){
_.each(this.collection.models, function(Text){
var TextTemplate = this.template(Text.toJSON());
$(this.el).append(TextTemplate);
}, this);
return this;
}
EDIT 2
Here are some reasons your fetch call may not be working. First check that you are using a web server, since ajax requests may be blocked for security reasons using file system. I know this is blocked in Chrome unless you change a certain setting. Not sure about Firefox.
The second reason is that the fetch call is asynchronous. This means that mostly likely your data will not be loaded when you run initialize
This means you'll need to make the following adjustments. First you need to add a listener to the add event of your collection so that anytime an item is added, your view will be notified.
initialize: function() {
_.bindAll(this);
this.render();
// Listen to the `add` event in your collection
this.listenTo(this.collection,"add", this.renderText);
},
Next we need to add a function to your view that will render a single item
renderText: function(Text) {
var TextTemplate = this.template(Text.toJSON());
this.$el.append(TextTemplate);
}
Also to answer your other question about the user of this in the each loop. The last parameter in the each function is the scope you want to use in the inside the callback function that executes. So if you use this as the second parameter, it allows you to access your viewing using this.
this.collection.each(function(Text){
var TextTemplate = this.template(Text.toJSON());
$(this.el).append(TextTemplate);
}, this);
If you don't add this, then you'd need to do this:
var view = this;
this.collection.each(function(Text){
var TextTemplate = view.template(Text.toJSON());
$(view.el).append(TextTemplate);
});
Related
I guess that's the simple question. I'm new in js, especially in Backbone.js.
All I want to know is how I can refer to my function inside jquery function.
getLanguages: function() {
...
return languages;
},
render: function() {
...
$("input[type='checkbox']").bind("change", function() {
// todo: getLanguages
});
}
I tried to get languages via this but, of course, I got checkbox in this case.
Edit:
It's so simple. Many thanks to all!!!
This is a classic problem in Javascript. You need to reference this inside a callback, but this changes to the element being bound to. A cheap way to do it:
render: function() {
var that = this;
$("input[type='checkbox']").bind("change", function() {
that.getLanguages();
});
}
that will stay defined as the this that render is defined on.
However, you’re using Backbone, and it has more suitable ways to handle this situation. I don’t know the name of your Backbone.View class, but here’s an example adapted from the documentation:
var DocumentView = Backbone.View.extend({
events: {
"change input[type='checkbox']": "doSomething"
},
doSomething: function() {
this.getLanguages(); # uses the correct this
}
});
Calling bind inside render is not The Backbone Way. Backbone views are made to handle event delegation without the unfortunate need to pass this around.
Save this object before bind change event in the scope of render function.
render: function() {
var CurrentObj = this;
$("input[type='checkbox']").bind("change", function() {
CurrentObj.getLanguages();
});
}
You can save the appropriate object into a local variable so from the event handler, you can still get to it:
getLanguages: function() {
...
return languages;
},
render: function() {
...
var self = this;
$("input[type='checkbox']").bind("change", function() {
var lang = self.getLanguages();
...
});
}
I am not sure if I am using Models and Collections correctly. If I'm not I would really appreciate any guidance or advice into what I am doing wrong.
I have set up a Model and a Collection. The Collection has a url which is executed using the .fetch() method. I pass the Collection to the View where I log the results to the console. When I console.log(this.model) in the View I see the attributes nested a few levels deep. I would like to see the attributes in the console.log. The .toJSON() method doe not seem to work.
Here's a Fiddle to my current code: http://jsfiddle.net/Gacgc/
Here is the JS:
(function () {
var DimensionsModel = Backbone.Model.extend();
var setHeader = function (xhr) {
xhr.setRequestHeader('JsonStub-User-Key', '0bb5822a-58f7-41cc-b8a7-17b4a30cd9d7');
xhr.setRequestHeader('JsonStub-Project-Key', '9e508c89-b7ac-400d-b414-b7d0dd35a42a');
};
var DimensionsCollection = Backbone.Collection.extend({
model: DimensionsModel,
url: 'http://jsonstub.com/calltestdata'
});
var dimensionsCollection = new DimensionsCollection();
var DimensionsView = Backbone.View.extend({
el: '.js-container',
initialize: function (options) {
this.model.fetch({beforeSend: setHeader});
_.bindAll(this, 'render');
this.model.bind('reset', this.render());
return this;
},
template: _.template( $('#dimensions-template').html() ),
render: function () {
console.log( this.model.toJSON() ); //Why does this return an empty array???
return this;
}
});
var myView = new DimensionsView({model: dimensionsCollection});
}());
Is this what you're looking for?
If you're passing a collection to the view you should assign it to the collection property:
// It's a collection. Backbone views have a collection
// property. We should totally use that!
var myView = new DimensionsView({collection: dimensionsCollection});
When you attempt to bind the reset event to your view's render function, you're actually invoking the function immediately (by including the braces):
// Omit the braces to assign the function definition rather than invoke
// it directly (and immediately)
this.model.bind('reset', this.render);
But that's beside the point, because backbone's collection doesn't trigger a reset event (see documentation). One approach would be to assign the view's render function to the success parameter of the options object you pass to your collection:
var self = this;
this.collection.fetch({
beforeSend: setHeader,
success: function() {
self.render();
}
});
Finally, you need a parse function in your collection to pull the dimensions array out of the JSON you're loading:
var DimensionsCollection = Backbone.Collection.extend({
model: DimensionsModel,
url: 'http://jsonstub.com/calltestdata',
parse: function(response) {
return response.dimensions;
}
});
Ok, so I am working on a method to override the fetch method on a model. I want to be able to pass it a list of URL's and have it do a fetch on each one, apply some processing to the results, then update its own attributes when they have all completed. Here's the basic design:
A Parent "wrapper" Model called AllVenues has a custom fetch function which reads a list of URL's it is given when it is instantiated
For each URL, it creates a Child Model and calls fetch on it specifying that URL as well as a success callback.
The AllVenues instance also has a property progress which it needs to update inside the success callback, so that it will know when all Child fetch's are complete.
And that's the part I'm having problems with. When the Child Model fetch completes, the success callback has no context of the Parent Model which originally called it. I've kind of hacked it because I have access to the Module and have stored the Parent Model in a variable, but this doesn't seem right to me. The Parent Model executed the Child's fetch so it should be able to pass the context along somehow. I don't want to hardcode the reference in there.
TL;DR
Here's my jsFiddle illustrating the problem. The interesting part starts on line 13. http://jsfiddle.net/tonicboy/64XpZ/5/
The full code:
// Define the app and a region to show content
// -------------------------------------------
var App = new Marionette.Application();
App.addRegions({
"mainRegion": "#main"
});
App.module("SampleModule", function (Mod, App, Backbone, Marionette, $, _) {
var MainView = Marionette.ItemView.extend({
template: "#sample-template"
});
var AllVenues = Backbone.Model.extend({
progress: 0,
join: function (model) {
this.progress++;
// do some processing of each model
if (this.progress === this.urls.length) this.finish();
},
finish: function() {
// do something when all models have completed
this.progress = 0;
console.log("FINISHED!");
},
fetch: function() {
successCallback = function(model) {
console.log("Returning from the fetch for a model");
Mod.controller.model.join(model);
};
_.bind(successCallback, this);
$.each(this.urls, function(key, val) {
var venue = new Backbone.Model();
venue.url = val;
venue.fetch({
success: successCallback
});
});
}
});
var Venue = Backbone.Model.extend({
toJSON: function () {
return _.clone(this.attributes.response);
}
});
var Controller = Marionette.Controller.extend({
initialize: function (options) {
this.region = options.region;
this.model = options.model;
this.listenTo(this.model, 'change', this.renderRegion);
},
show: function () {
this.model.fetch();
},
renderRegion: function () {
var view = new MainView({
model: this.model
});
this.region.show(view);
}
});
Mod.addInitializer(function () {
var allVenues = new AllVenues();
allVenues.urls = [
'https://api.foursquare.com/v2/venues/4a27485af964a52071911fe3?oauth_token=EWTYUCTSZDBOVTYZQ3Z01E54HMDYEPZMWOC0AKLVFRBIEXV4&v=20130811',
'https://api.foursquare.com/v2/venues/4afc4d3bf964a520512122e3?oauth_token=EWTYUCTSZDBOVTYZQ3Z01E54HMDYEPZMWOC0AKLVFRBIEXV4&v=20130811',
'https://api.foursquare.com/v2/venues/49cfde17f964a520d85a1fe3?oauth_token=EWTYUCTSZDBOVTYZQ3Z01E54HMDYEPZMWOC0AKLVFRBIEXV4&v=20130811'
];
Mod.controller = new Controller({
region: App.mainRegion,
model: allVenues
});
Mod.controller.show();
});
});
App.start();
I think you're misunderstanding how _.bind works. _.bind returns the bound function, it doesn't modify it in place. In truth, the documentation could be a bit clearer on this.
So this:
_.bind(successCallback, this);
is pointless as you're ignoring the bound function that _.bind is returning. I think you want to say this:
var successCallback = _.bind(function(model) {
console.log("Returning from the fetch for a model");
Mod.controller.model.join(model);
}, this);
Also note that I added a missing var, presumably you don't want successCallback to be global.
I had a strange issue working with backbone and binding events. I'll see if I can explain it in a clear way (it's a cropped example...)
In a view, I had the following code in the initialize method
var MyView = Backbone.View.extend({
initialize: function(options) {
//[...]
this.items = [];
this.collection.on('reset', this.updateItems, this);
this.fetched = false;
},
render: function() {
if (!this.fetched) {
this.collection.fetch(); // fetch the collection and fire updateItems
return this;
}
this.$el = $('#my-element');
this.$el.html(this.template(this.items));
},
updateItems: function() {
this.fetched = true;
this.loadItems();
this.render(); // call render with the items array ready to be displayed
}
}
The idea is that I have to fetch the collection, process the items (this.loadItems), and then I set this.$el.
The problem I was facing, is that inside updateItems, I couldn't see any property added after the binding (this.collection.on...)
It seemed like the binding was done against a frozen version of the view. I tried adding properties to test it, but inside updateItems (and inside render if being fired by the collection reset event) I could not see the added properties.
I solved it binding the collection just before fetching it, like this:
render: function() {
if (!this.fetched) {
this.collection.on('reset', this.updateItems, this);
this.collection.fetch();
return this;
}
But it's a strange behavior. Seems like when binding, a copy of 'this' is made, instead of a reference.
Am I right? or there's anything wrong I'm doing?
You should perform your binding in the initialization phase of your collection view:
// View of collection
initialize: function() {
this.model.bind('reset', this.updateItems);
}
now when fetch is finished on the collection updateItems method will be invoked.
Of course you need to bind the model and view before doing this:
var list = new ListModel();
var listView = new ListView({model: list});
list.fetch();
Is there a way to automatically do an _.bindAll for a backbone.js object?
I was talking to someone a while ago and they said that there was, but I have no idea where to start looking.
Example:
var TheView = Backbone.View.extend({
initialize: function() {
// HOW CAN I AVOID HAVING TO DO THIS?---->
_.bindAll(this,'render','on_element_01_click', 'on_element_02_click');
},
events: {
'click #element_01': 'on_element_01_click',
'click #element_02': 'on_element_02_click',
},
render: function(){
return this;
},
on_element_01_click: function(){
},
on_element_02_click: function(){
}
}
Do this instead:
_.bindAll(this);
Will bind ALL functions in this view.
I've since learned of a easier technique if you want to build bindAll in to your views (which is handy for things like AJAX callback methods that aren't auto-bound the way event handlers are). Basically you just override the constructor to perform the auto-binding.
var BoundModel = Backbone.Model.extend({
constructor: function() {
Backbone.Model.apply(this, arguments);
if (this.boundMethods) {
_(this).bindAll.apply(this, this.boundMethods);
}
}
})
var SubclassOfBoundModel = Backbone.Model.extend({
boundMethods: ['handleFetchResponse'],
initialize: function () {
this.model.on('sync', this.handleFetchResponse);
}
handleFetchResponse: function() {
// this function is bound to the model instance
}
})
Of course if you just wanted to bind all your methods you could leave out the "boundMethods" part and just have:
constructor: function() {
Backbone.Model.apply(this, arguments);
_(this).bindAll();
}
I tried doing this myself and I was able to get it working with something like this:
function bindOnExtend(clazz) {
var originalExtend = clazz.extend;
clazz.extend = function() {
var newSubClass = originalExtend.apply(this, arguments);
var originalInitialize = newSubClass.prototype.initialize;
newSubClass.prototype.initialize = function() {
// The constructor will get broken by bindAll; preserve it so _super keeps working
var realConstructor = this.constructor;
_.bindAll(this);
this.constructor = realConstructor;
originalInitialize.apply(this, arguments);
};
return bindOnExtend(newSubClass);
};
return clazz;
}
var BoundModel = Backbone.Model.extend();
bindOnExtend(BoundModel);
var BoundView = Backbone.View.extend();
bindOnExtend(BoundView);
However, I wouldn't recommend it. Doing that will make closures for every single method on every single model/view/whatever you instantiate. Not only does that add a slight increase in overall memory usage, it also opens up the possibility of memory leaks if you're not careful. Furthermore, it makes your stacktraces several lines longer, as they have to wind through bindOnExtend.
In my experience, having to do "_.bindAll(this, ..." is worth the trouble because:
1) it makes my code more clear/obvious to anyone coming after me
2) it encourages me to qualify my bindAll, instead of just using the 1-arg form
3) I hate wading through long stacktraces
But, if you want it the above code should work.