up
This commit is contained in:
parent
497c9fd2d3
commit
7b3f4550fd
2 changed files with 163 additions and 55 deletions
|
@ -77,24 +77,10 @@ If we omit `return false`, then after our code executes the browser will do its
|
|||
|
||||
By the way, using event delegation here makes our menu flexible. We can add nested lists and style them using CSS to "slide down".
|
||||
|
||||
## Other browser actions
|
||||
|
||||
There are many default browser actions.
|
||||
## Prevent futher events
|
||||
|
||||
Here are more events that cause browser actions:
|
||||
|
||||
- `mousedown` -- starts the selection (move the mouse to select).
|
||||
- `click` on `<input type="checkbox">` -- checks/unchecks the `input`.
|
||||
- `submit` -- clicking an `<input type="submit">` or hitting `key:Enter` inside a form field causes this event to happen, and the browser submits the form after it.
|
||||
- `wheel` -- rolling a mouse wheel event has scrolling as the default action.
|
||||
- `keydown` -- pressing a key may lead to adding a character into a field, or other actions.
|
||||
- `contextmenu` -- the event happens on a right-click, the action is to show the browser context menu.
|
||||
- ...
|
||||
|
||||
All the default actions can be prevented if we want to handle the event exclusively by JavaScript.
|
||||
|
||||
````warn header="Events may be related"
|
||||
Certain events flow one into another.
|
||||
Certain events flow one into another. If we prevent the first event, there will be no second.
|
||||
|
||||
For instance, `mousedown` on an `<input>` field leads to focusing in it, and the `focus` event. If we prevent the `mousedown` event, there's no focus.
|
||||
|
||||
|
@ -107,10 +93,129 @@ But if you click the second one, there's no focus.
|
|||
<input *!*onmousedown="return false"*/!* onfocus="this.value=''" value="Click me">
|
||||
```
|
||||
|
||||
That's because the browser action is canceled on `mousedown`. The focusing is still possible if we use another way to enter the input. For instance, the `key:Tab` key to switch from the 1st input into the 2nd.
|
||||
````
|
||||
That's because the browser action is canceled on `mousedown`. The focusing is still possible if we use another way to enter the input. For instance, the `key:Tab` key to switch from the 1st input into the 2nd. But not with the mouse click any more.
|
||||
|
||||
|
||||
## event.defaultPrevented
|
||||
|
||||
The property `event.defaultPrevented` is `true` if the default action was prevented, and `false` otherwise.
|
||||
|
||||
There's an interesting use case for it.
|
||||
|
||||
You remember in the chapter <info:bubbling-and-capturing> we talked about `event.stopPropagation()` and why stopping bubbling is bad?
|
||||
|
||||
Sometimes we can use `event.defaultPrevented` instead.
|
||||
|
||||
Let's see a practical example where stopping the bubbling looks necessary, but actually we can do well without it.
|
||||
|
||||
By default the browser on `contextmenu` event (right mouse click) shows a context menu with standard options. We can prevent it and show our own, like this:
|
||||
|
||||
```html autorun height=50 no-beautify run
|
||||
<button>Right-click for browser context menu</button>
|
||||
|
||||
<button *!*oncontextmenu="alert('Draw our menu'); return false"*/!*>
|
||||
Right-click for our context menu
|
||||
</button>
|
||||
```
|
||||
|
||||
Now let's say we want to implement our own document-wide context menu, with our options. And inside the document we may have other elements with their own context menus:
|
||||
|
||||
```html autorun height=80 no-beautify run
|
||||
<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>
|
||||
```
|
||||
|
||||
The problem is that when we click on `elem`, we get two menus: the button-level and (the event bubbles up) the document-level menu.
|
||||
|
||||
How to fix it? One of solutions is to think like: "We fully handle the event in the button handler, let's stop it" and use `event.stopPropagation()`:
|
||||
|
||||
```html autorun height=80 no-beautify run
|
||||
<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 intended. But the price is high. We forever deny access to information about right-clicks for any outer code, including counters that gather statistics and so on. That's quite unwise.
|
||||
|
||||
An alternative solution would be to check in the `document` handler if the default action was prevented? If it is so, then the event was handled, and we don't need to react on it.
|
||||
|
||||
|
||||
```html autorun height=80 no-beautify run
|
||||
<p>Right-click for the document menu (fixed with 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 also works correctly. If we have nested elements, and each of them has a context menu of its own, that would also work. Just make sure to check for `event.defaultPrevented` in each `contextmenu` handler.
|
||||
|
||||
```smart header="event.stopPropagation() and event.preventDefault()"
|
||||
As we can clearly see, `event.stopPropagation()` and `event.preventDefault()` (also known as `return false`) are two different things. They are not related to each other.
|
||||
```
|
||||
|
||||
```smart header="Nested context menus architecture"
|
||||
There are also alternative ways to implement nested context menus. One of them is to have a special global object with a method that handles `document.oncontextmenu`, and also methods that allow to store various "lower-level" handlers in it.
|
||||
|
||||
The object will catch any right-click, look through stored handlers and run the appropriate one.
|
||||
|
||||
But then each piece of code that wants a context menu should know about that object and use its help instead of the own `contextmenu` handler.
|
||||
```
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
- Browser has default actions for many events -- following a link, submitting a form etc.
|
||||
- To prevent a default action -- use either `event.preventDefault()` or `return false`. The second method works only for handlers assigned with `on<event>`.
|
||||
There are many default browser actions:
|
||||
|
||||
- `mousedown` -- starts the selection (move the mouse to select).
|
||||
- `click` on `<input type="checkbox">` -- checks/unchecks the `input`.
|
||||
- `submit` -- clicking an `<input type="submit">` or hitting `key:Enter` inside a form field causes this event to happen, and the browser submits the form after it.
|
||||
- `wheel` -- rolling a mouse wheel event has scrolling as the default action.
|
||||
- `keydown` -- pressing a key may lead to adding a character into a field, or other actions.
|
||||
- `contextmenu` -- the event happens on a right-click, the action is to show the browser context menu.
|
||||
- ...there are more...
|
||||
|
||||
All the default actions can be prevented if we want to handle the event exclusively by JavaScript.
|
||||
|
||||
To prevent a default action -- use either `event.preventDefault()` or `return false`. The second method works only for handlers assigned with `on<event>`.
|
||||
|
||||
If the default action was prevented, the value of `event.defaultPrevented` becomes `true`, otherwise it's `false`.
|
||||
|
|
|
@ -15,19 +15,19 @@ Events form a hierarchy, just like DOM element classes. The root is the built-in
|
|||
We can create `Event` objects like this:
|
||||
|
||||
```js
|
||||
let event = new Event(event type[, flags]);
|
||||
let event = new Event(event type[, options]);
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
- *event type* -- may be any string, like `"click"` or our own like `"hey-ho!"`.
|
||||
- *flags* -- the object with two optional properties:
|
||||
- *options* -- the object with two optional properties:
|
||||
- `bubbles: true/false` -- if `true`, then the event bubbles.
|
||||
- `cancelable: true/false` -- if `true`, then the "default action" may be prevented. Later we'll see what it means for custom events.
|
||||
|
||||
By default both flags are false: `{bubbles: false, cancelable: false}`.
|
||||
By default both are false: `{bubbles: false, cancelable: false}`.
|
||||
|
||||
## The method dispatchEvent
|
||||
## dispatchEvent
|
||||
|
||||
After an event object is created, we should "run" it on an element using the call `elem.dispatchEvent(event)`.
|
||||
|
||||
|
@ -73,8 +73,8 @@ All we need is to set `bubbles` to `true`:
|
|||
|
||||
Notes:
|
||||
|
||||
1. We must use `addEventListener` for our custom events, because `on<event>` only exists for built-in events.
|
||||
2. Must set `bubbles`, otherwise the event won't bubble up.
|
||||
1. We should use `addEventListener` for our custom events, because `on<event>` only exists for built-in events, `document.onhello` doesn't work.
|
||||
2. Must set `bubbles:true`, otherwise the event won't bubble up.
|
||||
|
||||
The bubbling mechanics is the same for built-in (`click`) and custom (`hello`) events. There are also capturing and bubbling stages.
|
||||
|
||||
|
@ -137,7 +137,7 @@ In the second argument (object) we can add an additional property `detail` for a
|
|||
|
||||
For instance:
|
||||
|
||||
```html run
|
||||
```html run refresh
|
||||
<h1 id="elem">Hello for John!</h1>
|
||||
|
||||
<script>
|
||||
|
@ -155,29 +155,29 @@ For instance:
|
|||
</script>
|
||||
```
|
||||
|
||||
The `detail` can be anything. Once again, technically we can assign any properties into a regular `Event` object after its creation. But `CustomEvent` clearly states that the event is not built-in, but our own. And reserves the special `detail` field, so that we can write to it safely, without conflicts with standard event properties.
|
||||
The `detail` property can have any data.
|
||||
|
||||
Once again, technically we can assign any properties into a regular `new Event` object after its creation. But `CustomEvent` provides the special `detail` field, so that we can write to it safely, without conflicts with standard event properties.
|
||||
|
||||
Also using the right class tells something about "what kind of event" it is, and `CustomEvent` is better for that matter. So we should use it for our events.
|
||||
|
||||
## event.preventDefault()
|
||||
|
||||
We can call `event.preventDefault()` on a script-generated event if `cancelable:true` flag is specified.
|
||||
|
||||
Of course, if the event has a non-standard name, then it's not known to the browser, and there's no "default browser action" for it.
|
||||
|
||||
But the event-generating code may plan some actions after `dispatchEvent`.
|
||||
|
||||
## Отмена действия по умолчанию
|
||||
The call of `event.preventDefault()` is a way for the handler to send a signal that those actions shouldn't be performed.
|
||||
|
||||
На сгенерированном событии, как и на встроенном браузерном, обработчик может вызвать метод `event.preventDefault()`. Тогда `dispatchEvent` возвратит `false`.
|
||||
In that case the call to `elem.dispatchEvent(event)` returns `false`. And the event-generating code knows that the processing shouldn't continue.
|
||||
|
||||
Остановимся здесь подробнее. Обычно `preventDefault()` вызов предотвращает действие браузера. В случае, если событие придумано нами, имеет нестандартное имя -- никакого действия браузера, конечно, нет.
|
||||
For instance, in the example below there's a `hide()` function. It generates the `"hide"` event on the element `#rabbit`, notifying all interested parties that the rabbit is going to hide.
|
||||
|
||||
Но код, который генерирует событие, может предусматривать какие-то ещё действия после `dispatchEvent`.
|
||||
A handler set by `rabbit.addEventListener('hide',...)` will learn about that and, if it wants, can prevent that action by calling `event.preventDefault()`. Then the rabbit won't hide:
|
||||
|
||||
Вызов `event.preventDefault()` является возможностью для обработчика события сообщить в сгенерировавший событие код, что эти действия продолжать не надо.
|
||||
|
||||
В примере ниже есть функция `hide()`, которая при вызове генерирует событие `hide` на элементе `#rabbit`, уведомляя всех интересующихся, что кролик собирается спрятаться.
|
||||
|
||||
Любой обработчик может узнать об этом, подписавшись на событие через `rabbit.addEventListener('hide',...)` и, при желании, отменить действие по умолчанию через `event.preventDefault()`. Тогда кролик не исчезнет:
|
||||
|
||||
```html run
|
||||
```html run refresh
|
||||
<pre id="rabbit">
|
||||
|\ /|
|
||||
\|_|/
|
||||
|
@ -187,46 +187,49 @@ The `detail` can be anything. Once again, technically we can assign any properti
|
|||
</pre>
|
||||
|
||||
<script>
|
||||
|
||||
// hide() will be called automatically in 2 seconds
|
||||
function hide() {
|
||||
var event = new Event("hide", {
|
||||
cancelable: true
|
||||
let event = new Event("hide", {
|
||||
cancelable: true // without that flag preventDefault doesn't work
|
||||
});
|
||||
if (!rabbit.dispatchEvent(event)) {
|
||||
alert( 'действие отменено обработчиком' );
|
||||
alert('the action was prevented by a handler');
|
||||
} else {
|
||||
rabbit.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
rabbit.addEventListener('hide', function(event) {
|
||||
if (confirm("Вызвать preventDefault?")) {
|
||||
if (confirm("Call preventDefault?")) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// прячемся через 2 секунды
|
||||
// hide in 2 seconds
|
||||
setTimeout(hide, 2000);
|
||||
|
||||
</script>
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
To generate an event, we first need to create an event object.
|
||||
|
||||
The generic `Event(name, options)` constructor accepts an arbitrary event name and the `options` object with two properties:
|
||||
- `bubbles:true` if the event should bubble.
|
||||
- `cancelable:true` is the `event.preventDefault()` should work.
|
||||
|
||||
Other constructors of native events like `MouseEvent`, `KeyboardEvent` and so on accept properties specific to that event type. For instance, `clientX` for mouse events.
|
||||
|
||||
## Итого
|
||||
For custom events we should use `CustomEvent` constructor. It has an additional option named `detail`, we should assign the event-specific data to it. Then all handlers can access it as `event.detail`.
|
||||
|
||||
- Все браузеры, кроме IE9-11, позволяют генерировать любые события, следуя стандарту DOM4.
|
||||
- В IE9+ поддерживается более старый стандарт, можно легко сделать полифилл, например для `CustomEvent` он рассмотрен в этой главе.
|
||||
- IE8- может генерировать только встроенные события.
|
||||
Despite the technical possibility to generate browser events like `click` or `keydown`, we should use with the great care.
|
||||
|
||||
Несмотря на техническую возможность генерировать встроенные браузерные события типа `click` или `keydown` -- пользоваться ей стоит с большой осторожностью.
|
||||
We shouldn't generate browser events as a hacky way to run handlers. That's a bad architecture most of the time.
|
||||
|
||||
В 98% случаев, когда разработчик начинающего или среднего уровня хочет сгенерировать *встроенное* событие -- это вызвано "кривой" архитектурой кода, и взаимодействие нужно на уровне выше.
|
||||
Native events might be generated:
|
||||
|
||||
Как правило события имеет смысл генерировать:
|
||||
- As a dirty hack to make 3rd-party libraries work the needed way, if they don't provide other means of interaction.
|
||||
- For automated testing, to "click the button" in the script and see if the interface reacts correctly.
|
||||
|
||||
- Либо как явный и грубый хак, чтобы заставить работать сторонние библиотеки, в которых не предусмотрены другие средства взаимодействия.
|
||||
- Либо для автоматического тестирования, чтобы скриптом "нажать на кнопку" и посмотреть, произошло ли нужное действие.
|
||||
- Либо при создании своих "элементов интерфейса". Например, никто не мешает при помощи JavaScript создать из `<div class="calendar">` красивый календарь и генерировать на нём событие `change` при выборе даты. Эту тему мы разовьём позже.
|
||||
Custom events with our own names are often generated for architectural purposes, to signal what happens inside our menus, sliders, carousels etc.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue