This commit is contained in:
Ilya Kantor 2017-03-21 14:41:49 +03:00
parent 4ae129054e
commit ab9ab64bd5
476 changed files with 3370 additions and 532 deletions

View file

@ -0,0 +1,39 @@
When the browser reads the `on*` attribute like `onclick`, it creates the handler from its content.
For `onclick="handler()"` the function will be:
```js
function(event) {
handler() // the content of onclick
}
```
Now we can see that the value returned by `handler()` is not used and does not affect the result.
The fix is simple:
```html run
<script>
function handler() {
alert("...");
return false;
}
</script>
<a href="http://w3.org" onclick="*!*return handler()*/!*">w3.org</a>
```
Also we can use `event.preventDefault()`, like this:
```html run
<script>
*!*
function handler(event) {
alert("...");
event.preventDefault();
}
*/!*
</script>
<a href="http://w3.org" onclick="*!*handler(event)*/!*">w3.org</a>
```

View file

@ -0,0 +1,22 @@
importance: 3
---
# Why "return false" doesn't work?
Why in the code below `return false` doesn't work at all?
```html autorun run
<script>
function handler() {
alert( "..." );
return false;
}
</script>
<a href="http://w3.org" onclick="handler()">the browser will go to w3.org</a>
```
The browser follows the URL on click, but we don't want it.
How to fix?

View file

@ -0,0 +1,5 @@
That's a great use of the event delegation pattern.
In real life instead of asking we can send a "logging" request to the server that saves the information about where the visitor left. Or we can load the content and show it right in the page (if allowable).
All we need is to catch the `contents.onclick` and use `confirm` to ask the user. A good idea would be to use `link.getAttribute('href')` instead of `link.href` for the URL. See the solution for details.

View file

@ -0,0 +1,40 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#contents {
padding: 5px;
border: 1px green solid;
}
</style>
</head>
<body>
<fieldset id="contents">
<legend>#contents</legend>
<p>
How about to read <a href="http://wikipedia.org">Wikipedia</a> or visit <a href="http://w3.org"><i>W3.org</i></a> and learn about modern standards?
</p>
</fieldset>
<script>
contents.onclick = function(event) {
function handleLink(href) {
let isLeaving = confirm(`Leave for ${href}?`);
if (!isLeaving) return false;
}
let target = event.target.closest('a');
if (target && contents.contains(target)) {
return handleLink(target.getAttribute('href'));
}
};
</script>
</body>
</html>

View file

@ -0,0 +1,24 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#contents {
padding: 5px;
border: 1px green solid;
}
</style>
</head>
<body>
<fieldset id="contents">
<legend>#contents</legend>
<p>
How about to read <a href="http://wikipedia.org">Wikipedia</a> or visit <a href="http://w3.org"><i>W3.org</i></a> and learn about modern standards?
</p>
</fieldset>
</body>
</html>

View file

@ -0,0 +1,16 @@
importance: 5
---
# Catch links in the element
Make all links inside the element with `id="contents"` ask the user if he really wants to leave. And if he doesn't then don't follow.
Like this:
[iframe height=100 border=1 src="solution"]
Details:
- HTML inside the element may be loaded or regenerated dynamically at any time, so we can't find all links and put handlers on them. Use the event delegation.
- The content may have nested tags. Inside links too, like `<a href=".."><i>...</i></a>`.

View file

@ -0,0 +1 @@
The solution is to assign the handler to the container and track clicks. If a click is on the `<a>` link, then change `src` of `#largeImg` to the `href` of the thumbnail.

View file

@ -0,0 +1,44 @@
body {
margin: 0;
padding: 0;
font: 75%/120% Arial, Helvetica, sans-serif;
}
h2 {
font: bold 190%/100% Arial, Helvetica, sans-serif;
margin: 0 0 .2em;
}
h2 em {
font: normal 80%/100% Arial, Helvetica, sans-serif;
color: #999999;
}
#largeImg {
border: solid 1px #ccc;
width: 550px;
height: 400px;
padding: 5px;
}
#thumbs a {
border: solid 1px #ccc;
width: 100px;
height: 100px;
padding: 3px;
margin: 2px;
float: left;
}
#thumbs a:hover {
border-color: #FF9900;
}
#thumbs li {
list-style: none;
}
#thumbs {
margin: 0;
padding: 0;
}

View file

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Gallery</title>
<link rel="stylesheet" href="gallery.css">
<meta charset="utf-8">
</head>
<body>
<p><img id="largeImg" src="https://en.js.cx/gallery/img1-lg.jpg" alt="Large image"></p>
<ul id="thumbs">
<!-- the browser shows a small built-in tooltip on hover with the text from "title" attribute -->
<li>
<a href="https://en.js.cx/gallery/img2-lg.jpg" title="Image 2"><img src="https://en.js.cx/gallery/img2-thumb.jpg"></a>
</li>
<li>
<a href="https://en.js.cx/gallery/img3-lg.jpg" title="Image 3"><img src="https://en.js.cx/gallery/img3-thumb.jpg"></a>
</li>
<li>
<a href="https://en.js.cx/gallery/img4-lg.jpg" title="Image 4"><img src="https://en.js.cx/gallery/img4-thumb.jpg"></a>
</li>
<li>
<a href="https://en.js.cx/gallery/img5-lg.jpg" title="Image 5"><img src="https://en.js.cx/gallery/img5-thumb.jpg"></a>
</li>
<li>
<a href="https://en.js.cx/gallery/img6-lg.jpg" title="Image 6"><img src="https://en.js.cx/gallery/img6-thumb.jpg"></a>
</li>
</ul>
<script>
thumbs.onclick = function(event) {
let thumbnail = event.target.closest('a');
if (!thumbnail) return;
showThumbnail(target.href, target.title);
event.preventDefault();
}
function showThumbnail(href, title) {
largeImg.src = href;
largeImg.alt = title;
}
</script>
</body>
</html>

View file

@ -0,0 +1,35 @@
body {
margin: 0;
padding: 0;
font: 75%/120% Arial, Helvetica, sans-serif;
}
h2 {
font: bold 190%/100% Arial, Helvetica, sans-serif;
margin: 0 0 .2em;
}
h2 em {
font: normal 80%/100% Arial, Helvetica, sans-serif;
color: #999999;
}
#largeImg {
border: solid 1px #ccc;
width: 550px;
height: 400px;
padding: 5px;
}
#thumbs a {
border: solid 1px #ccc;
width: 100px;
height: 100px;
padding: 3px;
margin: 2px;
float: left;
}
#thumbs a:hover {
border-color: #FF9900;
}

View file

@ -0,0 +1,34 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Gallery</title>
<link rel="stylesheet" href="gallery.css">
<meta charset="utf-8">
</head>
<body>
<p><img id="largeImg" src="https://en.js.cx/gallery/img1-lg.jpg" alt="Large image"></p>
<ul id="thumbs">
<!-- the browser shows a small built-in tooltip on hover with the text from "title" attribute -->
<li>
<a href="https://en.js.cx/gallery/img2-lg.jpg" title="Image 2"><img src="https://en.js.cx/gallery/img2-thumb.jpg"></a>
</li>
<li>
<a href="https://en.js.cx/gallery/img3-lg.jpg" title="Image 3"><img src="https://en.js.cx/gallery/img3-thumb.jpg"></a>
</li>
<li>
<a href="https://en.js.cx/gallery/img4-lg.jpg" title="Image 4"><img src="https://en.js.cx/gallery/img4-thumb.jpg"></a>
</li>
<li>
<a href="https://en.js.cx/gallery/img5-lg.jpg" title="Image 5"><img src="https://en.js.cx/gallery/img5-thumb.jpg"></a>
</li>
<li>
<a href="https://en.js.cx/gallery/img6-lg.jpg" title="Image 6"><img src="https://en.js.cx/gallery/img6-thumb.jpg"></a>
</li>
</ul>
</body>
</html>

