How is inline onclick evaluated? - javascript

I am curious about how the content of the inline element attribute event works under the hood.
We start off with a simple function
function handler(e) {
console.log(e);
}
Use Case 1
If we would want to add this handler to our button we could do:
<button onclick="handler();">Click Me</button>
Which Logs on click: undefined
Fair enough, we called a function without passing arguments.
Use Case 2
If we now change the button to take the event as an argument:
<button onclick="handler(event);">Click Me</button>
We log on click: MouseEvent{ ... }
Now we log the event, as we could have expected
Use Case 3
But what if we just specify the function name?
<button onclick="handler">Click Me</button>
Nothing happens!, I was hoping just giving a reference to a function, would do the trick. So when "onlick" would happen, the referred function would be called with the proper event argument.
Use Case 4
When we add the handler in javascript:
const btn = document.getElementById('btn');
btn.onclick = handler;
it works, capturing the event and logs the related event object.
Concluding Question
Whatever text/content/statement that we pass to onclick="" inline, does it basically run eval() on it? And when we attach it in javascript (btn.onclick = ...) it goes through the proper process to intercept the event and then call bounded handler function and passes the corresponding event object with it (e.g. MouseEvent)?
Quick Note
To be clear, this is an informative question, to understand the fundamentals, disregarding if it's considered a bad or good practice. That's not being discussed at this point.

