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,22 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<input type="button" id="hider" value="Click to hide the text" />
<div id="text">Text</div>
<script>
// Here it doesn't matter how we hide the text,
// could also use style.display:
document.getElementById('hider').onclick = function() {
document.getElementById('text').hidden = true;
}
</script>
</body>
</html>

View file

@ -0,0 +1,19 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<input type="button" id="hider" value="Click to hide the text" />
<div id="text">Text</div>
<script>
/* your code */
</script>
</body>
</html>

View file

@ -0,0 +1,11 @@
importance: 5
---
# Hide on click
Add JavaScript to the `button` to make `<div id="text">` disappear when we click it.
The demo:
[iframe border=1 src="solution" height=80]

View file

@ -0,0 +1,5 @@
Can use `this` in the handler to reference "itself" here:
```html run height=50
<input type="button" onclick="this.hidden=true" value="Click to hide">
```

View file

@ -0,0 +1,12 @@
importance: 5
---
# Hide self
Create a button that hides itself on click.
```online
Like this:
<input type="button" onclick="this.hidden=true" value="Click to hide">
```

View file

@ -0,0 +1,16 @@
The answer: `1` and `2`.
The first handler triggers, because it's not removed by `removeEventListener`. To remove the handler we need to pass exactly the function that was assigned. And in the code a new function is passed, that looks the same, but is still another function.
To remove a function object, we need to store a reference to it, like this:
```js
function handler() {
alert(1);
}
button.addEventListener("click", handler);
button.removeEventListener("click", handler);
```
The handler `button.onclick` works independantly and in addition to `addEventListener`.

View file