View file

@ -0,0 +1,13 @@
importance: 5
---
# Image gallery
Create an image gallery where the main image changes by the click on a thumbnail.
Like this:
[iframe src="solution" height=600]
P.S. Use event delegation.

View file

@ -0,0 +1,221 @@
# Browser default actions
Many events automatically lead to browser actions.
For instance:
- A click on a link -- initiates going to its URL.
- A click on submit button inside a form -- initiates its submission to the server.
- Pressing a mouse button over a text and moving it -- selects the text.
If we handle an event in JavaScript, often we don't want browser actions. Fortunately, it can be prevented.
[cut]
## Preventing browser actions
There are two ways to tell the browser we don't want it to act:
- The main way is to use the `event` object. There's a method `event.preventDefault()`.
- If the handler is assigned using `on<event>` (not by `addEventListener`), then we can just return `false` from it.
In the example below there a click to links don't lead to URL change:
```html autorun height=60 no-beautify
<a href="/" onclick="return false">Click here</a>
or
<a href="/" onclick="event.preventDefault()">here</a>
```
```warn header="Not necessary to return `true`"
The value returned by an event handler is usually ignored.
The only exception -- is `return false` from a handler assigned using `on<event>`.
In all other cases, the return is not needed and it's not processed anyhow.
```
### Example: the menu
Consider a site menu, like this:
```html
<ul id="menu" class="menu">
<li><a href="/html">HTML</a></li>
<li><a href="/javascript">JavaScript</a></li>
<li><a href="/css">CSS</a></li>
</ul>
```
Here's how it looks with some CSS:
[iframe height=70 src="menu" link edit]
Menu items are links `<a>`, not buttons. There are several benefits, for instance:
- Many people like to use "right click" -- "open in a new window". If we use `<button>` or `<span>`, that doesn't work.
- Search engines follow `<a href="...">` links while indexing.
So we use `<a>` in the markup. But normally we intend to handle clicks in JavaScript. So we should prevent the default browser action.
Like here:
```js
menu.onclick = function(event) {
if (event.target.nodeName != 'A') return;
let href = event.target.getAttribute('href');
alert( href ); // ...can be loading from the server, UI generation etc
*!*
return false; // prevent browser action (don't go to the URL)
*/!*
};
```
If we omit `return false`, then after our code executes the browser will do its "default action" -- following to the URL in `href`.
By the way, using event delegation here makes our menu flexible. We can add nested lists and style them using CSS to "slide down".
## Prevent futher events
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.
Try to click on the first `<input>` below -- the `focus` event happens. That's normal.
But if you click the second one, there's no focus.
```html run autorun
<input value="Focus works" onfocus="this.value=''">
<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. 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
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`.

View file

@ -0,0 +1,21 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="menu.css" />
</head>
<body>
<ul id="menu" class="menu">
<li><a href="/html">HTML</a></li>
<li><a href="/javascript">JavaScript</a></li>
<li><a href="/css">CSS</a></li>
</ul>
<script src="menu.js"></script>
</body>
</html>

View file

@ -0,0 +1,25 @@
.menu li {
display: inline-block;
margin: 0;
}
.menu > li a {
display: inline-block;
margin: 0 2px;
outline: none;
text-align: center;
text-decoration: none;
font: 14px/100% Arial, Helvetica, sans-serif;
padding: .5em 2em .55em;
text-shadow: 0 1px 1px rgba(0, 0, 0, .3);
border-radius: .5em;
box-shadow: 0 1px 2px rgba(0, 0, 0, .2);
color: #d9eef7;
border: solid 1px #0076a3;
background: #0095cd;
}
.menu > li:hover a {
text-decoration: none;
background: #007ead;
}

View file

@ -0,0 +1,8 @@
menu.onclick = function(event) {
if (event.target.nodeName != 'A') return;
let href = event.target.getAttribute('href');
alert(href);
return false; // prevent url change
};