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,40 @@
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" href="messages.css">
<meta charset="utf-8">
</head>
<body>
<div id="container">
<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>
<button class="remove-button">[x]</button>
</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>
<button class="remove-button">[x]</button>
</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>
<button class="remove-button">[x]</button>
</div>
</div>
<script>
container.onclick = function(event) {
if (event.target.className != 'remove-button') return;
let pane = event.target.closest('.pane');
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,35 @@
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" href="messages.css">
<meta charset="utf-8">
</head>
<body>
<div id="container">
<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>
<button class="remove-button">[x]</button>
</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>
<button class="remove-button">[x]</button>
</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>
<button class="remove-button">[x]</button>
</div>
</div>
<script>
// ...your code...
</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,13 @@
importance: 5
---
# Hide messages with delegation
There's a list of messages with removal buttons `[x]`. Make the buttons work.
Like this:
[iframe src="solution" height=420]
P.S. Should be only one event listener on the container, use event delegation.

View file

@ -0,0 +1,4 @@
The solution has two parts.
1. Wrap every tree node title into `<span>`. Then we can CSS-style them on `:hover` and handle clicks exactly on text, because `<span>` width is exactly the text width (unlike without it).
2. Set a handler to the `tree` root node and handle clicks on that `<span>` titles.

View file

@ -0,0 +1,80 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
.tree span:hover {
font-weight: bold;
}
.tree span {
cursor: pointer;
}
</style>
<meta charset="utf-8">
</head>
<body>
<ul class="tree" id="tree">
<li>Animals
<ul>
<li>Mammals
<ul>
<li>Cows</li>
<li>Donkeys</li>
<li>Dogs</li>
<li>Tigers</li>
</ul>
</li>
<li>Other
<ul>
<li>Snakes</li>
<li>Birds</li>
<li>Lizards</li>
</ul>
</li>
</ul>
</li>
<li>Fishes
<ul>
<li>Aquarium
<ul>
<li>Guppy</li>
<li>Angelfish</li>
</ul>
</li>
<li>Sea
<ul>
<li>Sea trout</li>
</ul>
</li>
</ul>
</li>
</ul>
<script>
// move all text into <span>
// they occupy exactly the place necessary for the text,
for (let li of tree.querySelectorAll('li')) {
let span = document.createElement('span');
li.prepend(span);
span.append(span.nextSibling); // move the text node into span
}
// catch clicks on whole tree
tree.onclick = function(event) {
if (event.target.tagName != 'SPAN') {
return;
}
let childrenContainer = target.parentNode.querySelector('ul');
if (!childrenContainer) return; // no children
childrenContainer.hidden = !childrenContainer.hidden;
}
</script>
</body>
</html>

View file

@ -0,0 +1,46 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<ul class="tree" id="tree">
<li>Animals
<ul>
<li>Mammals
<ul>
<li>Cows</li>
<li>Donkeys</li>
<li>Dogs</li>
<li>Tigers</li>
</ul>
</li>
<li>Other
<ul>
<li>Snakes</li>
<li>Birds</li>
<li>Lizards</li>
</ul>
</li>
</ul>
</li>
<li>Fishes
<ul>
<li>Aquarium
<ul>
<li>Guppy</li>
<li>Angelfish</li>
</ul>
</li>
<li>Sea
<ul>
<li>Sea trout</li>
</ul>
</li>
</ul>
</li>
</ul>
</body>
</html>

View file

@ -0,0 +1,14 @@
importance: 5
---
# Tree menu
Create a tree that shows/hides node children on click:
[iframe border=1 src="solution"]
Requirements:
- Only one event handler (use delegation)
- A click outside the node title (on an empty space) should not do anything.

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,98 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
table {
border-collapse: collapse;
}
th, td {
border: 1px solid black;
padding: 4px;
}
th {
cursor: pointer;
}
th:hover {
background: yellow;
}
</style>
</head>
<body>
<table id="grid">
<thead>
<tr>
<th data-type="number">Age</th>
<th data-type="string">Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>5</td>
<td>John</td>
</tr>
<tr>
<td>2</td>
<td>Pete</td>
</tr>
<tr>
<td>12</td>
<td>Ann</td>
</tr>
<tr>
<td>9</td>
<td>Eugene</td>
</tr>
<tr>
<td>1</td>
<td>Ilya</td>
</tr>
</tbody>
</table>
<script>
grid.onclick = function(e) {
if (e.target.tagName != 'TH') return;
let th = e.target;
// if TH, then sort
// cellIndex is the number of th:
// 0 for the first column
// 1 for the second column, etc
sortGrid(th.cellIndex, th.dataset.type);
};
function sortGrid(colNum, type) {
let tbody = grid.querySelector('tbody');
let rowsArray = Array.from(tbody.rows);
// compare(a, b) compares two rows, need for sorting
let compare;
switch (type) {
case 'number':
compare = function(rowA, rowB) {
return rowA.cells[colNum].innerHTML - rowB.cells[colNum].innerHTML;
};
break;
case 'string':
compare = function(rowA, rowB) {
return rowA.cells[colNum].innerHTML > rowB.cells[colNum].innerHTML ? 1 : -1;
};
break;
}
// sort
rowsArray.sort(compare);
tbody.append(...rowsArray);
}
</script>
</body>
</html>

View file

@ -0,0 +1,61 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
table {
border-collapse: collapse;
}
th, td {
border: 1px solid black;
padding: 4px;
}
th {
cursor: pointer;
}
th:hover {
background: yellow;
}
</style>
</head>
<body>
<table id="grid">
<thead>
<tr>
<th data-type="number">Age</th>
<th data-type="string">Name</th>
</tr>
</thead>
<tbody>
<tr>
<td>5</td>
<td>John</td>
</tr>
<tr>
<td>2</td>
<td>Pete</td>
</tr>
<tr>
<td>12</td>
<td>Ann</td>
</tr>
<tr>
<td>9</td>
<td>Eugene</td>
</tr>
<tr>
<td>1</td>
<td>Ilya</td>
</tr>
</tbody>
</table>
<script>
// ...your code...
</script>
</body>
</html>

View file

@ -0,0 +1,43 @@
importance: 4
---
# Sortable table
Make the table sortable: clicks on `<th>` elements should sort it by corresponding column.
Each `<th>` has the type in the attribute, like this:
```html
<table id="grid">
<thead>
<tr>
*!*
<th data-type="number">Age</th>
<th data-type="string">Name</th>
*/!*
</tr>
</thead>
<tbody>
<tr>
<td>5</td>
<td>John</td>
</tr>
<tr>
<td>10</td>
<td>Ann</td>
</tr>
...
</tbody>
</table>
```
In the example above the first column has numbers, and the second one -- strings. The sorting function should handle sort according to the type.
Only `"string"` and `"number"` types should be supported.
The working example:
[iframe border=1 src="solution" height=190]
P.S. The table can be big, with any number of rows and columns.

View file

@ -0,0 +1,80 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
body {
height: 2000px;
/* make body scrollable, the tooltip should work after the scroll */
}
.tooltip {
position: fixed;
padding: 10px 20px;
border: 1px solid #b3c9ce;
border-radius: 4px;
text-align: center;
font: italic 14px/1.3 arial, sans-serif;
color: #333;
background: #fff;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .3);
}
</style>
</head>
<body>
<p>LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa</p>
<p>LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa</p>
<button data-tooltip="the tooltip is longer than the element">Short button</button>
<button data-tooltip="HTML<br>tooltip">One more button</button>
<p>Scroll the page to make buttons appear on the top, check if the tooltips show up correctly.</p>
<script>
let tooltipElem;
document.onmouseover = function(event) {
let target = event.target;
// if we have tooltip HTML...
let tooltipHtml = target.dataset.tooltip;
if (!tooltipHtml) return;
// ...create the tooltip element
tooltipElem = document.createElement('div');
tooltipElem.className = 'tooltip';
tooltipElem.innerHTML = tooltipHtml;
document.body.append(tooltipElem);
// position it above the annotated element (top-center)
let coords = target.getBoundingClientRect();
let left = coords.left + (target.offsetWidth - tooltipElem.offsetWidth) / 2;
if (left < 0) left = 0; // don't cross the left window edge
let top = coords.top - tooltipElem.offsetHeight - 5;
if (top < 0) { // if crossing the top window edge, show below instead
top = coords.top + target.offsetHeight + 5;
}
tooltipElem.style.left = left + 'px';
tooltipElem.style.top = top + 'px';
};
document.onmouseout = function(e) {
if (tooltipElem) {
tooltipElem.remove();
tooltipElem = null;
}
};
</script>
</body>
</html>

View file

@ -0,0 +1,43 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
body {
height: 2000px;
/* make body scrollable, the tooltip should work after the scroll */
}
.tooltip {
/* some styles for the tooltip, you can use your own instead */
position: fixed;
padding: 10px 20px;
border: 1px solid #b3c9ce;
border-radius: 4px;
text-align: center;
font: italic 14px/1.3 arial, sans-serif;
color: #333;
background: #fff;
box-shadow: 3px 3px 3px rgba(0, 0, 0, .3);
}
</style>
</head>
<body>
<p>LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa</p>
<p>LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa LaLaLa</p>
<button data-tooltip="the tooltip is longer than the element">Short button</button>
<button data-tooltip="HTML<br>tooltip">One more button</button>
<p>Scroll the page to make buttons appear on the top, check if the tooltips show up correctly.</p>
<script>
// ...your code...
</script>
</body>
</html>

View file

@ -0,0 +1,36 @@
importance: 5
---
# Tooltip behavior
Create JS-code for the tooltip behavior.
When a mouse comes over an element with `data-tooltip`, the tooltip should appear over it, and when it's gone then hide.
An example of annotated HTML:
```html
<button data-tooltip="the tooltip is longer than the element">Short button</button>
<button data-tooltip="HTML<br>tooltip">One more button</button>
```
Should work like this:
[iframe src="solution" height=200 border=1]
In this task we assume that all elements with `data-tooltip` have only text inside. No nested tags.
Details:
- The tooltip should not cross window edges. Normally it should be above the element, but if the element is at the page top and there's no space for the tooltip, then below it.
- The tooltip is given in the `data-tooltip` attribute. It can be arbitrary HTML.
You'll need two events here:
- `mouseover` triggers when a pointer comes over an element.
- `mouseout` triggers when a pointer leaves an element.
Please use event delegation: set up two handlers on `document` to track all "overs" and "outs" from elements with `data-tooltip` and manage tooltips from there.
After the behavior is implemented, even people unfamiliar with JavaScript can add annotated elements.
P.S. To keep things natural and simple: only one tooltip may show up at a time.

View file

@ -0,0 +1,272 @@
# Event delegation
Capturing and bubbling allow to implement one of most powerful event handling patterns called *event delegation*.
The idea is that if we have a lot of elements handled in a similar way, then instead of assigning a handler to each of them -- we put a single handler on their common ancestor.
In the handler we get `event.target`, see where the event actually happened and handle it.
Let's see an example -- the [Ba-Gua diagram](http://en.wikipedia.org/wiki/Ba_gua) reflecting the ancient Chinese philosophy.
Here it is:
[iframe height=350 src="bagua" edit link]
The HTML is like this:
```html
<table>
<tr>
<th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th>
</tr>
<tr>
<td>...<strong>Northwest</strong>...</td>
<td>...</td>
<td>...</td>
</tr>
<tr>...2 more lines of this kind...</tr>
<tr>...2 more lines of this kind...</tr>
</table>
```
The table has 9 cells, but there could be 99 or 9999, doesn't matter.
**Our task is to highlight a cell `<td>` on click.**
Instead of assign an `onclick` handler to each `<td>` (can be many) -- we'll setup the "catch-all" handler on `<table>` element.
It will use `event.target` to get the clicked element and highlight it.
The code:
```js
let selectedTd;
*!*
table.onclick = function(event) {
let target = event.target; // where was the click?
if (target.tagName != 'TD') return; // not on TD? Then we're not interested
highlight(target); // highlight it
};
*/!*
function highlight(td) {
if (selectedTd) { // remove the existing highlight if any
selectedTd.classList.remove('highlight');
}
selectedTd = td;
selectedTd.classList.add('highlight'); // highlight the new td
}
```
Such a code doesn't care how many cells there are in the table. We can add/remove `<td>` dynamically at any time and the highlighting will still work.
Still, there's a drawback.
The click may occur no on the `<td>`, but inside it.
In our case if we take a look inside the HTML, we can see nested tags inside `<td>`, like `<strong>`:
```html
<td>
*!*
<strong>Northwest</strong>
*/!*
...
</td>
```
Naturally, if a click happens on that `<strong>` then it becomes the value of `event.target`.
![](bagua-bubble.png)
In the handler `table.onclick` we should take such `event.target` and find out whether the click was inside `<td>` or not.
Here's the improved code:
```js
table.onclick = function(event) {
let td = event.target.closest('td'); // (1)
if (!td) return; // (2)
if (!table.contains(td)) return; // (3)
highlight(td); // (4)
};
```
Explanations:
1. The method `elem.closest(selector)` returns the nearest ancestor that matches the selector. In our case we look for `<td>` on the way up from the source element.
2. If `event.target` is not inside any `<td>`, then the call returns `null`, and we don't have to do anything.
3. In case of nested tables, `event.target` may be a `<td>` outside of the current table. So we check if that's actually *our* `<td>`.
4. And, if it is so, highlight it.
## Delegation example: actions in markup
The event delegation may be used to optimize event handling. We use a single handler for similar actions on many elements. Like we did it for highlighting `<td>`.
But we can also use a single handler as an entry point for many different things.
For instance, we want to make a menu with buttons "Save", "Load", "Search" and so on. And there's an object with methods `save`, `load`, `search`....
The first idea may be to assign a separate handler to each button. But there's a more elegant solution. We can add a handler for the whole menu and `data-action` attributes for buttons that has the method to call:
```html
<button *!*data-action="save"*/!*>Click to Save</button>
```
The handler reads the attribute and executes the method. Take a look at the working example:
```html autorun height=60 run
<div id="menu">
<button data-action="save">Save</button>
<button data-action="load">Load</button>
<button data-action="search">Search</button>
</div>
<script>
class Menu {
constructor(elem) {
this._elem = elem;
elem.onclick = this.onClick.bind(this); // (*)
}
save() {
alert('saving');
}
load() {
alert('loading');
}
search() {
alert('searching');
}
onClick(event) {
*!*
let action = event.target.dataset.action;
if (action) {
this[action]();
}
*/!*
};
}
new Menu(menu);
</script>
```
Please note that `this.onClick` is bound to `this` in `(*)`. That's important, because otherwise `this` inside it would reference the DOM element (`elem`), not the menu object, and `this[action]` would not be what we need.
So, what the delegation gives us here?
```compare
+ We don't need to write the code to assign a handler to each button. Just make a method and put it in the markup.
+ The HTML structure is flexible, we can add/remove buttons at any time.
```
We could also use classes `.action-save`, `.action-load`, but an attribute `data-action` is better semantically. And we can use it in CSS rules too.
## The "behavior" pattern
We can also use event delegation to add "behaviors" to elements *declaratively*, with special attributes and classes.
The pattern has two parts:
1. We add a special attribute to an element.
2. A document-wide handler tracks events, and if an event happens on an attributed element -- performs the action.
### Counter
For instance, here the attribute `data-counter` adds a behavior: "increase on click" to buttons:
```html run autorun height=60
Counter: <input type="button" value="1" data-counter>
One more counter: <input type="button" value="2">
<script>
document.addEventListener('click', function(event) {
if (event.target.dataset.counter != undefined) { // if the attribute exists...
event.target.value++;
}
});
</script>
```
If we click a button -- its value is increased. Not buttons, but the general approach is important here.
There can be as many attributes with `data-counter` as we want. We can add new ones to HTML at any moment. Using the event delegation we "extended" HTML, added an attribute that describes a new behavior.
```warn header="For document-level handlers -- always `addEventListener`"
When we assign an event handler to the `document` object, we should always use `addEventListener`, not `document.onclick`, because the latter will cause conflicts: new handlers overwrite old ones.
For real projects it's normal that there are many handlers on `document` set by different parts of the code.
```
### Toggler
One more example. A click on an element with the attribute `data-toggle-id` will show/hide the element with the given `id`:
```html autorun run height=60
<button *!*data-toggle-id="subscribe-mail"*/!*>
Show the subscription form
</button>
<form id="subscribe-mail" hidden>
Your mail: <input type="email">
</form>
<script>
*!*
document.addEventListener('click', function(event) {
let id = event.target.dataset.toggleId;
if (!id) return;
let elem = document.getElementById(id);
elem.hidden = !elem.hidden;
});
*/!*
</script>
```
Let's note once again what we did. Now, to add toggling functionality to an element -- there's no need to know JavaScript, just use the attribute `data-toggle-id`.
That may become really convenient -- no need to write JavaScript for every such element. Just use the behavior. The document-level handler makes it work for any element of the page.
We can combine multiple behaviors on a single element as well.
The "behavior" pattern can be an alternative of mini-fragments of JavaScript.
## Summary
Event delegation is really cool! It's one of the most helpful patterns for DOM events.
It's often used to add same handling for many similar elements, but not only for that.
The algorithm:
1. Put a single handler on the container.
2. In the handler -- check the source element `event.target`.
3. If the event happened inside an element that interests us, then handle the event.
Benefits:
```compare
+ Simplifies initialization and saves memory: no need to add many handlers.
+ Less code: when adding or removing elements, no need to add/remove handlers.
+ DOM modifications: we can mass add/remove elements with `innerHTML` and alike.
```
The delegation has its limitations of course:
```compare
- First, the event must be bubbling. Some events do not bubble. Also, low-level handlers should not use `event.stopPropagation()`.
- Second, the delegation may add CPU load, because the container-level handler reacts on events in any place of the container, no matter if they interest us or not. But usually the load is negligible, so we don't take it into account.
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -0,0 +1,59 @@
#bagua-table th {
text-align: center;
font-weight: bold;
}
#bagua-table td {
width: 150px;
white-space: nowrap;
text-align: center;
vertical-align: bottom;
padding-top: 5px;
padding-bottom: 12px;
}
#bagua-table .nw {
background: #999;
}
#bagua-table .n {
background: #03f;
color: #fff;
}
#bagua-table .ne {
background: #ff6;
}
#bagua-table .w {
background: #ff0;
}
#bagua-table .c {
background: #60c;
color: #fff;
}
#bagua-table .e {
background: #09f;
color: #fff;
}
#bagua-table .sw {
background: #963;
color: #fff;
}
#bagua-table .s {
background: #f60;
color: #fff;
}
#bagua-table .se {
background: #0c3;
color: #fff;
}
#bagua-table .highlight {
background: red;
}

View file

@ -0,0 +1,94 @@
<!DOCTYPE HTML>
<html>
<body>
<link type="text/css" rel="stylesheet" href="bagua.css">
<table id="bagua-table">
<tr>
<th colspan="3"><em>Bagua</em> Chart: Direction, Element, Color, Meaning</th>
</tr>
<tr>
<td class="nw"><strong>Northwest</strong>
<br>Metal
<br>Silver
<br>Elders
</td>
<td class="n"><strong>North</strong>
<br>Water
<br>Blue
<br>Change
</td>
<td class="ne"><strong>Northeast</strong>
<br>Earth
<br>Yellow
<br>Direction
</td>
</tr>
<tr>
<td class="w"><strong>West</strong>
<br>Metal
<br>Gold
<br>Youth
</td>
<td class="c"><strong>Center</strong>
<br>All
<br>Purple
<br>Harmony
</td>
<td class="e"><strong>East</strong>
<br>Wood
<br>Blue
<br>Future
</td>
</tr>
<tr>
<td class="sw"><strong>Southwest</strong>
<br>Earth
<br>Brown
<br>Tranquility
</td>
<td class="s"><strong>South</strong>
<br>Fire
<br>Orange
<br>Fame
</td>
<td class="se"><strong>Southeast</strong>
<br>Wood
<br>Green
<br>Romance
</td>
</tr>
</table>
<script>
let table = document.getElementById('bagua-table');
let selectedTd;
table.onclick = function(event) {
let target = event.target;
while (target != this) {
if (target.tagName == 'TD') {
highlight(target);
return;
}
target = target.parentNode;
}
}
function highlight(node) {
if (selectedTd) {
selectedTd.classList.remove('highlight');
}
selectedTd = node;
selectedTd.classList.add('highlight');
}
</script>
</body>
</html>