I
have a backbone view and in the initialize function I have an event listener:
this.$el.on('hide', this.hideModal.bind(this));
So Im binding the current context to the callback.
I want to write a jasmine test (im using v2.0.0) for this but initializing the view means I get the error:
'undefined' is not a function (evaluating 'this.hideModal.bind(this)')
If I was to remove the .bind(this) and use var self = this as a global variable within the view, it will work. But that means I have to make my code more messy just so that it can be testable. Is there a way around this?
Related
Here is the app I'm referring to:
I am trying to fundamentally understand the bind method in Javascript.
My understanding when I play around with it in the console is that bind returns a copy of the function, with "this" bound to whatever you pass into bind.
function logThis(){
console.log(this)
}
logThis.bind({today: 'Tuesday'})
//Will return a copy of the logThis function, with 'this' set to the
{today:'Tuesday'} object. The code does not run right away though.
var explicitlyLogThis = logThis.bind({today: 'Tuesday'});
explicitlyLogThis(); //This will run the code and display the {today: 'Tuesday'} object to the console.
This is my understanding so far. I understand that to actually run this new function that has 'this' explicitly bound using the bind method, you need to set it to a variable and then run it.
I see a contradiction when I look at the app in the above link. If you look at the bindEvents method on line 56, we have .on('keyup', this.create.bind(this)). I understand that we have to set 'this' to App when we run the create method because jQuery defaults to setting 'this' to the jQuery object itself. So this line is actually the same as: $('#new-todo').on('keyup', App.create.bind(App)).
That isn't where my confusion is. My question is:
How exactly are these copies of the functions with 'this' set to App actually being called? The app does not set them to a variable and then call that variable the way I had to when I was working in the console.
It just invokes the bound functions directly as soon as an event occurs on one of the jQuery elements. But I thought writing it this way would just return a copy of the function, and not run the function itself, if I am basing my assumptions on what I have figured out in the code I wrote above. I thought in order to invoke the function immediately, you would need to use call or apply.
I also realize that the app runs the bindEvents method when it starts (see line 46). So I understand that when you start the app, copies of the various functions are created with the correct 'this' bound to the functions. But...when/how do they actually get invoked without assigning them to variables? How are these copies accessed?
I think I have a flawed understanding of the bind method, so I would love some help. Thanks!
It sounds like you understand bind well enough. Perhaps there is some confusion with passing anonymous functions. As you know calling bind returns a new function and this can optionally be stored as a variable or passed as a function argument.
In the example below btn1 accepts a bound function as you've seen. This could also be written in a more long hand fashion with btn2. They're identical. btn3 doesn't receive a bound function, when its clicked its context is the button element, this looses all visibility of MagicalApp fucntions.
<button id="example1">button one bound</button>
<button id="example2">button one bound</button>
<button id="example3">button two unbound</button>
<script>
class MagicalApp {
add() {
console.log('this could do addition');
}
}
const app = new MagicalApp();
function contextOfEvent(event) {
console.log('contextSensitive', this.add)
}
const btn1 = document.querySelector("#example1");
btn1.addEventListener('click', contextOfEvent.bind(app));
const btn2 = document.querySelector("#example2");
const btn2ClickHandler = contextOfEvent.bind(app)
btn2.addEventListener('click', btn2ClickHandler);
const btn3 = document.querySelector("#example3");
btn3.addEventListener('click', contextOfEvent);
</script>
I am trying to get some old code to work properly with minimal modification. The code was written on the assumption that it would run from a particular context. I have the context object.
Original code:
function oldExample(){
console.log(window); //New Error: window is undefined
console.log(gBrowser); //New Error: gBrowser is undefined
}
New, working code:
function fixedExample(){
console.log(this.window);
console.log(this.gBrowser);
}
//Elsewhere
function loadData(context) {
fixedExample.call(context);
}
Notes:
1. loadData and oldExample are defined in separate files.
2. context has other children besides window and gBrowser; This is an example
Is there a way to transition my oldExample code to work properly without needing to stuff this. everywhere? I.e., how can I run oldExample in a different context?
The only way I know how to do this is to define the properties as variables of the current context:
var object = {gBrowser: 'test'};
function oldExample(){
console.log(gBrowser);
}
var gBrowser = object.gBrowser;
oldExample();
This example outputs 'test'
But all this does is move the property access outside of the function definition, doesn't save you anything.
You can use bind method in javascript.
fixedExample.bind (context);
Now you need not use 'this' inside fixedExample and can use window directly.
I have this:
Ext.define('MyWindow',{stuff that uses param});
Ext.define('widget.panel',{
stuff
handlerFn: function (parameter) { //parameter comes from Ext.pass(this.handlerFn,parameter)
Ext.create('MyWindow',{param: parameter}).show();
}
stuff
});
The button and its handler are defined inside the initComponent of the panel. When I made this without using Ext.define on the window and directly hardcoding it in the handler instead everything worked fine. However now it says param is not defined. How to pass it correctly?
The handler has to be passed through Ext.pass(this.handlerFn, [parameter])
Also inside the Ext.define make sure you know what your scope is on every level and you will easily find the parameter on the 'window' level.
I have issues to spyOn a method bind to an event with scope.$on in a factory service, with Jasmine. The real method originally passed is called, instead of the spy.
I've made a plinkr: http://plnkr.co/edit/2RPwrw?p=preview
Thanks for your help.
This is because of the way you're binding the callback. Change
service.$on('hello', service.method);
to
service.$on('hello', function() {
service.method();
});
When you say spyOn(service, 'method'), you're saying "replace the value that is referenced at service.method with a spy." However, your original service.$on code doesn't look up the value at service.method when the event is triggered--instead it looks it up when the service is initialized. Thus, changing the reference that service.method points to later has no effect.
I think this question is more a javascript question than a backbone question, but I've run into it while developing an application in backbone, so that's the context I will ask it in.
I am binding a method with arguments to a model's change event. The code below executes that method when the listener is bound, not when the event is fired:
this.model.on("change:disposition", this.dChange("disposition"), this);
while the following code executes the method when the change event is fired (the desired behavior):
this.model.on("change:disposition", function(){ this.dChange("disposition"); }, this);
I would appreciate it if someone could describe what specifically is happening in these two instances. Also, is there is a better way to bind a method with arguments rather than wrapping it in a closure as I've done?
Thanks.
When you call this.dChange("disposition") you're invoking the function. (You're using the parentheses () to invoke)
But when you do function() {} or this.dChange you're in fact referencing a function object. And it's this reference that the event manager will call once the event is fired.
Side note: In your case, instead of using an anonymus function, you could use the bind method of Underscore.js like this:
this.model.on("change:disposition", _.bind(this.dChange, this, "disposition"));