Yes, in an inline handler, the attribute text is essentially just evaled, with a few qualifications. What
<button onclick="handler">Click Me</button>
is roughly equivalent to would be if you had a (true) handler which referenced the handler function but didn't call it, for example:
btn.onclick = () => {
handler;
};
This doesn't throw an error (because handler is referenceable), but it doesn't run handler either, because you're only referencing the function, not calling it.
When you do
btn.onclick = handler;
you are passing a reference to handler to the onclick internals, which then call that reference later, when the button is clicked.
(If you had done btn.onclick = handler;, handler would be invoked immediately, and its return value would be added as a listener, which isn't what you'd want)
When you do
<button onclick="handler(event);">Click Me</button>
the event argument you're passing comes from window.event, which is why it's referenceable.
Like I said, there are a few qualifications, though: inline handlers implicitly use something like a with(this) around the whole text that is run, where this is the element in question. (it also uses with for the document and containing form, as you can see from Quentin's answer) For example:
<a onclick="search();">Click me!</div>
looks, to the interpreter, a bit like:
<a onclick="
with(this) {
search();
}
">Click me!</div>
which is problematic, because search is a property of HTMLAnchorElement.prototype. Referencing other variable names can have similar problems, if those variable names are properties anywhere on the element's prototype chain.
Best to avoid inline handlers entirely, and attach the event properly using Javascript instead.

This is not what happens, but you can imagine it like this:
Imagine that the javascript engines saves the content of the onclick attribute as a string, and when a user clicks the element, then evaluates the content.
For example:
function foo(a, b) {
alert(a + ' ' + b);
}
<button onclick="foo('hello', 'world')">click me</button>
<button onclick="(function () { alert ('I\'m an inline IIFE') })()">click me</button>
It's the same result of eval("foo('hello', 'world')". What if you pass event or this or other keywords?
Well, also here, you can imagine a javascript engines doing this:
var this = /* create context */;
var event = /* create event */;
eval("foo(this, event)");
This is what the specification says, the attribute needs to be evaluated! I'm not saying that the engine uses eval :P
Example:
<button onclick="alert('hello world')">Click me</button>
What about btn.onclick = handler;?
This is another pair of shoes. The onclick property is a callback, and it wants a function reference.

When you use HTML attribute for the event, you actually create a function under the hood which assigned as the handler of the relevant event.
The body of the function is the content of the attribute. It should be JavaScript code.
The function gets as parameter the event parameter
So,
In case 1, The generated handler :
function handlerHtml(event) {
handler()
}
In case 2, The generated handler :
function handlerHtml(event) {
handler(event)
}
In case 3, The generated handler :
function handlerHtml(event) {
handler
}

Related

button onclick="e.preventDefault(); return true;" gives e is not defined in browser console

This is my button element:
<button id="loginButton"
...
onclick="e.preventDefault(); return true;"/>
But this approach gives me after login button click error in console:
Uncaught ReferenceError: e is not defined
Do you know how to fix my button to avoid "e is not defined" error in console?
e is a variable commonly used in event handlers. However you never defined e in your code. I recommend using an event listener in JS.
document.getElementById('loginButton').addEventListener('click', function(e) {
e.preventDefault();
return true;
});
This will accomplish what you need.
in events (like onclick="...") you can specify either a function name, then this function has to have a single parameter, where the event object will be passed.
Or you can specify an JavaScript expression (as in your case), in this case there will be created implicit function (event) { ... } with your code placed inside.
I.e. in that case you have to use event (instead of e) as the name of the event object.

Type error: "Not a function" using "this" keyword in a function

I have created the following function.
function showAllSelectOpts(select)
{
selectLength = select.children().length;
select.attr('size',selectLength);
select.css('height','auto');
select.focusout(function(){
select.attr('size','1');
});
}
When it is called directly on a select element like this showAllSelectOpts(mySelect); it works fine, but when called within another function, as below using the keyword "this", it returns the error. Type error: select.children not a function
$('select').on('focus',function(){
showAllSelectOpts(this);
})
Is this a scope issue or what, and how can I resolve it?
In an event handler, this is a reference to the DOM element, not a jQuery object. But your showAllSelectOpts expects its argument to be a jQuery object.
Either change the call to wrap the DOM element with $():
showAllSelectOpts($(this));
...or update showAllSelectOpts to do so itself:
function showAllSelectOpts(select)
{
select = $(select); // ***
selectLength = select.children().length;
select.attr('size',selectLength);
select.css('height','auto');
select.focusout(function(){
select.attr('size','1');
});
}
Side note: As A.Wolff points out, your function attaches a new focusout handler to the select every time it's called. You only want one.
I'd remove that part of the handler entirely, and replace it with a single focusout:
function showAllSelectOpts(select)
{
var selectLength = select.children().length;
select.attr('size',selectLength);
select.css('height','auto');
}
$('select')
.on('focus',function(){
showAllSelectOpts($(this));
})
.on('focusout', function(){
$(this).attr('size', '1');
});
Also note that I added a var for selectLength in showAllSelectOpts (although actually, you could just remove the variable entirely); without one, the code is falling prey to The Horror of Implicit Globals (that's a post on my anemic little blog). Be sure to declare your variables.
jQuery event listener callbacks set this as the HTMLElement that the event was fired on.
In your callback you are expecting a jQuery object, but you have the HTMLElement.
You can pass the HTMLElement to a jQuery constructor and pass it into the showAllSelectOpts function
$('select').on('focus',function(){
showAllSelectOpts($(this));
})
Try this one -
$('select').on('focus',function() {
showAllSelectOpts($(this)); })
Try this:
var myselect = $('select');
$('select').on('focus',function(){
showAllSelectOpts(myselect);
})
A better way could be:
$('select').on('focus',function(event){
showAllSelectOpts($(event.target));
})
Why your code not working?
$('select').on('focus',function(){
//Here `this` is bound with the dom html element, not the $('select') object.
showAllSelectOpts(this);
})
These previous answers fix it. I'd just add here to create it as an extension since $(this) refers to a prototype of one method call.
$.fn.showAllSelectOpts=function() {
$(this).on('focus',()=>{
$(this)
.css('height','auto')
.attr('size',$(this).children().length)
.focusout(()=>{
$(this).attr('size','1');
});
});
};
$('select').showAllSelectOpts();

How to set a property of an element to contain a function using jQuery?

I want to assign a function to a custom attribute of a DOM-element. Using jQuery.
.prop(String, function) will not assign the function to the specified property, but will invoke the function and assign its result.
I have an ugly solution:
$("#a").prop(
"customAttr",
function() {
return function() {
$("#a").text("B");
};
}
);
http://jsfiddle.net/rapik/fyn6zh85/
Is there any better way to do it (using jQuery)?
I'm guessing that the reason you're trying to associate this function with these objects is so that you can cause the function to be invoked when something specific happens in the application. A better way to do this is to use events.
// set up the event handler
$("#a").on('somecustomevent', function() { $(this).text("B"); });
// Then, where you want that function to be invoked, trigger it.
$("#a").trigger('somecustomevent');

Function (..) throws 'eval is evil' message

I'm not using eval, and I'm not sure what the problem is that Crockford has with the following. Is there a better approach to solve the following problem or is this just something I need to ignore (I prefer to perfect/improve my solutions if there is areas for improvement).
I'm using some pixel tracking stuff and in this case a client has bound a JS function to the onclick property of an HTML image tag which redirects off the site. I need to track the clicks reliably without running into race conditions with multiples of event listeners on the image. The strategy is to override the event at run time, copying and running it in my own function. Note this is being applied to a site I do not control and cannot change. So the solution looks something like:
...
func = Function(img.attr('onclick'));
...
img.attr('onclick', '');
... //some custom tracking code
func.call(this);
and the JSLint checker throws the eval is evil error.
Is there a better way to avoid race conditions for multiple events around href actions?
You're implicitly using eval because you're asking for the callback function as it was specified as an attribute in the HTML as a string and then constructing a Function with it.
Just use the img.onclick property instead, and you will directly obtain the function that the browser built from the attribute that you can then .call:
var func = img.onclick; // access already compiled function
img.onclick = null; // property change updates the attribute too
... // some custom tracking code
func.call(img, ev); // call the original function
or better yet:
(function(el) {
var old = el.onclick;
el.onclick = function() {
// do my stuff
..
// invoke the old handler with the same parameters
old.apply(this, arguments);
}
})(img);
The advantage of this latter method are two fold:
it creates no new global variables - everything is hidden inside the anonymous closure
It ensures that the original handler is called with the exact same parameters as are supplied to your replacement function
var oldClick = myImg.onclick;
myImg.onclick = function(evt){
// Put you own code here
return oldClick.call( this, evt );
};

Event keyword in js

I found this code working in Chrome, FF, and IE. However, I can't find any references to the "magic" event keyword on the web. See this
<html>
<head>
<script type="text/javascript">
function handler(e){
alert(e);
}
</script>
</head>
<body>
<h1 onclick="handler(event);alert(0)">Click on this text</h1>
</body>
</html>
This script STOPS working if I change event in brackets to something else. Is that a deprecated keyword?
The Event object is automatically passed to all event handlers. If you add event handlers with addEventListener, you can choose the parameter name (like you did in handler). But for code attached with the onclick attribute, you can only access the event object via the implicit variable event.
If you want to know more about event handling in general, I suggest to read about it at quirksmode.org.
Also have a look at MDC - Event handlers:
These are properties that correspond to the HTML 'on' event attributes.
Unlike the corresponding attributes, the values of these properties are functions (or any other object implementing the EventListener interface) rather than a string. In fact, assigning an event attribute in HTML creates a wrapper function around the specified code. For example, given the following HTML:
<div onclick="foo();">click me!</div>
If element is a reference to this div, the value of element.onclick is effectively:
function onclick(event) {
foo();
}
Note how the event object is passed as parameter event to this wrapper function.
When an event attribute is set, an implicit Function is created with event as the first argument. When the event fires, the event object is passed to that function. There wasn't a solid definition on this behavior until HTML 5 (which is still a draft), but it's been this way for a long time in the major browsers and that's how it made it into the spec:
When an event handler's Function object is invoked, its call() callback must be invoked with one argument, set to the Event object of the event in question.
That argument is aptly named event for obvious reasons. http://www.w3.org/TR/html5/webappapis.html#events
This really threw me. The following 'typo' works in Chromium, but not FF:
some_worker.onmessage = function(e) {
// do stuff
if (e.data instanceof ArrayBuffer)
intArray = new Uint8Array(event.data);
// do other stuff
};
Chromium was observing the implicit keyword for 'event' but FF threw an 'undefined variable' error! Just changed 'event' to 'e' and all is well.
I notice that even stackoverflow's code markup observes 'event' as an implicit keyword ... not to knock FF, since it detected the typo.
The word event is not only an "other reserved word" in JavaScript, it is a always reserved word in all JavaScript Events (Event). See here: https://developer.mozilla.org/en-US/docs/Web/API/Event
For an Event with a function like onclick="myFunction()", there are two solutions.
First: The onclick="myFunction(event)" passed the event to the function. This is similar by this.
You can test it with onclick="consloe.log(event)" and onclick="consloe.log(this)". event is here a reserved word equally this. this contains the (HTML) Element. event contains the Event.
Consider onclick="myFunction(evt)" don't work (Error: evt is undefined).
Whit the example below, the event passed not only into the myFunction(), but it can used as a variable inside the function. This is not necessary in simple matters like this, because event is always global inside the function.
example.com
<script>
function myFunction(evt) {
var confirmresult = confirm("Press a button!");
if (confirmresult == true) {
// OK = go away!
} else {
// Cancel = stay here!
evt.preventDefault(); // is in this case the same as event.preventDefault();
}
};
</script>
JSFiddle: https://jsfiddle.net/usfrc6dn/
Second: Nothing is vissible passed in the onclick="myFunction()". The event is used inside the function.
example.com
<script>
function myFunction() {
var confirmresult = confirm("Press a button!");
if (confirmresult == true) {
// OK = go away!
} else {
// Cancel = stay here!
event.preventDefault();
}
};
</script>
JSFiddle: https://jsfiddle.net/qczt587u/
Only the second methode is valide in the reference of the docs, because about the using of event as onclick="myFunction(event)" is nothing to find, respectively only for onclick="myFunction(this)". When you have other information, please make a correction.
When you use onclick="myFunction(event)", you can use the event inside the function as a variable like function myFunction(evt) { evt.preventDefault();}. But that have a little bit risk. Either, the event is automatically passed to functions that are embeeded or called inside the function. Also is only onclick="myFunction()" without risk and also valid.
Examples:
<div id="foo" onclick="foo();">click me!</div>
function foo(evt) {
console.log(evt); // undefined
console.log(event); // the Event "click" Object
}
<div id="foo" onclick="foo(evt);">click me!</div>
// ERROR: the variable "evt" is not defined!
<div id="foo" onclick="foo(event);">click me!</div>
function foo(evt) {
console.log(evt); // the Event "click" Object
console.log(event); // the Event "click" Object
}
<div id="foo" onclick="foo();">click me!</div>
function foo() {
console.log(this); // the Function "foo" Object
console.log(event); // the Event "click" Object
}
<div id="foo" onclick="foo(this);">click me!</div>
function foo() {
console.log(this); // the Function "foo" Object
console.log(event); // the Event "click" Object
}
<div id="foo" onclick="foo(this);">click me!</div>
function foo(element) {
console.log(element); // the Element "div" Object
console.log(this); // the Function "foo" Object
console.log(event); // the Event "click" Object
}
<div id="foo" onclick="foo(this);">click me!</div>
function foo(this) {
// ERROR: foo is not a function.
// Cause: 'this' is a reserved word.
console.log(element);
console.log(this);
console.log(event);
}
<div id="foo" onclick="foo(this);">click me!</div>
function foo(event) {
console.log(this); // the Function "foo" Object
console.log(event); // the Event "click" Object
}
There are also differences between this and event that are to consider.
Conclusion: Only onclick="myFunction()" is without risk and also valid in the matter of event.
I guess your are trying to pass the clicked HTML element to your handler() function. The correct way to do that i your code is:
<h1 onclick="handler(this);">Click on this text</h1>
However I strongly recommend avoid this approach. Avoid having styling and behavior defined on the HTML. Please read the following article:
Unobtrusive JavaScript

Categories