I have an onclick handler for an <a> element (actually, it's a jQuery-created handler, but that's not important). It looks like this:
function handleOnClick() {
if(confirm("Are you sure?")) {
return handleOnClickConfirmed();
}
return false;
}
From this function, the this object is accessable as the <a> element clicked. However, handleOnClickConfirmed's this is a Window element! I want handleOnClickConfirmed to have the same this as handleOnClick does. How would I do this?
(I know I can pass this as an argument to handleOnClickConfirmed, but some of my code already uses handleOnClickConfirmed and I don't want to have to rewrite those calls. Besides, I think using this looks cleaner.)
The following ought to do it:
function handleOnClick() {
if( confirm( "Sure?" ) ) {
return handleOnClickConfirmed.call( this );
}
return false;
}
The call() function attached to Function objects is designed to allow this; calling a function with a desired context. It's an extremely useful trick when setting up event handlers that call back into functions within other objects.
Rob's answer is the best answer for your problem, but I wanted to address something that you wrote in your original question:
I know I can pass this as an argument to handleOnClickConfirmed, but some of my code already uses handleOnClickConfirmed and I don't want to have to rewrite those calls.
JavaScript parameters are always optional, as far as the interpreter is concerned. For example if you have the function:
function MyFunction(paramA, paraB) {
// do nothing
}
All of these calls will execute without error:
MyFunction(1,2);
MyFunction(1);
MyFunction();
So you could modify handleOnClickConfirmed to accept what would essentially be an optional parameter. Like so:
function handleOnClickConfirmed(context) {
context = context || this;
// use context instead of 'this' through the rest of your code
}
Again, in this particular case, the call function is the best solution. But the technique I outlined above is a good one to have in your toolbox.
Related
How can I alter the code I have to accomplish to goal of referencing the original event (or properties/methods of it) in the subsequent code.
tViz.addEventListener(tableauSoftware.TableauEventName.MARKS_SELECTION, onMarksSelection);
function onMarksSelection(marksEvent) {
return marksEvent.getMarksAsync().then(showMarksHelp);
}
function showMarksHelp(marks){
I want to access a method from the marksEvent variable but here I can only access
the marks variable from getMarksAsync(). I need it here because stuff will also be
based on the marks variable contained here.
}
I feel like I could restructure my JS to accomplish this or pass something in somewhere, but after reading about Promises and the Tableau documentation I'm still lost. I don't want to use any global variables.
EDIT
So I changed my code to the below:
function onMarksSelection(marksEvent) {
var marks = marksEvent.getMarksAsync().then(function(marks){
showMarksHelp(marks,marksEvent)
});
}
function showMarksHelp(marks,marksEvent){}
Is this a/the correct method to accomplish what I did? I just made it up but it seems like it works.
Using an anonymous function, you can capture the value of marksEvent and then call showMarksHelp with two arguments.
function onMarksSelection(marksEvent) {
return marksEvent.getMarksAsync().then(function(marks) {
// marksEvent is accessible in here
showMarksHelp(marksEvent, marks);
});
}
function showMarksHelp(marksEvent, marks) {
// content of showMarksHelp here
}
I am trying to create a basic javascript framework that you can pass different things into, including functions for it to execute later. Right now, I'm in a more simple testing phase, but I can't quite get the function calling to work. A piece of my code is here:
[My JS Fiddle][1]http://jsfiddle.net/mp243wm6/
My code has an object that holds different data, and I want to call the method later, but with data that is available at the time of creation. Here is a code snippet of the function that uses the function that is passed to the object:
clickMe : function() {
this.obj.click(function() {
this.func();
});
}
Any suggestions or things I should read are welcome.
The problem is that there're two different contexts:
clickMe : function() {
// here is one
this.obj.click(function() {
// here is another
this.func();
});
}
You can simple pass the function as parameter, like the following:
clickMe : function() {
this.obj.click($.proxy(this.func, this));
}
http://jsfiddle.net/mp243wm6/2/
The problem:
Considering your code in the JSFiddle, you have:
onClick : function() {
this.obj.click(function() {
this.func();
});
},
As noted, you have different contexts going on here.
Consider the snippet this.obj.click(function() { this.func(); }). The first this here is a reference to the framework.events object. The second this here is a reference to whatever will be this when this function get called. In the case of your JSFiddle, when this.func gets called, this is actually the DOM object that represents the <div id="test">TEST</div> node. Since it doesn't have a func function, calling func() on it causes:
Uncaught TypeError: undefined is not a function
You have to understand the following: you have to pass the correct this in which you want the function func to be called.
The solution:
A couple of ways to make it work as you would like:
1. with bind
this.obj.click(this.func.bind(this));
This way, you are telling: "call my this.func function, but make sure that it will be called using the this that I am passing as a parameter". Vanilla JS, no $.proxy stuff.
JSFiddle
2. with a copy of the reference to the actual function
onClick : function() {
var theFunctionReference = this.func;
this.obj.click(function() {
theFunctionReference();
});
},
This way, you will not rely on the value of this outside of the context of the framework.events object.
JSFiddle
The issue is that this is not bound to the correct object. I would suggest you look into Function.bind() because that creates a function with this pointing to the right thing.
Let's start from the code:
function say(name) {
var ghost=function () {
function ghost() {
alert('!');
};
return body;
};
eval("var body=''+"+name+';');
eval(name+('=('+ghost).replace('body', body)+')();');
eval(name+'();');
}
function Baal() {
if ('undefined'===typeof ghost) {
say('Baal');
return;
}
ghost();
}
say('Baal'); // or just Baal();
Looks like that saying the devil's name invoke his presence (well, maybe he needs somebody for spiritual possession) ..
As you can see the ghost doesn't exist along with Baal, but we can invoke it since there're evals in say(name).
say(name) reassigns Baal to its code body as a closure and makes it captured a ghost method, that's how things work. But I'm trying to avoid eval ..
So .. let me reword the question:
How do I make a nonexistent(and not a member or global) method invocable without using eval?
Let me rephrase your question, just to make sure I’ve got it. Given a function, you want to put a new variable in its scope, without that scope being the global scope or a scope shared between the caller and the subject, without using eval (or the equivalent new Function and other hacks depending on the environment).
You can’t.
In the case you just mentioned, you could define one function, base(), that uses arguments.callee.caller.
Don’t do that.
The short answer: You don't.
That scope is not available. If you were to attach the scope then it would be available inside of the scope used. You could then access the method handles. I assume this is not what you were looking for, but here is what that would look like. demo
function say(name){
var methods = {};
methods.Baal = function(){
alert("!");
};
return methods[name];//this could invoke as well: methods[name]()
}
var handle = say('Baal');
handle();
What your evals break down to is something along these lines (although with dynamic content from string building - this is the end result)
function say(name) {
var Baal = (function () {
function ghost() {
alert('!');
};
return function(){
if ('undefined'===typeof ghost) {
say('Baal');
return;
}
ghost();
}
})();
Baal();
}
say('Baal'); // or just Baal();
Note that the meat of what happens here is from the function Baal, namely that it calls a hardcoded ghost() which in turn calls a hardcoded alert. Why go through all of this trouble to access a hardcoded function?
A better way would be to inject this function as a callback which expects some parameters to be injected.
jsFiddle Demo
function say(callback){
var params = "!";
if( typeof callback == "function" ){
callback(params);
}
}
say(function(params){
alert(params);
});
It's very difficult for me to read through your code and figure out what you are trying to accomplish with it, but it appears that you are trying to introduce a variable into the current scope so that you can call it. You cannot do this in javascript with the method that you demonstrated. Scoping only ever "flows down". By that I mean that a variable or function defined within a function will only be available to that function and any other functions defined therein. Your function named ghost will only ever be available within the function where it is defined, regardless of when that function is evaluated.
What you can do, however, is write a function that returns a function. You can then call that function and assign the result to a variable in the scope where you want to expose functionality. Doing that would look something like this.
function defineSpecialAlert() {
return function(name) {
alert(name + "!");
};
}
var newlyDefinedMethod = defineSpecialAlert();
newlyDefinedMethod("Baal");
So if I understand, it seems like you want to create an alias of eval: Something like
#Note this code is not intended as a solution, but demonstrates
#an attempt that is guaranteed to fail.
#
function myAlias(ctx) {
eval.call(ctx, 'var ghost = 42');
}
myAlias(this);
alert(ghost);
Javascript allows many funky sleight-of-hand tricks especially with closures, but this is maybe the one impossible thing that javascript cannot do. I've tried at length to do this exact same thing, and I can tell you that you'll run into nothing but complaints from the browser, saying that eval cannot be re-contexted or aliased in any way.
The documentation of jQuery's hover shows only one method of using the function:
$('.myClass').hover(function () {
console.log('on mouse over');
},
function () {
console.log('on mouse out');
});
However, when you change these to named functions it doesn't work correctly, firing the named functions upon page load (or as soon as you paste it into your console):
function onMouseOver() {
console.log('on mouse over');
}
function onMouseOut()
console.log('on mouse out');
}
$('.myClass').hover(onMouseOver(), onMouseOut());
Changing the last line to:
$('myClass').hover(onMouseOver, onMouseOut);
works as expected (firing on the event), but doesn't allow me to pass anything to the named functions. Is there any way to allow me to pass a variable to the functions?
Yes you need to use anonymous functions for this:
$('myClass').hover(function( e ) {
onMouseOver( param1, param2... );
}, function( e ) {
onMouseOut( param1, param2... );
});
You can pass variables into the named functions by calling it like so:
$('.myClass').hover(function() {
onMouseOver(arg);
}, function() {
onMouseOut(arg);
});
That's the only way to pass arguments, parameters into the named functions from that event.
The hover sugar method isn't really meant for complex scenarios.
In your case it would probably be better to use .on('mouseenter') and on('mouseleave') so that you can pass additional event data to each method, like
$('.myClass').on('mouseenter', {param1: val1}, onMouseOver).on('mouseleave', {param2: val2}, onMouseOut);
Then within your handlers you can access those params like so:
function onMouseOver(e) {
console.log(e.data.param1);
}
function onMouseOut(e) {
console.log(e.data.param2);
}
That's the sort of jQuery way to do it.
This is a problem with function references vs. function invocation. Adding the "()" invokes the function (which in this case you'd be doing at binding time...effectively binding the result of the function rather than the function itself.
To pass arguments the simplest option would be to wrap the named function in an anonymous function (as #antyrat just posted).
And also no, this is not a quirk of hover, this is standard JavaScript (and most any other language that has first class functions).
As several others have noted, you'll have to use currying or binding to pass values to the functions. In the example where you wrote this:
$('.myClass').hover(onMouseOver(), onMouseOut());
you're actually calling the onMouseOver() and onMouseOut() methods immediately on that line, and not when the mouse actually moves over or out of the element; what you wrote is equivalent to writing this:
var mouseOverResult = onMouseOver();
var mouseOutResult = onMouseOut();
$('.myClass').hover(mouseOverResult, mouseOutResult);
That's definitely not what you want.
jQuery can only understand functions that are of the form function(event), so if you want more parameters, or other parameters, you'll have to use currying to get them in there. Currying (named for the math professor who devised the concept) can be thought of as creating a new function where the values you want to pass are 'pre-bound' inside it.
So let's say you have a variable foo that you'd like to pass into your onMouseOver handler, like this:
function onMouseOver(foo) {
...
}
...
var foo = "Hello, World";
$('myClass').hover(...);
To be able to pass that value, you need another function that wraps up that foo and that onMouseOver with a function signature that jQuery can use. You do it like this:
function onMouseOver(foo) {
...
}
...
var foo = "Hello, World";
var curriedOnMouseOver = function(event) {
onMouseOver(foo);
};
$('myClass').hover(curriedOnMouseOver);
As several others have suggested, you can avoid the extra variable declaration by creating the curried closure inside the hover() call:
function onMouseOver(foo) {
...
}
...
var foo = "Hello, World";
$('myClass').hover(function(event) {
onMouseOver(foo);
});
This example above also shows how you would pass the event to your function as well, by simply adding more parameters to it:
function onMouseOver(event, foo, bar) {
...
}
...
var foo = "Hello, World";
var bar = "Goodbye, World";
$('myClass').hover(function(event) {
onMouseOver(event, foo, bar);
});
JavaScript's functions --- or closures --- are incredibly powerful tools, and it would be worth your while to learn some of the things you can do with them, like these examples.
How do I make the myFunction visibile for the in-line function in .ready() event?
$(document).ready(function() {
...stuffs...
myFunction(par1, par2, anotherFucntion_callback);
}
);
function anotherFunction_callback(data) {
..stuffs..
}
I didn't quite catch your question. Do you mean that you want to pass "myFunction_callback(data)" as the last argument in your:
myFunction(par1, par2, anotherFunction_callback);
, including that "data" parameter?
In that case the solution is pretty standard, write this before that one:
var temp = function() { anotherFunction_callback(data) };
an alternative syntax is:
function temp() { myFunction_callback(data) };
// even though this looks just like a free function,
// you still define it inside the ready(function())
// that's why I call it "alternative". They are equivalent.
In general, if you want to pass a function with 1 or more arguments to another function, you use that format. Here, we basically create a new no-argument function that calls another. The new function has access to the "data" variable. It's called "closure", you may want to read more on that.
Of course, if the callback require no argument, you can just use the original function name.
I hope this helps.
ps: You can even inline the function declaration, making it anonymous, like so:
myFunction(par1, par2, function() { myFunction_callback(data) });
Notice that the
$(document).ready(function() {});
looks pretty much just like that.
You use the actual name of the function, i.e. myFunction_callback instead of myFunction or anotherFucntion_callback.