event.preventDefault() vs. return false (no jQuery)
Here are a few examples that might help people to understand and troubleshoot their problems better.
TL;DR
on*
event handlers (e.g.onclick
attribute on a button element): return false to cancel eventaddEventListener
is a different API, return values (e.g.false
) are ignored: useevent.preventDefault()
.onclick="<somejs>"
has its own potential confusion because<somejs>
is wrapped into the body of an onclick function.- Use the browser devtools
getEventListeners
API to see what your event listener looks like to troubleshoot if your event handler is not behaving as expected.
Example
This example is specific to the click
event with an <a>
link...but can be generalized for most event types.
We have an anchor (link) with class js-some-link-hook
that we want to open a modal and prevent any page navigation from happening.
The below examples were run in google chrome (71) on MacOS Mojave.
One major pitfall is assuming that onclick=functionName
is the same behaviour as using addEventListener
Let's say we have an anchor tag (link) that we want to handle with javascript when javascript is enabled. We don't want the browser to follow the link when clicked ("prevent default" behaviour).
<a href="https://www.example.com/url/to/go/to/when/no/javascript"
class="js-some-link-hook">click me!</a>
onclick attribute
function eventHandler (event) {
// want to prevent link navigation
alert('eventHandler ran');
return false;
}
function addEventListenerToElement () {
var link = document.querySelector('.js-some-link-hook');
link.setAttribute('onclick', eventHandler);
}
addEventListenerToElement();
Then run in the browser devtools console:
var el = document.querySelector('a.js-some-link-hook'),
listener = getEventListeners(el).click[0].listener;
console.log(''+listener); // to avoid truncation of output
...and you see:
function onclick(event) {
function eventHandler (event) {alert('eventHandler ran'); return false;}
}
This doesn't work at all. When using onclick=
your handler function is wrapped in another function.
You can see that my function definition is included but not called because I specified the function reference without invoking it. i.e. we need onclick="functionName()"
not onclick="functionName"
to make sure functionName
is run when the element is clicked.
Further you can see that even if my function was called and my function returned false...the onclick
function would not return that false value...which is required to 'cancel' the event.
In order to fix this, we can set onclick
to be return myHandlerFunc();
which ensures that onclick
returns the return value (false) from myHandlerFunc
.
You could also remove the return false;
from myHandlerFunc
and change the onclick
to be myHandlerFunc(); return false;
but this makes less sense as you probably want to keep the logic together in your handler function.
Note when setting onclick
with javascript, when you are setting onclick
directly in the html rather than with javascript (like my examples), the onclick
attribute value is type string and everything works. If you are setting onclick
using javascript, you need to pay attention to type. If you say element.setAttribute('onclick', myHandlerFunc())
myHandlerFunc
will be run right now and the result will be stored in the attribute...rather than running on each click. Instead you should make sure the attribute value is set as a string. element.setAttribute('onclick', 'return myHandlerFunc();')
Now that we see how that is working, we can modify the code to do whatever we want. Odd example for illustration purposes (don't use this code):
function eventHandler (e) {
// want to prevent link navigation
alert('eventHandler ran');
console.log(e);
return false;
}
function addEventListenerToElement () {
var link = document.querySelector('.js-some-link-hook');
link.setAttribute('onclick', 'return ('+eventHandler+')(event);');
}
addEventListenerToElement();
You see we have wrapped our eventHandler function definition into string. Specifically: a self-executing function with a return statement at the front.
Again in chrome devtools console:
var el = document.querySelector('a.js-some-link-hook'),
listener = getEventListeners(el).click[0].listener;
console.log(''+listener); // to avoid truncation of output
...shows:
function onclick(event) {
return (function eventHandler (e) {
// want to prevent link navigation
alert('eventHandler ran');
console.log(e);
return false;
})(event);
}
...so yeah, that should work. Sure enough if we click on the link we get the alert and dismissing the alert the page doesn't navigate anywhere or refresh.
One more note about onclick
... If you want to receive and utilize the event
parameter at the time of the event, you should note that it is named "event". You can access this within your handler using the name event
(available via parent onclick
function scope). Or you can construct your handler to take event
as a parameter (better for testing)...e.g. onclick="return myEventHandler(event);"
or as you see in the previous example.
addEventListener
function eventHandler (ev) {
// want to prevent link navigation
alert('eventHandler ran');
console.log(ev);
return false;
}
function addEventListenerToElement () {
var link = document.querySelector('.js-some-link-hook');
link.addEventListener('click', eventHandler, false);
}
addEventListenerToElement();
browser devtools:
var el = document.querySelector('a.js-some-link-hook'),
listener = getEventListeners(el).click[0].listener;
console.log(''+listener); // to avoid truncation of output
result:
function eventHandler (ev) {
// want to prevent link navigation
alert('eventHandler ran');
console.log(ev);
return false;
}
So you can already see the difference. With addEventListener
we aren't wrapped in an onclick
function. Our handler receives the event
parameter directly (and so we can call it whatever we want). Also our return false
is at the "top level" here and we don't have to worry about adding an extra return statement like with onclick
.
So it looks like it should work. Clicking on the link we get the alert. Dismiss the alert and the page navigates/refreshes. ie the event was NOT cancelled by returning false.
If we lookup the spec (see resources at bottom), we see that our callback/handler function for addEventListener does not support a return type. We can return whatever we want, but since it isn't part of the browser API/interface, it doesn't have any effect.
Solution: using event.preventDefault()
instead of return false;
...
function eventHandler (ev) {
// want to prevent link navigation
ev.preventDefault();
alert('eventHandler ran');
}
function addEventListenerToElement () {
var link = document.querySelector('.js-some-link-hook');
link.addEventListener('click', eventHandler, false);
}
addEventListenerToElement();
browser devtools...
var el = document.querySelector('a.js-some-link-hook'),
listener = getEventListeners(el).click[0].listener;
console.log(''+listener); // to avoid truncation of output
gives...
function eventHandler (ev) {
// want to prevent link navigation
ev.preventDefault();
alert('eventHandler ran');
}
...as expected.
Testing again:
- Click link.
- Get the alert.
- Dismiss alert.
- No page navigation or refresh happens...which is what we want.
So with addEventListener
use event.preventDefault()
as returning false does nothing.
Resources
- https://www.w3.org/TR/dom41/#dom-eventtarget-addeventlistener
- Event Registration (e.g. addEventListener)
- https://www.w3.org/TR/dom41/#callbackdef-eventlistener
- callback/handler/listener def.
- NOTE return type is void. ie return value is not part of the interface and has no effect.
- https://www.w3.org/TR/dom41/#event
- Event Object (e.g. Event.preventDefault)
The html5 spec (https://www.w3.org/TR/html5/webappapis.html#events) confuses things because they use both onclick
and addEventListener
in their examples and they say the following:
The event handler processing algorithm for an event handler H and an Event object E is as follows:
...
- Process return value as follows:
...
If return value is a Web IDL boolean false value, then cancel the event.
So it seems to imply that return false
cancels the event for both addEventListener
and onclick
.
But, if you look at their linked definition of event-handler
you see:
An event handler has a name, which always starts with "on" and is followed by the name of the event for which it is intended.
...
Event handlers are exposed in one of two ways.
The first way, common to all event handlers, is as an event handler IDL attribute.
The second way is as an event handler content attribute. Event handlers on HTML elements and some of the event handlers on Window objects are exposed in this way.
https://www.w3.org/TR/html5/webappapis.html#event-handler
So it seems that the return false
cancelling the event really does only apply to the onclick
(or generally on*
) event handlers and not to event handlers registered via addEventListener
which has a different API.
Since the addEventListener
API is not covered under the html5 spec (only the on*
event handlers)...it would be less confusing if they stuck to the on*
style event handlers in their examples.
The W3C Document Object Model Events Specification in 1.3.1. Event registration interfaces states that handleEvent
in the EventListener has no return value:
handleEvent
This method is called whenever an event occurs of the type for which the EventListener interface was registered. [...] No Return Value
under 1.2.4. Event Cancelation the document also states that
Cancelation is accomplished by calling the Event's preventDefault method. If one or more EventListeners call preventDefault during any phase of event flow the default action will be canceled.
which should discourage you from using any effect that returning true / false could have in any browser and use event.preventDefault()
.
Update
The HTML5 spec actually specifies how to treat a return value different. Section 7.1.5.1 of the HTML Spec states that
If return value is a WebIDL boolean false value, then cancel the event.
for everything but the "mouseover" event.
Conclusion
I would still recommend to use event.preventDefault()
in most projects since you will be compatible with the old spec and thus older browsers. Only if you only need to support cutting edge browsers, returning false to cancel is okay.