up
This commit is contained in:
parent
4ae129054e
commit
ab9ab64bd5
476 changed files with 3370 additions and 532 deletions
|
@ -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>
|
||||
```
|
|
@ -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?
|
|
@ -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.
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>`.
|
|
@ -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.
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
|
@ -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.
|
221
2-ui/2-events/04-default-browser-action/article.md
Normal file
221
2-ui/2-events/04-default-browser-action/article.md
Normal 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`.
|
21
2-ui/2-events/04-default-browser-action/menu.view/index.html
Normal file
21
2-ui/2-events/04-default-browser-action/menu.view/index.html
Normal 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>
|
25
2-ui/2-events/04-default-browser-action/menu.view/menu.css
Normal file
25
2-ui/2-events/04-default-browser-action/menu.view/menu.css
Normal 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;
|
||||
}
|
|
@ -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
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue