Preface
In fact, browsers come with many built-in events, and many events automatically trigger certain behaviors in the browser.
For example:
- Clicking a link triggers navigation to the URL.
- Clicking the submit button in a form triggers submission to the server.
- Pressing and dragging the mouse button on text selects the text.
When handling an event with JavaScript, we usually don’t want the corresponding browser behavior to occur. Instead, we want to implement alternative behaviors.
Preventing Browser Behavior
There are two ways to tell the browser that we don’t want it to execute default behavior:
- The common way is to use the event object, which has a
event.preventDefault()
method. - If the handler is assigned using
on<event>
(rather thanaddEventListener
), returning false is also effective.
In the example below, clicking the link will not trigger navigation, and the browser will not perform any action:
<a href="/" onclick="return false">Click here</a>
or
<a href="/" onclick="event.preventDefault()">here</a>
It’s important to note that using on<event>
and returning false is not a good practice.
Returning false from the handler is an exception. The return value of event handlers is usually ignored. The only exception is returning false from a handler assigned using on<event>
.
Handler Option “passive”
The optional passive: true
option in addEventListener
signals to the browser that the handler will not call preventDefault()
.
Why is this necessary?
On mobile devices, some events like touchmove
(when the user moves their finger on the screen) can lead to scrolling by default, which can be prevented using preventDefault()
in the handler.
So, when the browser detects such events, it must first process all handlers. If preventDefault
is not called anywhere, the page can continue scrolling. However, this may cause unnecessary delays and “jitter” in the UI.
The passive: true
option informs the browser that the handler will not cancel scrolling. The browser then immediately scrolls the page to provide a smoother experience and somehow processes the event.
For certain browsers (such as Firefox and Chrome), touchstart
and touchmove
events have passive set to true by default.
event.defaultPrevented
If the default behavior is prevented, the event.defaultPrevented
property is true; otherwise, it is false.
Here’s an interesting use case.
Do you remember our discussion on event.stopPropagation()
in the Bubbling and Capturing chapter and why stopping propagation is not good?
Sometimes, we can use event.defaultPrevented
as an alternative to notify other event handlers that the event has been handled.
Let’s look at a practical example.
By default, the browser displays a context menu with standard options on the contextmenu
event (right-click). We can prevent it and display our custom menu like this:
<button>Right-click shows browser context menu</button>
<button oncontextmenu="alert('Draw our menu'); return false">
Right-click shows our context menu
</button>
Now, in addition to this context menu, let’s implement a document-wide context menu.
When right-clicking, it should display the nearest context menu:
<p>Right-click here for the document context menu</p>
<button id="elem">Right-click here for the button context menu</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Button context menu");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Document context menu");
};
</script>
Right-click here for the document context menu
Issue and Solutions
The problem arises when clicking on elem
, and we get two menus: the button-level menu and the document-level menu due to event bubbling.
One solution is to prevent the event from bubbling up when handling the right-click event in the button. We can achieve this using event.stopPropagation()
:
<p>Right-click for the document menu</p>
<button id="elem">Right-click for the button menu (fixed with event.stopPropagation)</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
event.stopPropagation();
alert("Button context menu");
};
document.oncontextmenu = function(event) {
event.preventDefault();
alert("Document context menu");
};
</script>
Now, the button-level menu works as expected. However, this comes at a cost — we deny any external code access to right-click information, including counters for collecting statistics. This is not advisable.
Another alternative is to check whether the document handler has prevented the browser’s default behavior. If it has, the event has already been handled, and we don’t need to react to it:
<p>Right-click for the document menu (added a check for event.defaultPrevented)</p>
<button id="elem">Right-click for the button menu</button>
<script>
elem.oncontextmenu = function(event) {
event.preventDefault();
alert("Button context menu");
};
document.oncontextmenu = function(event) {
if (event.defaultPrevented) return;
event.preventDefault();
alert("Document context menu");
};
</script>
Now everything works as expected. If we have nested elements, each with its own context menu, this approach will work. Just make sure to check event.defaultPrevented
in each contextmenu
handler.
event.stopPropagation() and event.preventDefault()
As we’ve seen,event.stopPropagation()
andevent.preventDefault()
(also considered asreturn false
) are two different things. They are unrelated to each other.
Nested Context Menu Structure
There are other ways to implement nested context menus. One approach is to have a global object with adocument.oncontextmenu
handler and a method to store other handlers. This object would capture any right-click, browse stored handlers, and run the appropriate one.
However, every piece of code needing a context menu should be aware of this object and use its assistance rather than having its own contextmenu
handler.
Summary
There are many default browser behaviors:
mousedown
— Starts selection (dragging the mouse for selection).click
on<input type="checkbox">
— Selects/deselects the input.submit
— Clicking<input type="submit">
or pressing Enter in form fields triggers this event, leading the browser to submit the form.keydown
— Pressing a key adds a character to a field or triggers other actions.contextmenu
— Event occurs on right-click, triggering the default behavior of showing the browser context menu.- …and many more…
If we want to handle events using JavaScript only, all default behaviors can be prevented.
To prevent default behavior, you can use event.preventDefault()
or return false
. The second method is only applicable to handlers assigned through on<event>
.
The passive: true
option of addEventListener
informs the browser that the behavior will not be prevented. This is useful for certain mobile events (like touchstart
and touchmove
) to let the browser scroll without waiting for all handlers to finish.
If the default behavior is prevented, the value of event.defaultPrevented
becomes true; otherwise, it is false.
Comments