Pass arguments into backbone .bind() function - javascript

I have a view that renders a video player and a video list.
initialize: function() {
var q = window.location.search.slice(3);
this.videos = new Videos(window.exampleVideoData);
this.videos.bind('select', this.notifyAppView, this);
if ( q ){
this.videos.bind('fetchFinished', function(){
console.log('should still work');
this.render();
}, this);
// Any time this.videos collection changes we will re-render
this.videoPlaying = this.videos.models[0];
this.videos.fetch( q );
}else{
//Collection of all videos
// Any time this.videos collection changes we will re-render
this.videoPlaying = this.videos.models[0];
this.render();
}
},
That is my initialize function on the view. The problem that I am getting is that when I pass in arguments into the bind function , the error
app.js:10 Uncaught SyntaxError: Unexpected string
That is the line :
this.videos.bind('fetchFinished', function(){
console.log('should still work');
this.render();
}, this);
We get the error when we pass in a string to the function like so, :
this.videos.bind('fetchFinished', function('adfadf'){
console.log('should still work');
this.render();
}, this);
What is the correct way to specify a function to be bound with backbone, that takes an argument?

That is incorrect syntax. You should specify the argument's name in function declaration, not the value.
You can pass in a value like this:
this.videos.bind('fetchFinished', function(arg){
console.log('should still work', arg); // should still work adfadf
this.render();
}.bind(this, 'adfadf'));
Note that the second bind is native bind method of function objects, not the backbone bind. It'd be better to use backbone's on instead of using it's alias bind to avoid confusion

Related

backbone: render this collection

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);
});

Backbone.js, cannot set context on a callback

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.

Backbone/javascript - confused by 'this' keyword

In the following backbone scripts, I tried to change a collection in a view click event.
var StudentView = Backbone.View.extend({
initialize: function() {
console.log("create student items view");
this.collection.bind('add',this.render,this);
this.collection.bind('remove',this.render,this);
},
render : function(){
},
events :{
"click ":"select_students"
},
select_students: function(){
this.collection.reset([]);
_.each(students.models, function(m) {
if(m.get('name')=="Daniel"){
this.collection.add(m);
}
});
}
});
var students_view = new StudentView({el:$("#student_table"),collection:selected_students});
I got this error
How should I call "this.collection" in the code?
You should change you select_students to
select_students: function(){
var self = this;
this.collection.reset([]);
_.each(students.models, function(m) {
if(m.get('name')=="Daniel"){
self.collection.add(m);
}
});
}
The problem is that in JavaScript, the this context is lost in inner functions (like the one you pass to _.each) so the general pattern is to save a reference outside of that (self) and then use that in the inner function.
You can avoid using a reference at all, utilizing Backbone's collection filter method.
select_students: function () {
this.collection.reset(students.filter(function (student) {
return student.get('name') == 'Daniel';
});
}
Rather than using underscore's each() function, backbone collections can be iterated on directly, and can take a context to define what the 'this' variable refers to (passed as the second argument to each below).
So the best way to do this is to:
select_students: function(){
this.collection.reset([]);
students.each(function(m) {
if(m.get('name')=="Daniel"){
this.collection.add(m);
}
}, this);
}

Reference backbone functions from within a nested d3 function

I am trying to reference a backbone function from within a d3 function inside the backbone render function. I now must reference other Backbone functions, to do some backboney things, but can't access them by referencing it by using the this/that method (I use this/ƒthis):
define([
// this is for require.js file modularization
], function(){
return Backbone.View.extend({
initialize: function(options){
//CODE
this.render();
},
render: function(options){
// HOW I ACCESS THE BACKBONE VIEW IN NESTED SITUATIONS
var ƒthis = this;
//NORMAL RENDERING
if (!options) {
// Do some stuff, get some vars
// compile the template
// D3 stuff
var lineData = ({...});
var pathFunction = d3.svg.line()
var beatUnwindingPaths = [......];
var beatContainer = d3.select('#beatHolder'+this.parent.cid);
var beatPath = beatContainer //.append('g')
.insert('path', ':first-child')
.data([beatUnwindingPaths[0]])
.attr('d', pathFunction)
.attr('class', 'beat')
//THIS IS WHERE I WANT TO REFERENCE THE FUNCTION TO BE CALLED, AND HOW I THINK IT SHOULD BE CALLED
.on('click', ƒthis.toggle);
//BUT CURRENTLY I AM ONLY LIMITED TO CALLING A FUNCTION DECLARED WITHIN THE BACKBONE render func(), so it would look like this:
.on('click', toggle);
//CURRENTLY I AM HAVING TO DECLARE THE FUNCTIONS INSIDE RENDER
function unroll() {
//do some stuff
};
function reverse() {
};
$('#a').on('click', unroll);
$('#b').on('click', reverse);
}
},
// THIS IS THE FUNCTION I WANT TO CALL
toggle: function(){
//DO some stuff to the other BackBone models, collections and other cool stuff
}
});
});
How do I access the Backbone toggle function from inside the D3 code?
Error code is from within the toggle function itself (worked before, so I am trying to figure out why it isn't now), and the error is on 313, not 314, my browser console always is one line off. I put a console.log() to see that with the ƒthis.toggle I got in the function, but error-ed out on the switching of the bool value.
311 toggle: function(){
312 //switch the selected boolean value on the model
313 this.model.set('selected', !this.model.get('selected'));
314 //re-render it, passing the clicked beat to render()
315 this.render(this.model);
316 // log.sendLog([[1, "beat" + this.model.cid + " toggled: "+!bool]]);
317 dispatch.trigger('beatClicked.event');
318 }
I switched from the rendering the circle in the template, to having d3 create it (so we could animate it using the d3 functions), and I think somehow the object has lost its binding to the model. Working on this.....
This isn't a D3/Backbone issue, it's just Javascript. You can't pass an object method to be invoked later and expect this to work within that method unless you bind it in one way or another:
var myObject = {
method: function() {
this.doSomeStuff();
},
doSomeStuff: function() {
console.log("stuff!");
}
};
myObject.method(); // "stuff!"
// pass this method somewhere else - this is
// effectively what you do with an event handler
var myMethod = myObject.method;
myMethod(); // TypeError: Object [object global] has no method 'doSomeStuff'
The second part fails because the invocation myObject.myMethod() binds this to myObject, but assigning the method to a variable (or assigning it as an event handler) does not (in most cases, this is bound to window, but D3 will reassign this to the DOM element you set the handler on).
The standard fixes are 1) wrapping it in a function:
var myMethod = function() {
myObject.method();
};
myMethod(); // "stuff!"
or 2) binding it to the object somehow, e.g. in your Backbone initialize method (Underscore provides a useful _.bindAll utility for this purpose):
Backbone.View.extend({
initialize: function(options){
_.bindAll(this, 'toggle');
// now you can pass this.toggle around with impunity
},
// ...
});

Strange issue binding events with backbone, "this" is not being updated

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();

Categories