Dealing with `return false` when using `eval`in JavaScript
I'm dealing with a situation where I need to bind jQuery events to a page to handle UI updates that is being generated via JSF. Alas, JSF sucks and it sticks onclick events on everything, which pre-empt any of my jQuery work.
Thanks to the fine folks here on SO, I found a solution for that:
mixing my jQuery click events with existing object's onclick attribute
The logic is:
- on page load, grab all of the
onclick
attributes and store them in a variable. - bind my jQuery events
- after my own jQuery events, I can then
eval
the original onclick:eval(onclickValueVariable)
This worked find in all my dummy onclick event testing.
But then it failed on that live JSF page. The issue is that all of the onclick's end with a 'return false' which leads to this error:
return not in function
For example:
<div class="uglyJSFcreatedTag" onclick="console.log('hey');return false">
And the jQuery that would fire it:
var $jsfT开发者_运维知识库ag = $('.uglyJSFcreatedTag');
var cacheOnclick = $jsfTag.attr(onclick);
$jsfTag.removeAttr('onclick');
...bunch of logic here to decide if the inline onclick should fire and, if so...
eval(cacheOnclick)
The problem is the return false
. It looks like nothing can be returned when firing a eval.
Is there a solution for this? I imagine I could take the value as a string and do string parsing to remove all return false, then call that from the script itself. But that sounds a bit messy. Is there a more elegant solution?
If not, what particular JS statements should I be looking for to filter out before calling eval
?
Two options:
1. Using your current approach, just wrap the onclick string with a function before you eval it. Store the result, and call it as a function. Make sure you call it with the appropriate this
context:
var f = eval("(function(){ "+cacheOnclick+"})");
f.call($jsfTag[0]);
Note: the parenthesis ()
around the function declaration, are required within the eval. This makes the declaration into an expression (syntactically speaking), thus making it legal in in the eval.
2. instead of grabbing the onclick attribute, grab the actual function from the dom element itself. Also, unless you need to do something special with the jsf handler function from your code, I would suggest that you just add the JSF function as a jquery click handler directly, rather than calling it explicitly from your code:
var $jsfTag = $('.uglyJSFcreatedTag');
$jsfTag.bind('click', $jsfTag[0].onclick);
$jsfTag.removeAttr('onclick');
Personally, I would go for approach #2, but either one should work.
Update: Here's a litte additional explanation for Option #2:
var $jsfTag = $('.uglyJSFcreatedTag');
That's from your example -- we're using jquery to retrieve a set containing all elements with the classname 'uglyJSFcreatedTag'.
$jsfTag.bind('click', $jsfTag[0].onclick);
This fetches the first element from the set ($jsfTag[0]
), and gets the onclick
property of that element object. .onclick
is a javascript property that contains a reference to the compiled function that the browser generated from the "onclick" attribute's string content. Now, since we have the handler function, we can bind it directly to the jquery click event, using the jquery bind()
function.
$jsfTag.removeAttr('onclick');
Finally, we just remove the attribute. This is ok, because we've already bound the function via jquery. If we don't remove it from the "old-style" onclick, then it'll get called twice.
Note: You may have noticed that the above code only works on the first element in the selected set. For the very likely case that your set contains multiple elements, you'll want to loop through the set and handle each element separately. Here's how you would do that:
var $jsfTag = $('.uglyJSFcreatedTag');
$jsfTag.each( function(idx, element) {
$(element).bind('click', element.onclick).removeAttr("onclick");
});
If you want to skip the jsf handler for certain elements, then insert logic within the "each loop" to test for this, and skip the call to bind accordingly.
If you must continue to call the jsf handler from within your click handler, then at least consider using element.onclick
instead of $(element).attr('onclick')
. The latter requires eval, while the former does not.
I think wrapping the code in an anonymous function and invoke it right away should be easier than filtering out return statements.
evalCode = "function() {" + evalCode + "}()";
精彩评论