In the code below, initializeBoard has access to the property, and the console returns 'white' when I start the script. But when I click inside the window, I get 'undefined'. What obvious thing am I missing? (Bonus: what's the search query that'd have led me to the answer without having to ask?)
var view = {
currentMove: 'white',
initializeBoard: function() {
console.log(this.currentMove);
},
click: function(e) {
console.log(this.currentMove);
}
}
window.onload = function() {
view.initializeBoard();
document.onclick = view.click;
}
The value of this is determined by how the function is called, not by where it is first assigned.
You are copying the (reference to the) function to document.onclick.
When the click event happens document.onclick is called. view.click is not called (even though it has the same value as document.onclick). This means that this is document not view.
Use bind if you want to create a wrapper function that calls the original function in the right context.
document.onclick = view.click.bind(view);
Related
I was working today at a front-end Javascript project. I will try to keep the description of the problem and the solution as short as possible.
I had to add click handlers to the links on a page that redirect the user to other pages, so I had 2 Javascript array arrayOfRedirectLinks and pageLinkElements:
var arrayOfRedirectLinks, pageLinkElements;
Initially I wrote the addEventHandlers function like this:
var addEventHandlers = function() {
var i, link;
for( var i in arrayOfRedirectLinks) {
link = arrayOfRedirectLinks[i];
pageLinkElements[i].addEventListener('click', function(e) {
e.preventDefault();
window.location = link;
});
}
}
I thought that this solution will do the job until... well until I opened the browser, clicked several links and noticed that all of them redirected me to the same link (the last link in the arrayOfRedirectLinks).
Finally I found that my problem was similar to the one posted here
Javascript multiple dynamic addEventListener created in for loop - passing parameters not working
And indeed both the first and the second solution posted there worked for me
var addEventHandlers = function() {
var i, link;
for( var i in arrayOfRedirectLinks) {
(function(link){
link = arrayOfRedirectLinks[i];
pageLinkElements[i].addEventListener('click', function(e) {
e.preventDefault();
window.location = link;
});
}(link));
}
}
and
var passLink = function(link) {
return function(e) {
e.preventDefault();
window.location = link;
};
};
var addEventHandlers = function() {
var i, link;
for( var i in arrayOfRedirectLinks) {
link = arrayOfRedirectLinks[i];
pageLinkElements[i].addEventListener('click',passLink(link));
}
}
Now this seems to work but I don't understand why it works.
I came with the following explanation and I would like if someone can confirm if it's correct:
When I declare a function in Javascript, it gets the references to the variables in the scope of the function where it was declared. ( i.e. my event handler gets a reference to the link variable in the addEventHandlers function)
Because the handler gets a reference to the variable link. When I reassign a value to the link variable, the value that will be used when the click handler gets triggered will also change. So the link variable from the event handler is not simply copy with of the link with a different memory address and same value as when the function handler was added, but they both share the same memory address and therefore the same value.
Because of the reasons described at 2), the all the click handlers will use the redirect to the same link, the last link in the array arrayOfRedirectLinks because that's the last value that will get assigned to the link variable at the end of the for loop.
But when I pass the link variable as a parameter to another function, a new scope it's created and the link inside that scope actually shares only it's initial value with the value of the link parameter passed to the function. The references of the 2 link variables are different.
Because of 4), when I pass the link to the click handler, it will take the reference to the link variable in the Immediately Invoked Function Expression who itself doesn't share the same address with the link in the addEventHandlers function. Therefore each link from the event handler functions will be isolated from the others and will keep the value of the arrayOfRedirectLinks[i]
Is this correct?
This is the critical bit:
var i, link;
for( var i in arrayOfRedirectLinks) {
link = arrayOfRedirectLinks[i];
pageLinkElements[i].addEventListener('click', function(e) {
e.preventDefault();
window.location = link;
});
}
There is only ever one link variable here. Note that the addEventListener callback function is only called when the link is clicked.
By that time, the variable link has its final value, which is shared by all event handler functions.
So all the links do the same thing.
Simplest solution (other than a wider refactor):
for(var i in arrayOfRedirectLinks) {
(function(i) {
var link = arrayOfRedirectLinks[i];
pageLinkElements[i].addEventListener('click', function(e) {
e.preventDefault();
window.location = link;
});
}(i));
}
I am trying to get this function to be called via click event, but for some reason it is being called when page loads. I am completely baffled on why my function is reacting this way.
Here is my function
var registerTab = function(panel){
var active = 'off';
if($('#'+panel).css('left') <= '0'){
$('#'+panel).animate({left: '0'});
active = 'on';
} else {
$('#'+panel).animate({left: '-380px'});
}
};
$(function() {
tabRegister.on('click', registerTab('sidePanel'));
});
The weird thing is if i call it when i remove the passed variable and hard-code the selector in it works fine which again makes no since to me. Please any help would be very helpful and save me some hair.
registerTab('sidePanel')
This call will cause the function to be called immediately. I think what you really want is this:
tabRegister.on('click', function () {
registerTab('sidePanel')
});
When I assign the event handler without parameters, it works: http://jsfiddle.net/mUj43/
function show(){
alert('work');
}
var myButton = document.createElement("input");
myButton.type="button";
myButton.value="click";
myButton.onclick=show;
var where = document.getElementById("where");
where.appendChild(myButton);
but if I pass parameters, it doesn't work: http://jsfiddle.net/mUj43/1/
myButton.onclick = show('test');
How can I use function with parameters in dynamically created elements?
You can't do that, you could use partial application by creating a new function and then attach that as event handler:
myButton.onclick=show.bind( myButton, 'test');
http://jsfiddle.net/mUj43/2/
Docs (which I recommend you read because this function is useful for many other things as well) and compatibility information: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
You'll have to create your own closure:
myButton.onclick = function () {
show.call(this, 'test');
};
You could also use #Esailija's bind method, but this one has deeper browser support.
try:
myButton.onclick = function(){show("test");}
or :
myButton.onclick = function(){ show.call( this, "test");}
if you want to retain the element object context inside the show function
That's because when you add events you need a function reference.
In your first example, show is a reference to a function.
In your second example, show('test') is a call to the function show, which returns nothing, and nothing isn't a function reference.
That's why when you load the page, it alerts "work" (the function is called), but when you click the button no function is called.
Then, you need a function.
You can declare it:
myButton.onclick=f;
function f(){
show('test')
}
Or you can use an anonymous one:
myButton.onclick=function(){
show('test')
}
I have an object I created in JavaScript. Let's say it looks like this:
function MyObject() {
this.secretIdea = "My Secret Idea!";
};
MyObject.prototype.load = function() {
this.MyButton = $(document.createElement("a"));
this.MyButton.addClass("CoolButtonClass");
this.MyButton.click = MyButton.onButtonClick;
someRandomHtmlObject.append(this.MyButton);
};
MyObject.prototype.onButtonClick = function(e) {
alert(this.secretIdea);
};
As you can see, I have an object setup in JavaScript and, when it's loaded, it creates an anchor tag. This anchor tag as a background image in CSS (so it's not empty).
Now, I understand that the 'this' statement, when the button would actually be clicked, would fall to the scope of the MyButton element rather than the object I have created.
I have tried using call(), try() and bind() and I cannot get this to work. I need it so that, when the button is clicked, it goes back to the object's scope and not the html element's scope.
What am I missing here?
The this value inside the click event handler refers to the DOM element, not to the object instance of your constructor.
You need to persist that value, also you refer to MyButton.onButtonClick I think you want to refer the onButtonClick method declared on MyObject.prototype:
MyObject.prototype.load = function() {
var instance = this;
//..
this.MyButton.click(function (e) { // <-- looks like you are using jQuery
// here `this` refers to `MyButton`
instance.onButtonClick(e);
});
//...
};
I have the code (inside one object)
onclick: this._addX.bind(this)
and then inside another object
onclick: this._addY.bind(this)
Now, _addX() and _addY are nearly identical, except they both end up calling (on the click event) a function with different argument values, say _addX calls foo('x') and _addY calls foo('y'). So I tried:
onclick: this._add.bind(this,'x') and
onclick: this._add.bind(this,'y') in the two objects. And of course I changed _add to accept an argument.
At runtime, when _add is called, it does not see any incoming arguments! I have fumbled around with different syntaxes but nothing works. Any ideas? The original syntax works fine (no arguments) but forces me to duplicate a large function with only one line different, which pains me. Thanks in advance.
_add: function(which) {
var me = this;
var checkFull = function(abk) {
if (abk.isFull) {
alert("full");
} else {
alert(which); // which is always undefined here!
}
};
getAddressBook(checkFull); //checkFull is a fn called by getAddressBook
},
this works and it keeps the scope within an element click event with the scope set to the class and not the element--there is no point in passing scope to the add method, it already has that:
var foo = new Class({
Implements: [Options],
add: function(what) {
alert(what);
},
initialize: function(options) {
this.setOptions(options);
this.options.element.addEvents({
click: function() {
this.add(this.options.what);
}.bind(this)
});
}
});
window.addEvent("domready", function() {
new foo({
element: $("foo"),
what: "nothin'"
});
});
just make an element with id=foo and click it to test (alerts nothin'). if your onclick is a function / event handler within your class as opposed to a normal element click event, then things are going to differ slightly - post a working skeleton of your work on http://mootools.net/shell/
If you read my previous answer, disregard it. The MooTools .bind method supports passing parameters. So something else isn't working as you expect:
onclick: this._add.bind(this, 'y');
Here is a simple setup on JSBin to show how bind truly does pass parameters.
The only purpose of bind is to "tell" the JS what object you mean when you say this. i.e. you pass as a parameter to bind an instance of the object you wish the this key word will refer to inside the function you used the bind on.