You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -77,24 +77,10 @@ If we omit `return false`, then after our code executes the browser will do its
77
77
78
78
By the way, using event delegation here makes our menu flexible. We can add nested lists and style them using CSS to "slide down".
79
79
80
-
## Other browser actions
81
80
82
-
There are many default browser actions.
81
+
## Prevent futher events
83
82
84
-
Here are more events that cause browser actions:
85
-
86
-
-`mousedown` -- starts the selection (move the mouse to select).
87
-
-`click` on `<input type="checkbox">` -- checks/unchecks the `input`.
88
-
-`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.
89
-
-`wheel` -- rolling a mouse wheel event has scrolling as the default action.
90
-
-`keydown` -- pressing a key may lead to adding a character into a field, or other actions.
91
-
-`contextmenu` -- the event happens on a right-click, the action is to show the browser context menu.
92
-
- ...
93
-
94
-
All the default actions can be prevented if we want to handle the event exclusively by JavaScript.
95
-
96
-
````warn header="Events may be related"
97
-
Certain events flow one into another.
83
+
Certain events flow one into another. If we prevent the first event, there will be no second.
98
84
99
85
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.
100
86
@@ -107,10 +93,129 @@ But if you click the second one, there's no focus.
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.
111
-
````
96
+
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.
97
+
98
+
99
+
## event.defaultPrevented
100
+
101
+
The property `event.defaultPrevented` is `true` if the default action was prevented, and `false` otherwise.
102
+
103
+
There's an interesting use case for it.
104
+
105
+
You remember in the chapter <info:bubbling-and-capturing> we talked about `event.stopPropagation()` and why stopping bubbling is bad?
106
+
107
+
Sometimes we can use `event.defaultPrevented` instead.
108
+
109
+
Let's see a practical example where stopping the bubbling looks necessary, but actually we can do well without it.
110
+
111
+
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:
112
+
113
+
```html autorun height=50 no-beautify run
114
+
<button>Right-click for browser 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:
122
+
123
+
```html autorun height=80 no-beautify run
124
+
<p>Right-click here for the document context menu</p>
125
+
<buttonid="elem">Right-click here for the button context menu</button>
126
+
127
+
<script>
128
+
elem.oncontextmenu=function(event) {
129
+
event.preventDefault();
130
+
alert("Button context menu");
131
+
};
132
+
133
+
document.oncontextmenu=function(event) {
134
+
event.preventDefault();
135
+
alert("Document context menu");
136
+
};
137
+
</script>
138
+
```
139
+
140
+
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.
141
+
142
+
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()`:
143
+
144
+
```html autorun height=80 no-beautify run
145
+
<p>Right-click for the document menu</p>
146
+
<buttonid="elem">Right-click for the button menu (fixed with event.stopPropagation)</button>
147
+
148
+
<script>
149
+
elem.oncontextmenu=function(event) {
150
+
event.preventDefault();
151
+
*!*
152
+
event.stopPropagation();
153
+
*/!*
154
+
alert("Button context menu");
155
+
};
156
+
157
+
document.oncontextmenu=function(event) {
158
+
event.preventDefault();
159
+
alert("Document context menu");
160
+
};
161
+
</script>
162
+
```
163
+
164
+
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.
165
+
166
+
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.
167
+
168
+
169
+
```html autorun height=80 no-beautify run
170
+
<p>Right-click for the document menu (fixed with event.defaultPrevented)</p>
171
+
<buttonid="elem">Right-click for the button menu</button>
172
+
173
+
<script>
174
+
elem.oncontextmenu=function(event) {
175
+
event.preventDefault();
176
+
alert("Button context menu");
177
+
};
178
+
179
+
document.oncontextmenu=function(event) {
180
+
*!*
181
+
if (event.defaultPrevented) return;
182
+
*/!*
183
+
184
+
event.preventDefault();
185
+
alert("Document context menu");
186
+
};
187
+
</script>
188
+
```
189
+
190
+
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.
191
+
192
+
```smart header="event.stopPropagation() and event.preventDefault()"
193
+
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.
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.
198
+
199
+
The object will catch any right-click, look through stored handlers and run the appropriate one.
200
+
201
+
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.
202
+
```
203
+
112
204
113
205
## Summary
114
206
115
-
- Browser has default actions for many events -- following a link, submitting a form etc.
116
-
- To prevent a default action -- use either `event.preventDefault()` or `return false`. The second method works only for handlers assigned with `on<event>`.
207
+
There are many default browser actions:
208
+
209
+
-`mousedown` -- starts the selection (move the mouse to select).
210
+
-`click` on `<input type="checkbox">` -- checks/unchecks the `input`.
211
+
-`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.
212
+
-`wheel` -- rolling a mouse wheel event has scrolling as the default action.
213
+
-`keydown` -- pressing a key may lead to adding a character into a field, or other actions.
214
+
-`contextmenu` -- the event happens on a right-click, the action is to show the browser context menu.
215
+
- ...there are more...
216
+
217
+
All the default actions can be prevented if we want to handle the event exclusively by JavaScript.
218
+
219
+
To prevent a default action -- use either `event.preventDefault()` or `return false`. The second method works only for handlers assigned with `on<event>`.
220
+
221
+
If the default action was prevented, the value of `event.defaultPrevented` becomes `true`, otherwise it's `false`.
Copy file name to clipboardExpand all lines: 2-ui/2-events/8-dispatch-events/article.md
+38-35Lines changed: 38 additions & 35 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,19 +15,19 @@ Events form a hierarchy, just like DOM element classes. The root is the built-in
15
15
We can create `Event` objects like this:
16
16
17
17
```js
18
-
letevent=newEvent(event type[, flags]);
18
+
letevent=newEvent(event type[, options]);
19
19
```
20
20
21
21
Arguments:
22
22
23
23
-*event type* -- may be any string, like `"click"` or our own like `"hey-ho!"`.
24
-
-*flags* -- the object with two optional properties:
24
+
-*options* -- the object with two optional properties:
25
25
-`bubbles: true/false` -- if `true`, then the event bubbles.
26
26
-`cancelable: true/false` -- if `true`, then the "default action" may be prevented. Later we'll see what it means for custom events.
27
27
28
-
By default both flags are false: `{bubbles: false, cancelable: false}`.
28
+
By default both are false: `{bubbles: false, cancelable: false}`.
29
29
30
-
## The method dispatchEvent
30
+
## dispatchEvent
31
31
32
32
After an event object is created, we should "run" it on an element using the call `elem.dispatchEvent(event)`.
33
33
@@ -73,8 +73,8 @@ All we need is to set `bubbles` to `true`:
73
73
74
74
Notes:
75
75
76
-
1. We must use `addEventListener` for our custom events, because `on<event>` only exists for built-in events.
77
-
2. Must set `bubbles`, otherwise the event won't bubble up.
76
+
1. We should use `addEventListener` for our custom events, because `on<event>` only exists for built-in events, `document.onhello` doesn't work.
77
+
2. Must set `bubbles:true`, otherwise the event won't bubble up.
78
78
79
79
The bubbling mechanics is the same for built-in (`click`) and custom (`hello`) events. There are also capturing and bubbling stages.
80
80
@@ -137,7 +137,7 @@ In the second argument (object) we can add an additional property `detail` for a
137
137
138
138
For instance:
139
139
140
-
```html run
140
+
```html run refresh
141
141
<h1id="elem">Hello for John!</h1>
142
142
143
143
<script>
@@ -155,29 +155,29 @@ For instance:
155
155
</script>
156
156
```
157
157
158
-
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.
158
+
The `detail`property can have any data.
159
159
160
+
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.
160
161
162
+
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.
161
163
164
+
## event.preventDefault()
162
165
166
+
We can call `event.preventDefault()` on a script-generated event if `cancelable:true` flag is specified.
163
167
168
+
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.
164
169
170
+
But the event-generating code may plan some actions after `dispatchEvent`.
165
171
166
-
## Отмена действия по умолчанию
172
+
The call of `event.preventDefault()` is a way for the handler to send a signal that those actions shouldn't be performed.
167
173
168
-
На сгенерированном событии, как и на встроенном браузерном, обработчик может вызвать метод `event.preventDefault()`. Тогда `dispatchEvent` возвратит `false`.
174
+
In that case the call to `elem.dispatchEvent(event)` returns `false`. And the event-generating code knows that the processing shouldn't continue.
169
175
170
-
Остановимся здесь подробнее. Обычно `preventDefault()`вызов предотвращает действие браузера. В случае, если событие придумано нами, имеет нестандартное имя -- никакого действия браузера, конечно, нет.
176
+
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.
171
177
172
-
Но код, который генерирует событие, может предусматривать какие-то ещё действия после `dispatchEvent`.
178
+
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:
173
179
174
-
Вызов `event.preventDefault()` является возможностью для обработчика события сообщить в сгенерировавший событие код, что эти действия продолжать не надо.
175
-
176
-
В примере ниже есть функция `hide()`, которая при вызове генерирует событие `hide` на элементе `#rabbit`, уведомляя всех интересующихся, что кролик собирается спрятаться.
177
-
178
-
Любой обработчик может узнать об этом, подписавшись на событие через `rabbit.addEventListener('hide',...)` и, при желании, отменить действие по умолчанию через `event.preventDefault()`. Тогда кролик не исчезнет:
179
-
180
-
```html run
180
+
```html run refresh
181
181
<preid="rabbit">
182
182
|\ /|
183
183
\|_|/
@@ -187,46 +187,49 @@ The `detail` can be anything. Once again, technically we can assign any properti
187
187
</pre>
188
188
189
189
<script>
190
-
190
+
// hide() will be called automatically in 2 seconds
191
191
functionhide() {
192
-
varevent=newEvent("hide", {
193
-
cancelable:true
192
+
letevent=newEvent("hide", {
193
+
cancelable:true// without that flag preventDefault doesn't work
194
194
});
195
195
if (!rabbit.dispatchEvent(event)) {
196
-
alert('действие отменено обработчиком');
196
+
alert('the action was prevented by a handler');
197
197
} else {
198
198
rabbit.hidden=true;
199
199
}
200
200
}
201
201
202
202
rabbit.addEventListener('hide', function(event) {
203
-
if (confirm("Вызвать preventDefault?")) {
203
+
if (confirm("Call preventDefault?")) {
204
204
event.preventDefault();
205
205
}
206
206
});
207
207
208
-
//прячемся через 2 секунды
208
+
//hide in 2 seconds
209
209
setTimeout(hide, 2000);
210
210
211
211
</script>
212
212
```
213
213
214
+
## Summary
214
215
216
+
To generate an event, we first need to create an event object.
215
217
218
+
The generic `Event(name, options)` constructor accepts an arbitrary event name and the `options` object with two properties:
219
+
-`bubbles:true` if the event should bubble.
220
+
-`cancelable:true` is the `event.preventDefault()` should work.
216
221
222
+
Other constructors of native events like `MouseEvent`, `KeyboardEvent` and so on accept properties specific to that event type. For instance, `clientX` for mouse events.
217
223
218
-
## Итого
224
+
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`.
219
225
220
-
- Все браузеры, кроме IE9-11, позволяют генерировать любые события, следуя стандарту DOM4.
221
-
- В IE9+ поддерживается более старый стандарт, можно легко сделать полифилл, например для `CustomEvent` он рассмотрен в этой главе.
222
-
- IE8- может генерировать только встроенные события.
226
+
Despite the technical possibility to generate browser events like `click` or `keydown`, we should use with the great care.
223
227
224
-
Несмотря на техническую возможность генерировать встроенные браузерные события типа `click` или `keydown` -- пользоваться ей стоит с большой осторожностью.
228
+
We shouldn't generate browser events as a hacky way to run handlers. That's a bad architecture most of the time.
225
229
226
-
В 98% случаев, когда разработчик начинающего или среднего уровня хочет сгенерировать *встроенное* событие -- это вызвано "кривой" архитектурой кода, и взаимодействие нужно на уровне выше.
230
+
Native events might be generated:
227
231
228
-
Как правило события имеет смысл генерировать:
232
+
- As a dirty hack to make 3rd-party libraries work the needed way, if they don't provide other means of interaction.
233
+
- For automated testing, to "click the button" in the script and see if the interface reacts correctly.
229
234
230
-
- Либо как явный и грубый хак, чтобы заставить работать сторонние библиотеки, в которых не предусмотрены другие средства взаимодействия.
231
-
- Либо для автоматического тестирования, чтобы скриптом "нажать на кнопку" и посмотреть, произошло ли нужное действие.
232
-
- Либо при создании своих "элементов интерфейса". Например, никто не мешает при помощи JavaScript создать из `<div class="calendar">` красивый календарь и генерировать на нём событие `change` при выборе даты. Эту тему мы разовьём позже.
235
+
Custom events with our own names are often generated for architectural purposes, to signal what happens inside our menus, sliders, carousels etc.
0 commit comments