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
},
// ...
});
Related
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
I try to change some way to call methods into namespace.
Calling parent methods (I dont think its possible)
Creating and call inheritance function
Calling inside another method (mostly jquery onReady event function) (this.MyFunction() not working)
I split every namespace in files (want to keep it that way)
I try How to call function A from function B within the same namespace? but I didn't succed to split namespaces.
my fiddle sample got only 1 sub-namespace but could be more.
https://jsfiddle.net/forX/kv1w2rvc/
/**************************************************************************
// FILE Master.js
***************************************************************************/
if (!Master) var Master = {};
Master.Print= function(text){
console.log("master.Print :" + text);
$("body").append("<div>master.Print : " + text + "</div>");
}
/**************************************************************************
// FILE Master.Test1.js
***************************************************************************/
if (!Master) var Master = {};
if (!Master.Test1) Master.Test1 = {};
/**************************************************************************
* Descrition :
* Function for managing event load/documentReady
**************************************************************************/
Master.Test1.onReady = function () {
$(function () {
Master.Test1.Function1(); //try to replace because need all namespace.
try {
this.Function2(); //not working
}
catch(err) {
console.log("this.Function2 not working");
$("body").append("<div>this.Function2 not working</div>");
}
try {
this.Print("onReady"); //not working
}
catch(err) {
console.log("this.Print not working");
$("body").append("<div>this.Print not working</div>");
}
try {
Print("onReady"); //not working
}
catch(err) {
console.log("Print not working");
$("body").append("<div>Print not working</div>");
}
});
}
Master.Test1.Function1 = function () {
console.log("Function1");
$("body").append("<div>Function1</div>");
this.Function3(); //working because not inside another function
}
Master.Test1.Function2 = function () {
$("body").append("<div>Function2</div>");
console.log("Function2");
}
Master.Test1.Function3 = function () {
$("body").append("<div>Function3</div>");
console.log("Function3");
Master.Print("Function3"); //try to replace because need all namespace.
}
Master.Test1.onReady();
I use Master.Test1.Function1(); and I want to change that because Function1 is inside the same namespace.
I use Master.Print("Function3"); I dont think I can change that. the way I try to use it, it's more an inheritance function. but I dont know if theres a way to do that?
Maybe I should change the my namespace methode? maybe prototype will do what I want?
You can capture the this in a variable because this inside $(function() {}) will point to document object. The below will work provided you never change the calling context of onReady -- i.e. it is always called on the Test1 object and not called on other context:
Master.Test1.onReady = function () {
var self = this;
$(function () {
self.Function1();
// ..
});
}
To access Print you have to reference using the Master object like: Master.Print() as it won't be available in the Test1 object
this is document within .ready() or jQuery() alias for .ready() where function(){} is parameter $(function() {}). this at this.Function2() will reference document.
"Objects" in javascript are not built the same way as in most object-oriented languages. Essentially, what you are building is a hierarchy of static methods that have no real internal state in-and-of themselves. Therefore, when one of the defined methods is invoked, the context (or state) of that method depends on what object invoked the method.
If you want to have any internal context, you will need to create an "instance" of an "object prototype". At that point, you can use "this.otherFunction" within your other functions. Here is a small example:
var MyObject = function() {};
MyObject.functionOne = function() {
console.log("Function 1");
this.functionTwo();
};
MyObject.functionTwo = function() {
console.log("Function 2");
};
var instanceOne = new MyObject();
instanceOne.functionOne();
You might get some more information about object definition here
This is probably a stupid question, but is there way in Javascript (ES5 preferred) to "extend" a class function similar to how i can i extend a parent' function in PHP ?
Basicly, i have this class hierarchy from System -> Weapon -> Dual and i would like Dual to use the code from System.setState() and then do some more stuff.
Note i use pre ES6 syntax for my hierarchy.
function System(system){
this.setState = function(){
//do stuff
}
}
function Weapon(system){
System.call(this, system);
}
Weapon.prototype = Object.create(System.prototype);
function Dual(system){
Weapon.call(this, system);
this.setState = function(){ // this is the problem
System.prototype.setState(); // error - not defined
//Weapon.protoype.setState() doesnt work either
//do more stuff
}
}
Dual.prototype = Object.create(Weapon.prototype);
Because setState is an instance property of System it does not exist on System.proptotype so you can't call it using System.prototype.setState.call. If you want to call it in this case, just create an object from System like so
function Dual(system){
Weapon.call(this, system);
var parent = new System(system);
this.setState = function() {
parent.setState(); // done
}
}
Instance properties are duplicated on each individual object ( they don't share). Whereas, prototype properties will be shared among children( they are not duplicated on child classes). To make all System 's subclasses share setState function, add it to System 's prototype
function System (arg) { ... }
System.prototype.setState = function () {...}
Now in your child classes, you can do
function Dual(system){
Weapon.call(this, system);
this.setState = function() {
System.prototype.setState.call(this); // done
}
}
First, you should set your instance methods on the prototype:
System.prototype.setState = function() {
// your stuff
}
This will improve performance and allow you to inherit the method without constructing a System instance.
Then, you just need to call System's version of setState on the right object (the instance of Dual) instead of calling it on System.prototype:
Dual.prototype = Object.create(Weapon.prototype, {
'setState': { value: function(){
System.prototype.setState.call(this) // fixed
// other stuff
}}
})
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();
I'm new at Backbone.js.And I hava some problem at this keyworks.I hava a Backbone view blow:
var print = Backbone.View.extend({
el : $('ul li.newItem'),
events : { 'click li.newItem':'printText'},
initialize:function(){
_.bind(printText,this); // does 'this' refer to the li.newItem ?
alert(1233); // init does't work.
},
printText : function(){
//I wanna print the hello world text each list item when clicked.
}
});
var print = new print();
Here is my demo : http://jsbin.com/evoqef/3/edit
You have two problems that are keeping your initialize from working:
There is no printText in scope.
_.bind and _.bindAll behave differently.
The first is easy to fix, you want to use this.printText, not just printText.
_.bind binds a single function to a this and returns that bound function; _.bindAll, on the other hand, binds several named functions to a this and leaves the bound functions attached to the specified this. So, doing this:
_.bind(printText, this);
doesn't do anything useful as you're throwing away the bound function. You'd want to do this:
this.printText = _.bind(this.printText, this);
Or more commonly in Backbone apps, you'd use _.bindAll:
_.bindAll(this, 'printText');
Now you have a functioning initialize and you'll have the right this inside printText and we can move on to fixing printText. I think you want to extract the text from the <li> that was clicked; you can do this like this:
printText: function(ev) {
console.log($(ev.target).text());
}
But that still doesn't work and we're left to wondering what's going on here. Well, Backbone binds events to a view's el so let us have a look at that:
var print = Backbone.View.extend({
el : $('ul li.newItem'),
//...
When that Backbone.View.extend runs, there won't be any li.newItem elements in the DOM so you won't get a useful el in that view. The usual approach here would be to have a view that looks like this:
var Print = Backbone.View.extend({
tagName: 'li',
events: {
'click': 'printText'
},
render: function() {
this.$el.text('Hello world ' + this.options.i);
return this;
},
printText: function(e){
console.log($(e.target).text());
}
});
We set tagName to 'li' and let Backbone create the <li> by itself. Then we'd pass the counter value to the Print view as an argument, Backbone will take care of leaving the argument in this.options.i when we say new Print({ i: ... }).
Now we just have to adjust the addItem method in your ListView to create new Prints and add them to the <ul>:
addItem: function(){
this.counter++;
var v = new Print({ i: this.counter });
this.$('ul').append(v.render().el);
}
Updated demo: http://jsbin.com/evoqef/10/edit
I've also made a few other changes:
Use this.$el instead of $(this.el), there's no need to create something that's already available.
Use this.$() instead of $('ul', this.el), same result but this.$() doesn't hide the context at the end of the $() function call and this.$() is more idiomatic in Backbone.
In your _.bind(printText, this);
The printText is outside of the scope of the init function. this as your second argument represents the print Backbone.View.
You could do something like this:
_.bind(this.printText, this);
and probably rid yourself of the init() error. But you could use this inside of printText and it will represent your View anyway.
printText: function() {
console.log(this); // this represents your view
}