@ -0,0 +1,17 @@
importance: 5
---
# Which handlers run?
There's a button in the variable. There are no handlers on it.
Which handlers run on click after the following code? Which alerts show up?
```js no-beautify
button.addEventListener("click", () => alert("1"));
button.removeEventListener("click", () => alert("1"));
button.onclick = () => alert(2);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -0,0 +1,51 @@
First we need to choose a method of positioning the ball.
We can't use `position:fixed` for it, because scrolling the page would move the ball from the field.
So we should use `position:absolute` and, to make the positioning really solid, make `field` itself positioned.
Then the ball will be positioned relatively to the field:
```css
#field {
width: 200px;
height: 150px;
position: relative;
}
#ball {
position: absolute;
left: 0; /* relative to the closest positioned ancestor (field) */
top: 0;
transition: 1s all; /* CSS animation for left/top makes the ball fly */
}
```
Next we need to assign the correct `ball.style.position.left/top`. They contain field-relative coordinates now.
Here's the picture:
![](move-ball-coords.png)
We have `event.clientX/clientY` -- window-relative coordinates of the click.
To get field-relative `left` coordinate of the click, we can substract the field left edge and the border width:
```js
let left = event.clientX - fieldInnerCoords.left - field.clientLeft;
```
Normally, `ball.style.position.left` means the "left edge of the element" (the ball). So if we assign that `left`, then the ball edge would be under the mouse cursor.
We need to move the ball half-width left and half-height up to make it center.
So the final `left` would be:
```js
let left = event.clientX - fieldInnerCoords.left - field.clientLeft - ball.offsetWidth/2;
```
The vertical coordinate is calculated using the same logic.
Please note that the ball width/height must be known at the time we access `ball.offsetWidth`. Should be specified in HTML or CSS.

View file

@ -0,0 +1,75 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#field {
width: 200px;
height: 150px;
border: 10px solid black;
background-color: #00FF00;
position: relative;
overflow: hidden;
cursor: pointer;
}
#ball {
position: absolute;
left: 0;
top: 0;
width: 40px;
height: 40px;
transition: all 1s;
}
</style>
</head>
<body style="height:2000px">
Click on a field to move the ball there.
<br>
<div id="field">
<img src="https://en.js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
<script>
field.onclick = function(event) {
// window-relative field coordinates
let fieldCoords = this.getBoundingClientRect();
// the ball has position:absolute, the field: position:relative
// so ball coordinates are relative to the field inner left-upper corner
let ballCoords = {
top: event.clientY - fieldInnerCoords.top - field.clientTop - ball.clientHeight / 2,
left: event.clientX - fieldInnerCoords.left - field.clientLeft - ball.clientWidth / 2
};
// prevent crossing the top field boundary
if (ballCoords.top < 0) ballCoords.top = 0;
// prevent crossing the left field boundary
if (ballCoords.left < 0) ballCoords.left = 0;
// // prevent crossing the right field boundary
if (ballCoords.left + ball.clientWidth > field.clientWidth) {
ballCoords.left = field.clientWidth - ball.clientWidth;
}
// prevent crossing the bottom field boundary
if (ballCoords.top + ball.clientHeight > field.clientHeight) {
ballCoords.top = field.clientHeight - ball.clientHeight;
}
ball.style.left = ballCoords.left + 'px';
ball.style.top = ballCoords.top + 'px';
}
</script>
</body>
</html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
#field {
width: 200px;
height: 150px;
border: 10px solid black;
background-color: #00FF00;
overflow: hidden;
}
</style>
</head>
<body style="height:2000px">
Click on a field to move the ball there.
<br> The ball should never leave the field.
<div id="field">
<img src="https://en.js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
</body>
</html>

View file

@ -0,0 +1,21 @@
importance: 5
---
# Move the ball across the field
Move the ball across the field to a click. Like this:
[iframe src="solution" height="260" link]
Requirements:
- The ball center should come exactly under the pointer on click (if possible without crossing the field edge).
- CSS-animation is welcome.
- The ball must not cross field boundaries.
- When the page is scrolled, nothing should break.
Notes:
- The code should also work with different ball and field sizes, not be bound to any fixed values.
- Use properties `event.clientX/event.clientY` for click coordinates.

View file

@ -0,0 +1,71 @@
# HTML/CSS
First let's create HTML/CSS.
A menu is a standalone graphical component on the page, so its better to put it into a single DOM element.
A list of menu items can be layed out as a list `ul/li`.
Here's the example structure:
```html
<div class="menu">
<span class="title">Sweeties (click me)!</span>
<ul>
<li>Cake</li>
<li>Donut</li>
<li>Honey</li>
</ul>
</div>
```
We use `<span>` for the title, because `<div>` has an implicit `display:block` on it, and it will occupy 100% of the horizontal width.
Like this:
```html autorun height=50
<div style="border: solid red 1px" onclick="alert(1)">Sweeties (click me)!</div>
```
So if we set `onclick` on it, then it will catch clicks to the right of the text.
...but `<span>` has an implicit `display: inline`, so it occupies exactly enough place to fit all the text:
```html autorun height=50
<span style="border: solid red 1px" onclick="alert(1)">Sweeties (click me)!</span>
```
# Toggling the menu
Toggling the menu should change the arrow and show/hide the menu list.
All these changes are perfectly handled by CSS. In JavaScript we should label the current state of the menu by adding/removing the class `.open`.
Without it, the menu will be closed:
```css
.menu ul {
margin: 0;
list-style: none;
padding-left: 20px;
display: none;
}
.menu .title::before {
content: '▶ ';
font-size: 80%;
color: green;
}
```
...And with `.open` the arrow changes and the list shows up:
```css
.menu.open .title::before {
content: '▼ ';
}
.menu.open ul {
display: block;
}
```

View file

@ -0,0 +1,57 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
.menu ul {
margin: 0;
list-style: none;
padding-left: 20px;
display: none;
}
.menu .title {
font-size: 18px;
cursor: pointer;
}
.menu .title::before {
content: '▶ ';
font-size: 80%;
color: green;
}
.menu.open .title::before {
content: '▼ ';
}
.menu.open ul {
display: block;
}
</style>
</head>
<body>
<div id="sweeties" class="menu">
<span class="title">Sweeties (click me)!</span>
<ul>
<li>Cake</li>
<li>Donut</li>
<li>Honey</li>
</ul>
</div>
<script>
let menuElem = document.getElementById('sweeties');
let titleElem = menuElem.querySelector('.title');
titleElem.onclick = function() {
menuElem.classList.toggle('open');
};
</script>
</body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
▶ ▼ Sweeties (click me)!
<ul>
<li>Cake</li>
<li>Donut</li>
<li>Honey</li>
</ul>
</body>
</html>

View file

@ -0,0 +1,11 @@
importance: 5
---
# Create a menu sliding menu
Create a menu that opens/collapses on click:
[iframe border=1 height=100 src="solution"]
P.S. HTML/CSS of the source document is to be modified.

View file

@ -0,0 +1,13 @@
To add the button we can use either `position:absolute` (and make the pane `position:relative`) or `float:right`. The `float:right` has the benefit that the button never overlaps the text, but `position:absolute` gives more freedom. So the choice is yours.
Then for each pane the code can be like:
```js
pane.insertAdjacentHTML("afterbegin", '<button class="remove-button">[x]</button>');
```
Then the `<button>` becomes `pane.firstChild`, so we can add a handler to it like this:
```js
pane.firstChild.onclick = () => pane.remove();
```

View file

@ -0,0 +1,39 @@
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" href="messages.css">
<meta charset="utf-8">
</head>
<body>
<div>
<div class="pane">
<h3>Horse</h3>
<p>The horse is one of two extant subspecies of Equus ferus. It is an odd-toed ungulate mammal belonging to the taxonomic family Equidae. The horse has evolved over the past 45 to 55 million years from a small multi-toed creature, Eohippus, into the large, single-toed animal of today.</p>
</div>
<div class="pane">
<h3>Donkey</h3>
<p>The donkey or ass (Equus africanus asinus) is a domesticated member of the horse family, Equidae. The wild ancestor of the donkey is the African wild ass, E. africanus. The donkey has been used as a working animal for at least 5000 years.</p>
</div>
<div class="pane">
<h3>Cat</h3>
<p>The domestic cat (Latin: Felis catus) is a small, typically furry, carnivorous mammal. They are often called house cats when kept as indoor pets or simply cats when there is no need to distinguish them from other felids and felines. Cats are often valued by humans for companionship and for their ability to hunt vermin.
</p>
</div>
</div>
<script>
let panes = document.querySelectorAll('.pane');
for(let pane of panes) {
pane.insertAdjacentHTML("afterbegin", '<button class="remove-button">[x]</button>');
// button becomes the first child of pane
pane.firstChild.onclick = () => pane.remove();
}
</script>
</body>
</html>

View file

@ -0,0 +1,37 @@
body {
margin: 10px auto;
width: 470px;
}
h3 {
margin: 0;
padding-bottom: .3em;
padding-right: 20px;
font-size: 1.1em;
}
p {
margin: 0;
padding: 0 0 .5em;
}
.pane {
background: #edf5e1;
padding: 10px 20px 10px;
border-top: solid 2px #c4df9b;
position: relative;
}
.remove-button {
position: absolute;
font-size: 110%;
top: 0;
color: darkred;
right: 10px;
display: block;
width: 24px;
height: 24px;
border: none;
background: transparent;
cursor: pointer;
}

View file

@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" type="text/css" href="messages.css">
<meta charset="utf-8">
</head>
<body>
The button code (may need to adjust CSS):
<button class="remove-button">[x]</button>
<div>
<div class="pane">
<h3>Horse</h3>
<p>The horse is one of two extant subspecies of Equus ferus. It is an odd-toed ungulate mammal belonging to the taxonomic family Equidae. The horse has evolved over the past 45 to 55 million years from a small multi-toed creature, Eohippus, into the large, single-toed animal of today.</p>
</div>
<div class="pane">
<h3>Donkey</h3>
<p>The donkey or ass (Equus africanus asinus) is a domesticated member of the horse family, Equidae. The wild ancestor of the donkey is the African wild ass, E. africanus. The donkey has been used as a working animal for at least 5000 years.</p>
</div>
<div class="pane">
<h3>Cat</h3>
<p>The domestic cat (Latin: Felis catus) is a small, typically furry, carnivorous mammal. They are often called house cats when kept as indoor pets or simply cats when there is no need to distinguish them from other felids and felines. Cats are often valued by humans for companionship and for their ability to hunt vermin.
</p>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,32 @@
body {
margin: 10px auto;
width: 470px;
}
h3 {
margin: 0;
padding-bottom: .3em;
font-size: 1.1em;
}
p {
margin: 0;
padding: 0 0 .5em;
}
.pane {
background: #edf5e1;
padding: 10px 20px 10px;
border-top: solid 2px #c4df9b;
}
.remove-button {
font-size: 110%;
color: darkred;
right: 10px;
width: 24px;
height: 24px;
border: none;
background: transparent;
cursor: pointer;
}

View file

@ -0,0 +1,13 @@
importance: 5
---
# Add a closing button
There's a list of messages.
Use JavaScript to add a closing button to the right-upper corner of each message.
The result should look like this:
[iframe src="solution" height=450]

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -0,0 +1,17 @@
The images ribbon can be represented as `ul/li` list of images `<img>`.
Normally, such a ribbon is wide, but we put a fixed-size `<div>` around to "cut" it, so that only a part of the ribbon is visibble:
![](carousel1.png)
To make the list show horizontally we need to apply correct CSS properties for `<li>`, like `display: inline-block`.
For `<img>` we should also adjust `display`, because by default it's `inline`. There's extra space reserved under `inline` elements for "letter tails", so we can use `display:block` to remove it.
To do the scrolling, we can shift `<ul>`. There are many ways to do it, for instance by changing `margin-left` or (better performance) use `transform: translateX()`:
![](carousel2.png)
The outer `<div>` has a fixed width, so "extra" images are cut.
The whole carousel is a self-contained "graphical component" on the page, so we'd better wrap it into a single `<div class="carousel">` and style things inside it.

View file

@ -0,0 +1,65 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="carousel" class="carousel">
<button class="arrow prev"></button>
<div class="gallery">
<ul class="images">
<li><img src="https://en.js.cx/carousel/1.png"></li>
<li><img src="https://en.js.cx/carousel/2.png"></li>
<li><img src="https://en.js.cx/carousel/3.png"></li>
<li><img src="https://en.js.cx/carousel/4.png"></li>
<li><img src="https://en.js.cx/carousel/5.png"></li>
<li><img src="https://en.js.cx/carousel/6.png"></li>
<li><img src="https://en.js.cx/carousel/7.png"></li>
<li><img src="https://en.js.cx/carousel/8.png"></li>
<li><img src="https://en.js.cx/carousel/9.png"></li>
<li><img src="https://en.js.cx/carousel/10.png"></li>
</ul>
</div>
<button class="arrow next"></button>
</div>
<script>
/* label the images, just for convenience, to visually track them */
let i = 1;
for(let li of carousel.querySelectorAll('li')) {
li.style.position = 'relative';
li.insertAdjacentHTML('beforeend', `<span style="position:absolute;left:0;top:0">${i}</span>`);
i++;
}
/* configuration */
let width = 130; // image width
let count = 3; // visible images count
let list = carousel.querySelector('ul');
let listElems = carousel.querySelectorAll('li');
let position = 0; // ribbon scroll position
carousel.querySelector('.prev').onclick = function() {
// shift left
position += width * count;
// can't move to the left too much, end of images
position = Math.min(position, 0)
list.style.marginLeft = position + 'px';
};
carousel.querySelector('.next').onclick = function() {
// shift right
position -= width * count;
// can only shift the ribbbon for (total ribbon length - visible count) images
position = Math.max(position, -width * (listElems.length - count));
list.style.marginLeft = position + 'px';
};
</script>
</body>
</html>

View file

@ -0,0 +1,70 @@
body {
padding: 10px;
}
.carousel {
position: relative;
width: 398px;
padding: 10px 40px;
border: 1px solid #CCC;
border-radius: 15px;
background: #eee;
}
.carousel img {
width: 130px;
height: 130px;
/* make it block to remove space around images */
display: block;
}
.arrow {
position: absolute;
top: 60px;
padding: 0;
background: #ddd;
border-radius: 15px;
border: 1px solid gray;
font-size: 24px;
line-height: 24px;
color: #444;
display: block;
}
.arrow:focus {
outline: none;
}
.arrow:hover {
background: #ccc;
cursor: pointer;
}
.prev {
left: 7px;
}
.next {
right: 7px;
}
.gallery {
width: 390px;
overflow: hidden;
}
.gallery ul {
height: 130px;
width: 9999px;
margin: 0;
padding: 0;
list-style: none;
transition: margin-left 250ms;
/* remove white-space between inline-block'ed li */
/* http://davidwalsh.name/remove-whitespace-inline-block */
font-size: 0;
}
.gallery li {
display: inline-block;
}

View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- create your markup and styles -->
<button class="arrow"></button>
<button class="arrow"></button>
<ul>
<li><img src="https://en.js.cx/carousel/1.png"></li>
<li><img src="https://en.js.cx/carousel/2.png"></li>
<li><img src="https://en.js.cx/carousel/3.png"></li>
<li><img src="https://en.js.cx/carousel/4.png"></li>
<li><img src="https://en.js.cx/carousel/5.png"></li>
<li><img src="https://en.js.cx/carousel/6.png"></li>
<li><img src="https://en.js.cx/carousel/7.png"></li>
<li><img src="https://en.js.cx/carousel/8.png"></li>
<li><img src="https://en.js.cx/carousel/9.png"></li>
<li><img src="https://en.js.cx/carousel/10.png"></li>
</ul>
<script>
// label the images to visually track them, just for convenience,
// this code can be removed
let i = 1;
for(let li of carousel.querySelectorAll('li')) {
li.style.position = 'relative';
li.insertAdjacentHTML('beforeend', `<span style="position:absolute;left:0;top:0">${i}</span>`);
i++;
}
// ...your code to make carousel alive!
</script>
</body>
</html>

View file

@ -0,0 +1,38 @@
.arrow {
padding: 0;
background: #ddd;
border-radius: 15px;
border: 1px solid gray;
font-size: 24px;
line-height: 24px;
color: #444;
display: block;
}
.arrow:focus {
outline: none;
}
.arrow:hover {
background: #ccc;
cursor: pointer;
}
ul {
height: 130px;
width: 9999px;
margin: 0;
padding: 0;
list-style: none;
font-size: 0;
}
ul img {
width: 130px;
height: 130px;
display: block; /* removes extra space near images */
}
ul li {
display: inline-block; /* removes extra space between list items
}

View file

@ -0,0 +1,13 @@
importance: 4
---
# Carousel
Create a "carousel" -- a ribbon of images that can be scrolled by clicking on arrows.
[iframe height=200 src="solution"]
Later we can add more features to it: infinite scrolling, dynamic loading etc.
P.S. For this task HTML/CSS structure is actually 90% of the solution.

View file

@ -0,0 +1,388 @@
# Introduction to browser events
*An event* is a signal that something has happened. All DOM nodes generate such signals (but events are not limited to DOM).
[cut]
Here's a list of the most useful DOM events, just to take a look at:
**Mouse events:**
- `click` -- when the mouse clicks on an element (touchscreen devices generate it on a tap).
- `contextmenu` -- when the mouse right-clicks on an element.
- `mouseover` -- when the mouse cursor comes over an element.
- `mousedown` and `mouseup` -- when the mouse button is pressed and released over an element.
- `mousemove` -- when the mouse is moved.
**Form element events:**
- `submit` -- when the visitor submits a `<form>`.
- `focus` -- when the visitor focuses on an element, e.g. on an `<input>`.
**Keyboard events:**
- `keydown` and `keyup` -- when the visitor presses and then releases the button.
**Document events**
- `DOMContentLoaded` -- when the HTML is loaded and processed, DOM is fully built.
**CSS events:**
- `transitionend` -- when a CSS-animation finishes.
There are many other events.
## Event handlers
To react on events we can assign a *handler* -- a function that runs in case of an event.
Handlers is a way to run JavaScript code in case of user actions.
There are several ways to assign a handler. Let's see them, starting from the simplest one.
### HTML-attribute
A handler can be set in HTML with an attribute named `on<event>`.
For instance, to assign a `click` handler for an `input`, we can use `onclick`, like here:
```html run
<input value="Click me" *!*onclick="alert('Click!')"*/!* type="button">
```
On mouse click, the code inside `onclick` runs.
Please note that inside `onclick` we use single quotes, because the attribute itself is in double quotes. If we fforget that the code is inside the attribute and use double quotes inside, like this: `onclick="alert("Click!")"`, then it won't work right.
An HTML-attribute is not a convenient place to write a lot of code, so we'd better create a JavaScript function and call it there.
Here a click runs the function `countRabbits()`:
```html autorun height=50
<script>
function countRabbits() {
for(let i=1; i<=3; i++) {
alert("Rabbit number " + i);
}
}
</script>
<input type="button" *!*onclick="countRabbits()"*/!* value="Count rabbits!">
```
As we know, HTML attribute names are not case-sensitive, so `ONCLICK` works as well as `onClick` and `onCLICK`... But usually attributes are lowercased: `onclick`.
### DOM property
We can assign a handler using a DOM property `on<event>`.
For instance, `elem.onclick`:
```html autorun
<input id="elem" type="button" value="Click me">
<script>
*!*
elem.onclick = function() {
alert('Thank you');
};
*/!*
</script>
```
If the handler is assigned using an HTML-attribute then the browser reads it, creates a new function from the attribute content and writes it to the DOM property.
So this way is actually the same as the previous one.
**The handler is always in the DOM property: the HTML-attribute is just one of the ways to initialize it.**
These two code pieces work the same:
1. Only HTML:
```html autorun height=50
<input type="button" *!*onclick="alert('Click!')"*/!* value="Button">
```
2. HTML + JS:
```html autorun height=50
<input type="button" id="button" value="Button">
<script>
*!*
button.onclick = function() {
alert('Click!');
};
*/!*
</script>
```
**As there's only one `onclick` property, we can't assign more than one event handler.**
In the example below adding a handler with JavaScript overwrites the existing handler:
```html run height=50 autorun
<input type="button" id="elem" onclick="alert('Before')" value="Click me">
<script>
*!*
elem.onclick = function() { // overwrites the existing handler
alert('After'); // only this will be shown
};
*/!*
</script>
```
By the way, we can assign an existing function as a handler directly:
```js
function sayThanks() {
alert('Thanks!');
}
elem.onclick = sayThanks;
```
To remove a handler -- assign `elem.onclick = null`.
## Accessing the element: this
The value of `this` inside a handler is the element. The one which has the handler on it.
In the code below `button` shows its contents using `this.innerHTML`:
```html height=50 autorun
<button onclick="alert(this.innerHTML)">Click me</button>
```
## Possible mistakes
If you're starting to work with event -- please note some subtleties.
**The function should be assigned as `sayThanks`, not `sayThanks()`.**
```js
// right
button.onclick = sayThanks;
// wrong
button.onclick = sayThanks();
```
If we add brackets, then `sayThanks()` -- will be the *result* of the function execution, so `onclick` in the last code becomes `undefined` (the function returns nothing). That won't work.
...But in the markup we do need the brackets:
```html
<input type="button" id="button" onclick="sayThanks()">
```
The difference is easy to explain. When the browser reads the attribute, it creates a handler function with the body from its content.
So the last example is the same as:
```js
button.onclick = function() {
*!*
sayThanks(); // the attribute content
*/!*
};
```
**Use functions, not strings.**
The assignment `elem.onclick = "alert(1)"` would work too. It works for compatibility reasons, but strongly not recommended.
**Don't use `setAttribute` for handlers.**
Such a call won't work:
```js run no-beautify
// a click on <body> will generate errors,
// because attributes are always strings, function becomes a string
document.body.setAttribute('onclick', function() { alert(1) });
```
**DOM-property case matters.**
Assign a handler to `elem.onclick`, not `elem.ONCLICK`, because DOM properties are case-sensitive.
## addEventListener
The fundamental problem of the aforementioned ways to assign handlers -- we can't assign multiple handlers to one event.
For instance, one part of our code wants to highlight a button on click, and another one wants to show a message.
We'd like to assign two event handlers for that. But a new DOM property will overwrite the existing one:
```js no-beautify
input.onclick = function() { alert(1); }
// ...
input.onclick = function() { alert(2); } // replaces the previous handler
```
Web-standard developers understood that long ago and suggested an alternative way of managing handlers using special methods `addEventListener` and `removeEventListener`. They are free of such a problem.
The syntax to add a handler:
```js
element.addEventListener(event, handler[, phase]);
```
`event`
: Event name, e.g. `"click"`.
`handler`
: The handler function.
`phase`
: An optional argument, the "phase" for the handler to work. To be covered later. Usually we don't use it.
To remove the handler, use `removeEventListener`:
```js
// exactly the same arguments as addEventListener
element.removeEventListener(event, handler[, phase]);
```
````warn header="Removal requires the same function"
To remove a handler we should pass exactly the same function as was assigned.
That doesn't work:
```js no-beautify
elem.addEventListener( "click" , () => alert('Thanks!'));
// ....
elem.removeEventListener( "click", () => alert('Thanks!'));
```
The handler won't be removed, because `removeEventListener` gets another function -- with the same code, but that doesn't matter.
Here's the right way:
```js
function handler() {
alert( 'Thanks!' );
}
input.addEventListener("click", handler);
// ....
input.removeEventListener("click", handler);
```
Please note -- if we don't store the function in a variable, then we can't remove it. There's no way to "read back" handlers assigned by `addEventListener`.
````
Multiple calls to `addEventListener` allow to add multiple handlers, like this:
```html run no-beautify
<input id="elem" type="button" value="Click me"/>
<script>
function handler1() {
alert('Thanks!');
};
function handler2() {
alert('Thanks again!');
}
*!*
elem.onclick = () => alert("Hello");
elem.addEventListener("click", handler1); // Thanks!
elem.addEventListener("click", handler2); // Thanks again!
*/!*
</script>
```
As we can see in the example above, we can set handlers *both* using a DOM-property and `addEventListener`. But generally we use only one of these ways.
````warn header="For some events handlers only work with `addEventListener`"
There exist events that can't be assigned via a DOM-property. Must use `addEventListener`.
For instance, the event `transitionend` (CSS animation finished) is like that.
Try the code below. In most browsers only the second handler works, not the first one.
```html run
<style>
input {
transition: width 1s;
width: 100px;
}
.wide {
width: 300px;
}
</style>
<input type="button" id="elem" onclick="this.classList.toggle('wide')" value="Click me">
<script>
elem.ontransitionend = function() {
alert("DOM property"); // doesn't work
};
*!*
elem.addEventListener("transitionend", function() {
alert("addEventListener"); // shows up when the animation finishes
});
*/!*
</script>
```
````
## Event object
To properly handle an event we'd want to know more about what's happened. Not just a "click" or a "keypress", but what were the pointer coordinates? Which key was pressed? And so on.
When an event happens, the browser creates an *event object*, puts details into it and passes it as an argument to the handler.
Here's an example of getting mouse coordinates from the event object:
```html run
<input type="button" value="Click me" id="elem">
<script>
elem.onclick = function(*!*event*/!*) {
// show event type, element and coordinates of the click
alert(event.type + " at " + event.currentTarget);
alert(event.clientX + ":" + event.clientY);
};
</script>
```
Some properties of `event` object:
`event.type`
: Event type, here it's `"click"`.
`event.currentTarget`
: Element that handled the event. That's exactly the same as `this`, unless you bind `this` to something else, and then `event.currentTarget` becomes useful.
`event.clientX / event.clientY`
: Window-relative coordinates of the cursor, for mouse events.
There are more properties. They depend on the event type, so we'll study them later when come to different events in details.
````smart header="The event object is also accessible from HTML"
If we assign a handler in HTML, we can also use the `event` object, like this:
```html autorun height=60
<input type="button" onclick="*!*alert(event.type)*/!*" value="Event type">
```
That's possible because when the browser reads the attribute, it creates a handler like this: `function(event) { alert(event.type) }`. That is: its first argument is called `"event"`, and the body is taken from the attribute.
````
## Summary
There are 3 ways to assign event handlers:
1. HTML attribute: `onclick="..."`.
2. DOM property: `elem.onclick = function`.
3. Methods `elem.addEventListener(event, handler[, phase])`, to remove: `removeEventListener`.
HTML attributes are used sparingly, because JavaScript in the middle of an HTML tag looks a little bit odd and alien. Also can't write lots of code in there.
DOM properties are ok to use, but we can't assign more than one handler of the particular event. In many cases that limitation is not pressing.
The last way is the most flexible, but it is also the longest to write. There are few events that only work with it, for instance `transtionend` and `DOMContentLoaded` (to be covered).
When a handler is called, it gets an event objects as the first argument. It contains details about what's happened. We'll see more of them later.
As of now we're just starting to work with events. More details in the next chapters.

View file

@ -0,0 +1,53 @@
<style>
/*
.d0 { text-align:center;margin:auto; }
.d1 p { margin: 0 }
.d1 {
margin:2em;
background-color:green;
width:13em;
height:13em;
text-align:center;
}
.d1 .number {
line-height: 2em;
}
.d2 {
text-align:center;
margin:auto;
background-color:blue;
width:9em;
height:9em;
}
.d1 .d2 ,number {
line-height: 2em;
}
.d3 {
text-align:center;
margin:auto;
background-color:red;
width:5em;
height:5em;
}
.d1 .d2 .d3 .number {
line-height: 5em;
}
.d1 .d2 .d2a {
color:white;
line-height: 2em;
}
*/
</style>
<script>
/*
function highlightMe(elem) {
elem.style.backgroundColor='yellow'
alert(elem.className)
elem.style.backgroundColor = ''
}
function highlightMe2(e) {
highlightMe(e.currentTarget);
}
*/
</script>