Skip to content

Commit 7b3f455

Browse files
committed
up
1 parent 497c9fd commit 7b3f455

File tree

2 files changed

+163
-55
lines changed

2 files changed

+163
-55
lines changed

2-ui/2-events/7-default-browser-action/article.md

Lines changed: 125 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -77,24 +77,10 @@ If we omit `return false`, then after our code executes the browser will do its
7777

7878
By the way, using event delegation here makes our menu flexible. We can add nested lists and style them using CSS to "slide down".
7979

80-
## Other browser actions
8180

82-
There are many default browser actions.
81+
## Prevent futher events
8382

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.
9884

9985
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.
10086

@@ -107,10 +93,129 @@ But if you click the second one, there's no focus.
10793
<input *!*onmousedown="return false"*/!* onfocus="this.value=''" value="Click me">
10894
```
10995

110-
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>
115+
116+
<button *!*oncontextmenu="alert('Draw our menu'); return false"*/!*>
117+
Right-click for our context menu
118+
</button>
119+
```
120+
121+
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+
<button id="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+
<button id="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+
<button id="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.
194+
```
195+
196+
```smart header="Nested context menus architecture"
197+
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+
112204

113205
## Summary
114206

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`.

2-ui/2-events/8-dispatch-events/article.md

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@ Events form a hierarchy, just like DOM element classes. The root is the built-in
1515
We can create `Event` objects like this:
1616

1717
```js
18-
let event = new Event(event type[, flags]);
18+
let event = new Event(event type[, options]);
1919
```
2020

2121
Arguments:
2222

2323
- *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:
2525
- `bubbles: true/false` -- if `true`, then the event bubbles.
2626
- `cancelable: true/false` -- if `true`, then the "default action" may be prevented. Later we'll see what it means for custom events.
2727

28-
By default both flags are false: `{bubbles: false, cancelable: false}`.
28+
By default both are false: `{bubbles: false, cancelable: false}`.
2929

30-
## The method dispatchEvent
30+
## dispatchEvent
3131

3232
After an event object is created, we should "run" it on an element using the call `elem.dispatchEvent(event)`.
3333

@@ -73,8 +73,8 @@ All we need is to set `bubbles` to `true`:
7373

7474
Notes:
7575

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.
7878

7979
The bubbling mechanics is the same for built-in (`click`) and custom (`hello`) events. There are also capturing and bubbling stages.
8080

@@ -137,7 +137,7 @@ In the second argument (object) we can add an additional property `detail` for a
137137

138138
For instance:
139139

140-
```html run
140+
```html run refresh
141141
<h1 id="elem">Hello for John!</h1>
142142

143143
<script>
@@ -155,29 +155,29 @@ For instance:
155155
</script>
156156
```
157157

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.
159159

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.
160161

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.
161163

164+
## event.preventDefault()
162165

166+
We can call `event.preventDefault()` on a script-generated event if `cancelable:true` flag is specified.
163167

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.
164169

170+
But the event-generating code may plan some actions after `dispatchEvent`.
165171

166-
## Отмена действия по умолчанию
172+
The call of `event.preventDefault()` is a way for the handler to send a signal that those actions shouldn't be performed.
167173

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.
169175

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.
171177

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:
173179

174-
Вызов `event.preventDefault()` является возможностью для обработчика события сообщить в сгенерировавший событие код, что эти действия продолжать не надо.
175-
176-
В примере ниже есть функция `hide()`, которая при вызове генерирует событие `hide` на элементе `#rabbit`, уведомляя всех интересующихся, что кролик собирается спрятаться.
177-
178-
Любой обработчик может узнать об этом, подписавшись на событие через `rabbit.addEventListener('hide',...)` и, при желании, отменить действие по умолчанию через `event.preventDefault()`. Тогда кролик не исчезнет:
179-
180-
```html run
180+
```html run refresh
181181
<pre id="rabbit">
182182
|\ /|
183183
\|_|/
@@ -187,46 +187,49 @@ The `detail` can be anything. Once again, technically we can assign any properti
187187
</pre>
188188

189189
<script>
190-
190+
// hide() will be called automatically in 2 seconds
191191
function hide() {
192-
var event = new Event("hide", {
193-
cancelable: true
192+
let event = new Event("hide", {
193+
cancelable: true // without that flag preventDefault doesn't work
194194
});
195195
if (!rabbit.dispatchEvent(event)) {
196-
alert( 'действие отменено обработчиком' );
196+
alert('the action was prevented by a handler');
197197
} else {
198198
rabbit.hidden = true;
199199
}
200200
}
201201
202202
rabbit.addEventListener('hide', function(event) {
203-
if (confirm("Вызвать preventDefault?")) {
203+
if (confirm("Call preventDefault?")) {
204204
event.preventDefault();
205205
}
206206
});
207207
208-
// прячемся через 2 секунды
208+
// hide in 2 seconds
209209
setTimeout(hide, 2000);
210210
211211
</script>
212212
```
213213

214+
## Summary
214215

216+
To generate an event, we first need to create an event object.
215217

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.
216221

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.
217223

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`.
219225

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.
223227

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.
225229

226-
В 98% случаев, когда разработчик начинающего или среднего уровня хочет сгенерировать *встроенное* событие -- это вызвано "кривой" архитектурой кода, и взаимодействие нужно на уровне выше.
230+
Native events might be generated:
227231

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.
229234

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

Comments
 (0)