up
|
@ -1,8 +1,8 @@
|
|||
<style>
|
||||
span.devtools {
|
||||
display: inline-block;
|
||||
background-image: url(/article/debugging-chrome/statusbarButtonGlyphs.svg);
|
||||
background-image: url(/article/debugging-chrome/toolbarButtonGlyphs.svg);
|
||||
height:16px;
|
||||
width:16px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -97,8 +97,8 @@ CSSOM specification
|
|||
: Describes styles, manipulations with them and their binding to documents, see <https://www.w3.org/TR/cssom-1/>.
|
||||
|
||||
HTML specification
|
||||
: Describes HTML language and also BOM (browser object model) -- various browser functions: `setTimeout`, `alert`, `location` and so on, see <https://html.spec.whatwg.org>.
|
||||
: Describes HTML language (tags etc) and also BOM (browser object model) -- various browser functions: `setTimeout`, `alert`, `location` and so on, see <https://html.spec.whatwg.org>. It takes DOM specification and extends it with many additional properties and methods.
|
||||
|
||||
Now we'll get down to learning DOM, because the document plays the central role in the UI, and working with it is the most complex part.
|
||||
|
||||
Please note the links above, because there's so many stuff to learn, it's impossible to cover and remember everything. When you'd like to read about a property or a method -- can go <https://developer.mozilla.org/en-US/search>, but looking up the corresponding spec is even better. Longer to read, but will make your fundamental knowledge stronger.
|
||||
Please note the links above, because there's so many stuff to learn, it's impossible to cover and remember everything. When you'd like to read about a property or a method -- can go <https://developer.mozilla.org/en-US/search>, but looking up the corresponding spec may be better. Longer to read, but will make your fundamental knowledge stronger.
|
||||
|
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
|
@ -1,20 +0,0 @@
|
|||
Выведет `null`, так как на момент выполнения скрипта тег `<body>` ещё не обработан браузером.
|
||||
|
||||
Попробуйте в действии:
|
||||
|
||||
```html run
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<script>
|
||||
alert( document.body ); // null
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
Привет, мир!
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Что выведет этот alert?
|
||||
|
||||
Что выведет `alert`?
|
||||
|
||||
```html
|
||||
<html>
|
||||
|
||||
<head>
|
||||
*!*
|
||||
<script>
|
||||
alert( document.body ); // ?
|
||||
</script>
|
||||
*/!*
|
||||
</head>
|
||||
|
||||
<body>
|
||||
Привет, мир!
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
|
@ -69,10 +69,13 @@ var node = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"
|
|||
drawHtmlTree(node, 'div.domtree', 690, 210);
|
||||
</script>
|
||||
|
||||
```smart
|
||||
From here on, spaces and line-breaks on DOM pictures will only be shown for "space-only" nodes that have no other text.
|
||||
```smart header="Starting/ending spaces and line breaks are usually not shown in DOM tools"
|
||||
Tools working with DOM usually do not show spaces at start/end of the text and line-breaks between nodes. That's because they are mainly used to decorate HTML, and do not affect (in most cases) how it is shown.
|
||||
|
||||
On our DOM pictures we'll omit them too where they are not important, to keep things short.
|
||||
```
|
||||
|
||||
|
||||
## Autocorrection
|
||||
|
||||
If the browser encounters malformed HTML, it automatically corrects it when making DOM.
|
||||
|
@ -83,7 +86,7 @@ Like, if the HTML file is a single word `"Hello"`, the browser will wrap it into
|
|||
|
||||
**While generating DOM, browser automatically processes errors in the document, closes tags and so on.**
|
||||
|
||||
Such a document:
|
||||
Such an "invalid" document:
|
||||
|
||||
```html no-beautify
|
||||
<p>Hello
|
||||
|
@ -92,14 +95,14 @@ Such a document:
|
|||
<li>Dad
|
||||
```
|
||||
|
||||
...Will make a respectable DOM, as the browser knows how to read tags (from the spec):
|
||||
...Will become a normal DOM, as the browser read tags and restores the missing parts:
|
||||
|
||||
<div class="domtree"></div>
|
||||
|
||||
<script>
|
||||
var node = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"children":[]},{"name":"BODY","nodeType":1,"children":[{"name":"P","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"Hello"}]},{"name":"LI","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"Mom"}]},{"name":"LI","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"and"}]},{"name":"LI","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"Dad"}]}]}]}
|
||||
|
||||
drawHtmlTree(node, 'div.domtree', 690, 400);
|
||||
drawHtmlTree(node, 'div.domtree', 690, 360);
|
||||
</script>
|
||||
|
||||
````warn header="Tables always have `<tbody>`"
|
||||
|
@ -120,7 +123,7 @@ var node = {"name":"TABLE","nodeType":1,"children":[{"name":"TBODY","nodeType":1
|
|||
drawHtmlTree(node, 'div.domtree', 600, 200);
|
||||
</script>
|
||||
|
||||
Do you see? The `<tbody>` has appeared out of nowhere. Should keep in mind while working with tables to evade surprises.
|
||||
You see? The `<tbody>` has appeared out of nowhere. Should keep in mind while working with tables to evade surprises.
|
||||
````
|
||||
|
||||
## Other node types
|
||||
|
@ -155,49 +158,76 @@ Here we see a new tree node type -- *comment node*.
|
|||
|
||||
We may think -- why a comment is added to the DOM? It doesn't affect the visual representation anyway. But there's a rule -- if something's in HTML, then it also must be in the DOM tree.
|
||||
|
||||
**Everything in HTML has its place in DOM.**
|
||||
**Everything in HTML, even comments, becomes a part of DOM.**
|
||||
|
||||
Even the `<!DOCTYPE...>` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before `<html>`. The pictures above don't show that fact, because we are not going to touch that node, but it's there.
|
||||
Even the `<!DOCTYPE...>` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before `<html>`. We are not going to touch that node, but it's there.
|
||||
|
||||
The `document` object that represents the whole document is, formally, a DOM node as well.
|
||||
|
||||
There are 12 node types. In practice we only work with 4 of them:
|
||||
There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we mainly work with 4 of them:
|
||||
|
||||
1. `document` -- the "entry point" into DOM.
|
||||
2. element nodes -- HTML-tags, the tree building blocks.
|
||||
3. text nodes -- they contain text.
|
||||
4. comments -- sometimes we can put the information there, that won't be shown, but JS can read it from DOM.
|
||||
|
||||
If you want to explore how DOM changes with the document, please consider the [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in/modify the document, and it will show up DOM at instant.
|
||||
## See it yourself
|
||||
|
||||
## Power of DOM
|
||||
To see the DOM structure in real-time, try [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up DOM at instant.
|
||||
|
||||
Why besides nice pictures do we need DOM? To manipulate the page -- read the information from HTML, create and modify elements.
|
||||
## In the browser inspector
|
||||
|
||||
The `<html>` node is accessible as `document.documentElement`, and `<body>` -- as `document.body`.
|
||||
Another way to explore DOM is to use browser developer tools. Actually, that's what we use when developing.
|
||||
|
||||
Then we can do something with the node.
|
||||
To do so, open the web-page [elks.html](elks.html), turn on browser developer tools and switch to Elements tab.
|
||||
|
||||
Like changing the color:
|
||||
```js run
|
||||
document.body.style.background = 'yellow';
|
||||
alert('The body is now yellow');
|
||||
Should look like this:
|
||||
|
||||
// return back in 3 seconds
|
||||
setTimeout(() => document.body.style.background = '', 3000);
|
||||
```
|
||||

|
||||
|
||||
...But actually much more.
|
||||
So you can see the DOM, click on elements, see the details about them and so on.
|
||||
|
||||
In the next chapters we're going to learn it.
|
||||
Please note that the DOM structure show in developer tools is simplified. Text nodes are shown just as text. And there are no "blank" (space only) text nodes at all. That's fine, because most of time we are interested in element nodes.
|
||||
|
||||
Clicking the <span class="devtools" style="background-position:-328px -124px"></span> button allows to choose a node from the webpage using a mouse (or alike) and "inspect" it. Works great when we have a huge HTML page and would like to see the DOM of a particular place in it.
|
||||
|
||||
Another way to do it would be just right-clicking on a webpage and selecting "Inspect element" in the context menu.
|
||||
|
||||

|
||||
|
||||
The right part has tabs:
|
||||
- Styles -- to see CSS applied to the current element rule by rule, including built-in rules (gray). Almost everything can be edited at-place including the dimensions/margins/paddings of the box below.
|
||||
- Computed -- to see CSS applied to the element by property: for each property we can see a rule that gives it (including CSS inheritance and such).
|
||||
- ...there are other less used tabs as well.
|
||||
|
||||
The best way to study them is to click around. Again, please note that most values are in-place editable.
|
||||
|
||||
## Interaction with console
|
||||
|
||||
As we explore the DOM, open/close nodes, we also may want to apply Javascript to it. Like get a node and some code on it, to see how it works. There are few tips to travel between nodes in Elements tab and the console.
|
||||
|
||||
Press `key:Esc` -- it will open console right below the Elements tab.
|
||||
|
||||
Now, the most recently selected element is available as `$0`, the previous one as `$1` etc.
|
||||
|
||||
We can run commands on them, like `$0.style.background = 'red'` here:
|
||||
|
||||

|
||||
|
||||
From the other side, if we're in console and have a node in a variable, then we can use the command `inspect(node)` to see it in the Elements pane. Or we can just output it and explore "at-place".
|
||||
|
||||

|
||||
|
||||
From the next chapter on we'll study how to access and modify the DOM using Javascript. The browser developer tools are the great help in debugging things.
|
||||
|
||||
## Summary
|
||||
|
||||
- DOM-модель -- это внутреннее представление HTML-страницы в виде дерева.
|
||||
- Все элементы страницы, включая теги, текст, комментарии, являются узлами DOM.
|
||||
- У элементов DOM есть свойства и методы, которые позволяют изменять их.
|
||||
- IE8- не генерирует пробельные узлы.
|
||||
An HTML/XML document are represented inside the browser as the DOM tree.
|
||||
|
||||
Кстати, DOM-модель используется не только в JavaScript, это известный способ представления XML-документов.
|
||||
- Tags become element nodes and form the structure.
|
||||
- Text becomes text nodes.
|
||||
- ...etc, everything in HTML has its place in DOM, even comments.
|
||||
|
||||
В следующих главах мы познакомимся с DOM более плотно.
|
||||
We can use developer tools to inspect DOM and modify it manually. There's an extensive documentation about Chrome developer tools at <https://developers.google.com/web/tools/chrome-devtools>, but the best way to learn it is to click here and there, see various menus: most options are obvious. And then later, when you know most stuff, read the docs and pick up the rest.
|
||||
|
||||
DOM nodes have properties and methods that allow to modify them. We'll get down to it in further chapters.
|
||||
|
|
BIN
2-ui/1-document/2-dom-nodes/domconsole0.png
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
2-ui/1-document/2-dom-nodes/domconsole0@2x.png
Normal file
After Width: | Height: | Size: 164 KiB |
BIN
2-ui/1-document/2-dom-nodes/domconsole1.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
2-ui/1-document/2-dom-nodes/domconsole1@2x.png
Normal file
After Width: | Height: | Size: 138 KiB |
11
2-ui/1-document/2-dom-nodes/elks.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
The truth about elks.
|
||||
<ol>
|
||||
<li>An elk is a smart</li>
|
||||
<!-- comment -->
|
||||
<li>...and cunning animal!</li>
|
||||
</ol>
|
||||
</body>
|
||||
</html>
|
BIN
2-ui/1-document/2-dom-nodes/elks.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
2-ui/1-document/2-dom-nodes/elks@2x.png
Normal file
After Width: | Height: | Size: 130 KiB |
8
2-ui/1-document/2-dom-nodes/head.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<style>
|
||||
span.devtools {
|
||||
display: inline-block;
|
||||
background-image: url(/article/dom-nodes/toolbarButtonGlyphs.svg);
|
||||
height:16px;
|
||||
width:16px;
|
||||
}
|
||||
</style>
|
BIN
2-ui/1-document/2-dom-nodes/inspect.png
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
2-ui/1-document/2-dom-nodes/inspect@2x.png
Normal file
After Width: | Height: | Size: 194 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
1035
2-ui/1-document/2-dom-nodes/toolbarButtonGlyphs.svg
Normal file
After Width: | Height: | Size: 48 KiB |
27
2-ui/1-document/3-dom-navigation/1-dom-children/solution.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
There are many ways, for instance:
|
||||
|
||||
|
||||
The `<div>` DOM node:
|
||||
|
||||
```js
|
||||
document.body.firstElementChild
|
||||
// or
|
||||
document.body.children[0]
|
||||
// or (the first node is space, so we take 2nd)
|
||||
document.body.childNodes[1]
|
||||
```
|
||||
|
||||
The `<ul>` DOM node:
|
||||
|
||||
```js
|
||||
document.body.lastElementChild
|
||||
// or
|
||||
document.body.children[1]
|
||||
```
|
||||
|
||||
The second `<li>` (with Pete):
|
||||
|
||||
```js
|
||||
// get <ul>, and then get its last element child
|
||||
document.body.lastElementChild.lastElementChild
|
||||
```
|
24
2-ui/1-document/3-dom-navigation/1-dom-children/task.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# DOM children
|
||||
|
||||
For the page:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<div>Users:</div>
|
||||
<ul>
|
||||
<li>John</li>
|
||||
<li>Pete</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
How to access:
|
||||
- The `<div>` DOM node?
|
||||
- The `<ul>` DOM node?
|
||||
- The second `<li>` (with Pete)?
|
|
@ -0,0 +1,4 @@
|
|||
1. Yes, true. The element `elem.lastChild` is always the last one, it has no `nextSibling`, so if there are children, then yes.
|
||||
2. No, wrong, because `elem.children[0]` is the first child among elements. But there may be non-element nodes before it. So `previousSibling` may be a text node.
|
||||
|
||||
Please note that for both cases if there are no children, then there will be an error. For instance, if `elem.lastChild` is `null`, we can't access `elem.lastChild.nextSibling`.
|
|
@ -0,0 +1,10 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# The sibling question
|
||||
|
||||
If `elem` -- is an arbitrary DOM element node...
|
||||
|
||||
- Is it true that `elem.lastChild.nextSibling` is always `null`?
|
||||
- Is it true that `elem.children[0].previousSibling` is always `null` ?
|
|
@ -0,0 +1 @@
|
|||
We'll be using `rows` and `cells` properties to access diagonal table cells.
|
|
@ -0,0 +1,63 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td {
|
||||
border: 1px solid black;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<table>
|
||||
<tr>
|
||||
<td>1:1</td>
|
||||
<td>2:1</td>
|
||||
<td>3:1</td>
|
||||
<td>4:1</td>
|
||||
<td>5:1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1:2</td>
|
||||
<td>2:2</td>
|
||||
<td>3:2</td>
|
||||
<td>4:2</td>
|
||||
<td>5:2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1:3</td>
|
||||
<td>2:3</td>
|
||||
<td>3:3</td>
|
||||
<td>4:3</td>
|
||||
<td>5:3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1:4</td>
|
||||
<td>2:4</td>
|
||||
<td>3:4</td>
|
||||
<td>4:4</td>
|
||||
<td>5:4</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1:5</td>
|
||||
<td>2:5</td>
|
||||
<td>3:5</td>
|
||||
<td>4:5</td>
|
||||
<td>5:5</td>
|
||||
</tr>
|
||||
</table>
|
||||
<script>
|
||||
let table = document.body.firstElementChild;
|
||||
|
||||
for (let i = 0; i < table.rows.length; i++) {
|
||||
let row = table.rows[i];
|
||||
row.cells[i].style.backgroundColor = 'red';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,60 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td {
|
||||
border: 1px solid black;
|
||||
padding: 3px 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<table>
|
||||
<tr>
|
||||
<td>1:1</td>
|
||||
<td>2:1</td>
|
||||
<td>3:1</td>
|
||||
<td>4:1</td>
|
||||
<td>5:1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1:2</td>
|
||||
<td>2:2</td>
|
||||
<td>3:2</td>
|
||||
<td>4:2</td>
|
||||
<td>5:2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1:3</td>
|
||||
<td>2:3</td>
|
||||
<td>3:3</td>
|
||||
<td>4:3</td>
|
||||
<td>5:3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1:4</td>
|
||||
<td>2:4</td>
|
||||
<td>3:4</td>
|
||||
<td>4:4</td>
|
||||
<td>5:4</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1:5</td>
|
||||
<td>2:5</td>
|
||||
<td>3:5</td>
|
||||
<td>4:5</td>
|
||||
<td>5:5</td>
|
||||
</tr>
|
||||
</table>
|
||||
<script>
|
||||
let table = document.body.firstElementChild;
|
||||
|
||||
// your code
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,18 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Select all diagonal cells
|
||||
|
||||
Write the code to paint all diagonal table cells in red.
|
||||
|
||||
You'll need to get all diagonal `<td>` from the `<table>` and paint them using the code:
|
||||
|
||||
```js
|
||||
// td should be the reference to the table cell
|
||||
td.style.backgroundColor = 'red';
|
||||
```
|
||||
|
||||
The result should be:
|
||||
|
||||
[iframe src="solution" height=180]
|
284
2-ui/1-document/3-dom-navigation/article.md
Normal file
|
@ -0,0 +1,284 @@
|
|||
libs:
|
||||
- d3
|
||||
- domtree
|
||||
|
||||
---
|
||||
|
||||
|
||||
# DOM Navigation
|
||||
|
||||
DOM allows to do anything with HTML-element and its contents, but first we need to reach it.
|
||||
|
||||
All operations on DOM start with the `document` object. From it we can access any node.
|
||||
|
||||
[cut]
|
||||
|
||||
Here's a picture of links that allow to walk between DOM nodes:
|
||||
|
||||

|
||||
|
||||
Let's discuss them in more detail.
|
||||
|
||||
## On top: documentElement and body
|
||||
|
||||
The topmost tree nodes are available directly as `document` properties:
|
||||
|
||||
`<html>` = `document.documentElement`
|
||||
: The topmost document node is `document.documentElement`. That's DOM node of `<html>` tag.
|
||||
|
||||
`<body>` = `document.body`
|
||||
: Another widely used DOM node is the `<body>` element -- `document.body`.
|
||||
|
||||
`<head>` = `document.head`
|
||||
: The `<head>` tag is available as `document.head`.
|
||||
|
||||
````warn header="There's a catch: `document.body` can be `null`"
|
||||
A script cannot access an element that doesn't exist at the moment of running.
|
||||
|
||||
In particular, if a script is inside `<head>`, then `document.body` is unavailable, because the browser did not reach it yet.
|
||||
|
||||
So, in the example below `alert` will show `null`:
|
||||
|
||||
```html run
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<script>
|
||||
*!*
|
||||
alert( "From HEAD: " + document.body ); // null, there's no <body> yet
|
||||
*/!*
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script>
|
||||
alert( "From BODY: " + document.body ); // HTMLBodyElement, now it exists
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
````
|
||||
|
||||
```smart header="In the DOM world `null` is actively used"
|
||||
In the DOM, the value meaning "no such node" is `null`, not `undefined`.
|
||||
```
|
||||
|
||||
## Children: childNodes, firstChild, lastChild
|
||||
|
||||
There are two tree terms that we'll use:
|
||||
|
||||
- **Child nodes (or children)** -- elements that are nested exactly 1 level below the given one. For instance, `<head>` and `<body>` are children of `<html>` element.
|
||||
- **Descendants** -- all elements that are nested in the given one, including children, their children and so on. That is: the DOM subtree.
|
||||
|
||||
The `childNodes` collection provides access to all child nodes, including text nodes.
|
||||
|
||||
The example below shows children of `document.body`:
|
||||
|
||||
```html run
|
||||
<html>
|
||||
<body>
|
||||
<div>Begin</div>
|
||||
|
||||
<ul>
|
||||
<li>Information</li>
|
||||
</ul>
|
||||
|
||||
<div>End</div>
|
||||
|
||||
<script>
|
||||
*!*
|
||||
for (let i = 0; i < document.body.childNodes.length; i++) {
|
||||
alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT
|
||||
}
|
||||
*/!*
|
||||
</script>
|
||||
...more stuff...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Please note an interesting detail here. If we run the example above, the last element shown is `<script>`. In fact, the document has more stuff below, but at the moment of the execution the browser did not read it yet, so the script doesn't see it.
|
||||
|
||||
Properties `firstChild` and `lastChild` give fast access to the first and last elements.
|
||||
|
||||
If there are child nodes, then the following is always true:
|
||||
```js
|
||||
elem.childNodes[0] === elem.firstChild
|
||||
elem.childNodes[elem.childNodes.length - 1] === elem.lastChild
|
||||
```
|
||||
|
||||
There's also a special function `elem.hasChildNodes()` to check whether an element is empty.
|
||||
|
||||
### DOM collections
|
||||
|
||||
As we can see, `childNodes` looks like an array. But actually it's not an array, but rather a *collection* -- a special array-like iterable object.
|
||||
|
||||
There are two important consequences:
|
||||
|
||||
1. We can use `for..of` to iterate over it:
|
||||
```js
|
||||
for (let node of document.body.childNodes) {
|
||||
alert(node); // will work the same as above
|
||||
}
|
||||
```
|
||||
That's because it's iterable -- supports the `Symbol.iterator` property, as required.
|
||||
|
||||
2. Array methods won't work, because it's not an array:
|
||||
```js run
|
||||
alert(document.body.childNodes.filter); // undefined (there's no filter method!)
|
||||
```
|
||||
|
||||
The first thing is nice. The second is tolerable, because we can use `Array.from` to create a "real" array from it:
|
||||
|
||||
```js run
|
||||
alert( Array.from(document.body.childNodes).filter ); // now it's there
|
||||
```
|
||||
|
||||
```warn header="DOM collections are read-only"
|
||||
Even more -- *all* navigation properties listed in this chapter are read-only.
|
||||
|
||||
We can't replace an element by assigning `childNodes[i] = ...`.
|
||||
|
||||
Changing DOM is accomplished with other methods that we'll see in the next chapter.
|
||||
```
|
||||
|
||||
```warn header="DOM collections are live"
|
||||
Almost all DOM collections with minor exceptions are *live*. In other words, they reflect the current state of DOM.
|
||||
|
||||
If new elements appear or get removed from DOM, they are refreshed automatically.
|
||||
```
|
||||
|
||||
```warn header="Shouldn't use `for..in` to loop over collections"
|
||||
Collections are iterable using `for..of`. Sometimes people try to use `for..in` for that.
|
||||
|
||||
Please, don't. The `for..in` loop iterates over all enumerable properties. And collections some that we usually do not want to get.
|
||||
```
|
||||
|
||||
## Siblings and the parent
|
||||
|
||||
The parent is available as `parentNode`. The next node in the same parent (sibling) is `nextSibling`, and the previous one is `previousSibling`.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run
|
||||
// <html><head>...</head><body>...</body></html>
|
||||
|
||||
// parent of <body> is <html>
|
||||
alert( document.body.parentNode === document.documentElement ); // true
|
||||
|
||||
// after <head> goes <body>
|
||||
alert( document.head.nextSibling ); // HTMLBodyElement
|
||||
|
||||
// before <body> goes <head>
|
||||
alert( document.body.previousSibling ); // HTMLHeadElement
|
||||
```
|
||||
|
||||
## Element-only navigation
|
||||
|
||||
Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if there exist.
|
||||
|
||||
But for many tasks we don't want text or comment nodes. We want to manipulate on element nodes that represent tags.
|
||||
|
||||
So let's check out more navigation links that only take *element nodes* into account:
|
||||
|
||||

|
||||
|
||||
The links look like those given above, just with `Element` word inside:
|
||||
|
||||
- `children` -- only those children that are element nodes.
|
||||
- `firstElementChild`, `lastElementChild` -- first and last element children.
|
||||
- `previousElementSibling`, `nextElementSibling` -- neighbour elements.
|
||||
- `parentElement` -- parent element.
|
||||
|
||||
````smart header="Why `parentElement`? Can the parent be *not* an element?"
|
||||
The `parentElement` property returns the element parent, while `parentNode` returns "any node" parent. These properties are always the same, with one exception:
|
||||
|
||||
```js run
|
||||
alert( document.documentElement.parentNode ); // document
|
||||
alert( document.documentElement.parentElement ); // null
|
||||
```
|
||||
|
||||
In other words, the `documentElement` (`<html>`) is the root node. Formally, it has `document` as its parent. But `document` is not an element, so `parentNode` returns it and `parentElement` does not.
|
||||
|
||||
Sometimes that matters when we're walking over the chain of parents and call a method on each of them, but `document` doesn't have it, so we don't want it.
|
||||
````
|
||||
|
||||
Let's modify one of examples above: replace `childNodes` with `children`. Now it will show only elements:
|
||||
|
||||
```html run
|
||||
<html>
|
||||
<body>
|
||||
<div>Begin</div>
|
||||
|
||||
<ul>
|
||||
<li>Information</li>
|
||||
</ul>
|
||||
|
||||
<div>End</div>
|
||||
|
||||
<script>
|
||||
*!*
|
||||
for (let elem of document.body.children) {
|
||||
alert(elem); // DIV, UL, DIV, SCRIPT
|
||||
}
|
||||
*/!*
|
||||
</script>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## More links, tables [#dom-navigation-tables]
|
||||
|
||||
Till now we described the basic navigation properties. Certain DOM element types may provide more, specific to their type, for better convenience.
|
||||
|
||||
Tables are a great example and important particular case of that.
|
||||
|
||||
`<table>` elements support these properties:
|
||||
- `table.rows` -- the collection of `<tr>` elements of the table.
|
||||
- `table.caption/tHead/tFoot` -- references to elements `<caption>`, `<thead>`, `<tfoot>`.
|
||||
- `table.tBodies` -- the collection of `<tbody>` elements (can be many according to the standard).
|
||||
|
||||
`<thead>`, `<tfoot>`, `<tbody>` elements provide:
|
||||
- `tbody.rows` -- the collection of `<tr>` inside them.
|
||||
|
||||
`<tr>`:
|
||||
- `tr.cells` -- the collection of `<td>` and `<th>` cells inside the given `<tr>`.
|
||||
- `tr.sectionRowIndex` -- the number of the given `<tr>` inside the enclosing `<thead>/<tbody>`.
|
||||
- `tr.rowIndex` -- the number of the `<tr>` in the table.
|
||||
|
||||
`<td>` and `<th>`:
|
||||
- `td.cellIndex` -- the number of the cell inside the enclosing `<tr>`.
|
||||
|
||||
An example of usage:
|
||||
|
||||
```html run height=100
|
||||
<table id="table">
|
||||
<tr>
|
||||
<td>one</td><td>two</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>three</td><td>four</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
// get the content of the first row, second cell
|
||||
alert( table.*!*rows[0].cells[1]*/!*.innerHTML ) // "two"
|
||||
</script>
|
||||
```
|
||||
|
||||
The specification: [tabular data](https://html.spec.whatwg.org/multipage/tables.html).
|
||||
|
||||
There are also additional navigation properties for HTML forms. We'll look at them later when start working with forms.
|
||||
|
||||
# Summary
|
||||
|
||||
The main navigation sets are:
|
||||
|
||||
- `parentNode`, `childNodes`, `firstChild`, `lastChild`, `previousSibling`, `nextSibling` -- for all nodes.
|
||||
- `parentElement`, `children`, `firstElementChild`, `lastElementChild`, `previousElementSibling`, `nextElementSibling` -- if we take only element nodes into account.
|
||||
|
||||
Some types of DOM elements, e.g. tables, provide additional properties and collections to access their content.
|
BIN
2-ui/1-document/3-dom-navigation/dom-links-elements.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
2-ui/1-document/3-dom-navigation/dom-links-elements@2x.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
2-ui/1-document/3-dom-navigation/dom-links.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
2-ui/1-document/3-dom-navigation/dom-links@2x.png
Normal file
After Width: | Height: | Size: 66 KiB |
8
2-ui/1-document/3-dom-navigation/head.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<style>
|
||||
#travel-dom-comment {
|
||||
font-style: italic;
|
||||
}
|
||||
#travel-dom-control ul {
|
||||
margin: 6px 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,27 @@
|
|||
Есть много вариантов решения, вот некоторые из них:
|
||||
|
||||
```js
|
||||
// 1
|
||||
document.getElementById('age-table').getElementsByTagName('label');
|
||||
|
||||
// 2
|
||||
document.getElementById('age-table').getElementsByTagName('td')[0];
|
||||
// в современных браузерах можно одним запросом:
|
||||
var result = document.querySelector('#age-table td');
|
||||
|
||||
// 3
|
||||
document.getElementsByTagName('form')[1];
|
||||
|
||||
// 4
|
||||
document.querySelector('form[name="search"]');
|
||||
|
||||
// 5
|
||||
document.querySelector('form[name="search"] input')
|
||||
|
||||
// 6
|
||||
document.getElementsByName("info[0]")[0];
|
||||
|
||||
// 7
|
||||
document.querySelector('form[name="search-person"] [name="info[0]"]');
|
||||
```
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<form name="search">
|
||||
<label>Поиск по сайту:
|
||||
<input type="text" name="search">
|
||||
</label>
|
||||
<input type="submit" value="Искать!">
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<form name="search-person">
|
||||
Поиск по посетителям:
|
||||
<table id="age-table">
|
||||
<tr>
|
||||
<td>Возраст:</td>
|
||||
<td id="age-list">
|
||||
<label>
|
||||
<input type="radio" name="age" value="young">до 18</label>
|
||||
<label>
|
||||
<input type="radio" name="age" value="mature">18-50</label>
|
||||
<label>
|
||||
<input type="radio" name="age" value="senior">более 50</label>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Дополнительно:</td>
|
||||
<td>
|
||||
<input type="text" name="info[0]">
|
||||
<input type="text" name="info[1]">
|
||||
<input type="text" name="info[2]">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<input type="submit" value="Искать!">
|
||||
</form>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,20 @@
|
|||
importance: 4
|
||||
|
||||
---
|
||||
|
||||
# Поиск элементов
|
||||
|
||||
Ниже находится документ с таблицей и формой.
|
||||
|
||||
Найдите (получите в переменную) в нём:
|
||||
|
||||
1. Все элементы `label` внутри таблицы. Должно быть 3 элемента.
|
||||
2. Первую ячейку таблицы (со словом `"Возраст"`).
|
||||
3. Вторую форму в документе.
|
||||
4. Форму с именем `search`, без использования её позиции в документе.
|
||||
5. Элемент `input` в форме с именем `search`. Если их несколько, то нужен первый.
|
||||
6. Элемент с именем `info[0]`, без точного знания его позиции в документе.
|
||||
7. Элемент с именем `info[0]`, внутри формы с именем `search-person`.
|
||||
|
||||
Используйте для этого консоль браузера, открыв страницу [table.html](table.html) в отдельном окне.
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
Сделаем цикл по узлам `<li>`:
|
||||
|
||||
```js
|
||||
var lis = document.getElementsByTagName('li');
|
||||
|
||||
for (i = 0; i < lis.length; i++) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
В цикле для каждого `lis[i]` можно получить текст, используя свойство `firstChild`. Ведь первым в `<li>` является как раз текстовый узел, содержащий текст названия.
|
||||
|
||||
Также можно получить количество потомков, используя `lis[i].getElementsByTagName('li')`.
|
||||
|
||||
Напишите код с этой подсказкой.
|
||||
|
||||
Если уж не выйдет -- тогда откройте решение.
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<ul>
|
||||
<li>Животные
|
||||
<ul>
|
||||
<li>Млекопитающие
|
||||
<ul>
|
||||
<li>Коровы</li>
|
||||
<li>Ослы</li>
|
||||
<li>Собаки</li>
|
||||
<li>Тигры</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Другие
|
||||
<ul>
|
||||
<li>Змеи</li>
|
||||
<li>Птицы</li>
|
||||
<li>Ящерицы</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Рыбы
|
||||
<ul>
|
||||
<li>Аквариумные
|
||||
<ul>
|
||||
<li>Гуппи</li>
|
||||
<li>Скалярии</li>
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
<li>Морские
|
||||
<ul>
|
||||
<li>Морская форель</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
var lis = document.getElementsByTagName('li');
|
||||
|
||||
for (i = 0; i < lis.length; i++) {
|
||||
// получить название из текстового узла
|
||||
var title = lis[i].firstChild.data;
|
||||
|
||||
title = title.trim(); // убрать лишние пробелы с концов
|
||||
|
||||
// получить количество детей
|
||||
var childCount = lis[i].getElementsByTagName('li').length;
|
||||
|
||||
alert(title + ': ' + childCount);
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<ul>
|
||||
<li>Животные
|
||||
<ul>
|
||||
<li>Млекопитающие
|
||||
<ul>
|
||||
<li>Коровы</li>
|
||||
<li>Ослы</li>
|
||||
<li>Собаки</li>
|
||||
<li>Тигры</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Другие
|
||||
<ul>
|
||||
<li>Змеи</li>
|
||||
<li>Птицы</li>
|
||||
<li>Ящерицы</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Рыбы
|
||||
<ul>
|
||||
<li>Аквариумные
|
||||
<ul>
|
||||
<li>Гуппи</li>
|
||||
<li>Скалярии</li>
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
<li>Морские
|
||||
<ul>
|
||||
<li>Морская форель</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
// .. ваш код ..
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
15
2-ui/1-document/5-searching-elements-dom/2-tree-info/task.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
importance: 5
|
||||
|
||||
---
|
||||
|
||||
# Дерево
|
||||
|
||||
Есть дерево из тегов `<ul>/<li>`.
|
||||
|
||||
Напишите код, который для каждого элемента `<li>` выведет:
|
||||
|
||||
1. Текст непосредственно в нём (без подразделов).
|
||||
2. Количество вложенных в него элементов `<li>` -- всех, с учётом вложенных.
|
||||
|
||||
[demo src="solution"]
|
||||
|
323
2-ui/1-document/5-searching-elements-dom/article.md
Normal file
|
@ -0,0 +1,323 @@
|
|||
# Searching: getElement*, querySelector* and others
|
||||
|
||||
DOM navigation properties are great when elements are close to each other. What if they are not? How to get an arbitrary element of the page?
|
||||
|
||||
There are additional searching methods for that.
|
||||
[cut]
|
||||
|
||||
## document.getElementById or just id
|
||||
|
||||
If an element has the `id` attribute, then there's a global variable by the name from that `id`.
|
||||
|
||||
We can use it to access the element, like this:
|
||||
|
||||
```html run
|
||||
<div id="*!*elem*/!*">
|
||||
<div id="*!*elem-content*/!*">Element</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
alert(elem); // DOM-element with id="elem"
|
||||
alert(window.elem); // accessing global variable like this also works
|
||||
|
||||
// for elem-content the name has dash inside, so the "simple" variable access won't work
|
||||
alert(window['elem-content']); // but still possible to access with [...]
|
||||
</script>
|
||||
```
|
||||
|
||||
The behavior is described [in the specification](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem), but it is supported mainly for compatibility, because relies on global variables. The browser tries to help us by mixing namespaces of JS and DOM, but there may be conflicts.
|
||||
|
||||
The better alternative is to use a special method `document.getElementById(id)`.
|
||||
|
||||
For instance:
|
||||
|
||||
```html run
|
||||
<div id="*!*elem*/!*">
|
||||
<div id="*!*elem-content*/!*">Element</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let elem = document.getElementById('elem');
|
||||
|
||||
elem.style.background = 'red';
|
||||
</script>
|
||||
```
|
||||
|
||||
```smart header="There must be only one"
|
||||
By the specification the value of `id` must be unique. There may be only one element in the document with the given `id`.
|
||||
|
||||
If there are multiple elements with the same `id`, then the behavior is unpredictable. The browser may return any of them at random. So please stick to the rule and keep `id` unique.
|
||||
```
|
||||
|
||||
I will often use `id` to directly reference an element in examples, but that's only to keep things short. In real life `document.getElementById` is definitely the preferred method.
|
||||
|
||||
## getElementsBy*
|
||||
|
||||
There are also other methods of this kind:
|
||||
|
||||
`elem.getElementsByTagName(tag)` looks for elements with the given tag and returns the collection of them. The `tag` can also be `*` for any.
|
||||
|
||||
For instance:
|
||||
```js
|
||||
// get all divs in the document
|
||||
let divs = document.getElementsByTagName('div');
|
||||
```
|
||||
|
||||
Unlike `getElementById` that can be called only on `document`, this method can look inside any element.
|
||||
|
||||
Let's find all `input` inside the table:
|
||||
|
||||
```html run height=50
|
||||
<table id="table">
|
||||
<tr>
|
||||
<td>Your age:</td>
|
||||
|
||||
<td>
|
||||
<label>
|
||||
<input type="radio" name="age" value="young" checked> less than 18
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="age" value="mature"> from 18 to 50
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="age" value="senior"> more than 60
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
*!*
|
||||
let inputs = table.getElementsByTagName('input');
|
||||
*/!*
|
||||
|
||||
for (let input of inputs) {
|
||||
alert( input.value + ': ' + input.checked );
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
```warn header="Don't forget the `\"s\"` letter!"
|
||||
Novice developers sometimes forget the letter `"s"`. That is, they try to call `getElementByTagName` instead of <code>getElement<b>s</b>ByTagName</code>.
|
||||
|
||||
The `"s"` letter is only absent in `getElementById`, because we look for a single element. But `getElementsByTagName` returns a collection.
|
||||
```
|
||||
|
||||
````warn header="It returns a collection, not an element!"
|
||||
Another widespread novice mistake is to write like:
|
||||
|
||||
```js
|
||||
// doesn't work
|
||||
document.getElementsByTagName('input').value = 5;
|
||||
```
|
||||
|
||||
That won't work, because we take a *collection* of inputs and assign the value to it, rather to elements inside it.
|
||||
|
||||
We should either iterate over the collection or get an element by the number, and then assign, like this:
|
||||
|
||||
```js
|
||||
// should work (if there's an input)
|
||||
document.getElementsByTagName('input')[0].value = 5;
|
||||
```
|
||||
````
|
||||
|
||||
Other methods:
|
||||
|
||||
- `document.getElementsByName(name)` returns elements with the given `name` attribute. Rarely used.
|
||||
- `elem.getElementsByClassName(className)` returns elements that have the given CSS class. Elements may have other classes too.
|
||||
|
||||
For instance:
|
||||
|
||||
```html run height=50
|
||||
<form name="my-form">
|
||||
<div class="article">Article</div>
|
||||
<div class="long article">Long article</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
// find by name attribute
|
||||
let form = document.getElementsByName('my-form')[0];
|
||||
|
||||
let articles = form.getElementsByClassName('article');
|
||||
alert( articles.length ); // 2, will find both elements with class "article"
|
||||
</script>
|
||||
```
|
||||
|
||||
## querySelectorAll [#querySelectorAll]
|
||||
|
||||
Now the heavy artillery.
|
||||
|
||||
The call to `elem.querySelectorAll(css)` returns all elements inside `elem` matching the given CSS selector. That's the most often used and powerful method.
|
||||
|
||||
Here we look for all `<li>` elements that are last children:
|
||||
|
||||
```html run
|
||||
<ul>
|
||||
<li>The</li>
|
||||
<li>test</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>has</li>
|
||||
<li>passed</li>
|
||||
</ul>
|
||||
<script>
|
||||
*!*
|
||||
let elements = document.querySelectorAll('ul > li:last-child');
|
||||
*/!*
|
||||
|
||||
for (let elem of elements) {
|
||||
alert(elem.innerHTML); // "test", "passed"
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
```smart header="Can use pseudo-classes as well"
|
||||
Pseudo-classes in the CSS selector like `:hover` and `:active` are also supported. For instance, `document.querySelectorAll(':hover')` will return the collection with elements that the pointer is over now (in nesting order: from the outmost `<html>` to the most nested one).
|
||||
```
|
||||
|
||||
## querySelector [#querySelector]
|
||||
|
||||
The call to `elem.querySelector(css)` returns the first element for the given CSS selector.
|
||||
|
||||
In other words, the result is the same as `elem.querySelectorAll(css)[0]`, but that's looking for *all* elements and picking the first, much slower than only looking for the first one.
|
||||
|
||||
## matches
|
||||
|
||||
Previous methods were searching the DOM.
|
||||
|
||||
The [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` fits the CSS-selector. It returns `true` or `false`.
|
||||
|
||||
The method comes handy when we are walking over elements (like in array or something) and trying to filter those that interest us.
|
||||
|
||||
For instance:
|
||||
|
||||
```html run
|
||||
<a href="http://example.com/file.zip">...</a>
|
||||
<a href="http://ya.ru">...</a>
|
||||
|
||||
<script>
|
||||
for (let elem of document.body.children) {
|
||||
*!*
|
||||
if (elem.matches('a[href$="zip"]')) {
|
||||
*/!*
|
||||
alert("The archive reference: " + elem.href );
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## closest
|
||||
|
||||
The method `elem.closest(css)` looks the nearest ancestor (parent or his parent and so on) that matches the CSS-selector. The `elem` itself is also included in the search.
|
||||
|
||||
In other words, the method `closest` goes up from the element and checks each of them. If it matches, then stops the search and returns it.
|
||||
|
||||
For instance:
|
||||
|
||||
```html run
|
||||
<ul>
|
||||
<li class="chapter">Chapter I
|
||||
<ul>
|
||||
<li class="subchapter">Chapter <span class="num">1.1</span></li>
|
||||
<li class="subchapter">Chapter <span class="num">1.2</span></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
let numSpan = document.querySelector('.num');
|
||||
|
||||
// nearest li
|
||||
alert(numSpan.closest('li').className) // subchapter
|
||||
|
||||
// nearest chapter
|
||||
alert(numSpan.closest('.chapter').className) // chapter
|
||||
|
||||
// nearest span
|
||||
// (the numSpan is the span itself, so it's the result)
|
||||
alert(numSpan.closest('span') === numSpan) // true
|
||||
</script>
|
||||
```
|
||||
|
||||
## XPath в современных браузерах
|
||||
|
||||
Для полноты картины рассмотрим ещё один способ поиска, который обычно используется в XML. Это <a href="http://www.w3.org/TR/xpath/">язык запросов XPath</a>.
|
||||
|
||||
Он очень мощный, во многом мощнее CSS, но сложнее. Например, запрос для поиска элементов `H2`, содержащих текст `"XPath"`, будет выглядеть так: `//h2[contains(., "XPath")]`.
|
||||
|
||||
Все современные браузеры, кроме IE, поддерживают XPath с синтаксисом, близким к [описанному в MDN](https://developer.mozilla.org/en/XPath).
|
||||
|
||||
Найдем заголовки с текстом `XPath` в текущем документе:
|
||||
|
||||
```js run no-beautify
|
||||
var result = document.evaluate("//h2[contains(., 'XPath')]", document.documentElement, null,
|
||||
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
||||
|
||||
for (var i = 0; i < result.snapshotLength; i++) {
|
||||
alert( result.snapshotItem(i).outerHTML );
|
||||
}
|
||||
```
|
||||
|
||||
IE тоже поддерживает XPath, но эта поддержка не соответствует стандарту и работает только для XML-документов, например, полученных с помощью `XMLHTTPRequest` (AJAX). Для обычных же HTML-документов XPath в IE не поддерживается.
|
||||
|
||||
Так как XPath сложнее и длиннее CSS, то используют его очень редко.
|
||||
|
||||
## Итого
|
||||
|
||||
Есть 6 основных методов поиска элементов DOM:
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Метод</td>
|
||||
<td>Ищет по...</td>
|
||||
<td>Ищет внутри элемента?</td>
|
||||
<td>Поддержка</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>getElementById</code></td>
|
||||
<td><code>id</code></td>
|
||||
<td>-</td>
|
||||
<td>везде</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>getElementsByName</code></td>
|
||||
<td><code>name</code></td>
|
||||
<td>-</td>
|
||||
<td>везде</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>getElementsByTagName</code></td>
|
||||
<td>тег или <code>'*'</code></td>
|
||||
<td>✔</td>
|
||||
<td>везде</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>getElementsByClassName</code></td>
|
||||
<td>классу</td>
|
||||
<td>✔</td>
|
||||
<td>кроме IE8-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>querySelector</code></td>
|
||||
<td>CSS-селектор</td>
|
||||
<td>✔</td>
|
||||
<td>везде</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>querySelectorAll</code></td>
|
||||
<td>CSS-селектор</td>
|
||||
<td>✔</td>
|
||||
<td>везде</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Практика показывает, что в 95% ситуаций достаточно `querySelector/querySelectorAll`. Хотя более специализированные методы `getElement*` работают чуть быстрее, но разница в миллисекунду-другую редко играет роль.
|
||||
|
||||
Кроме того:
|
||||
|
||||
- Есть метод `elem.matches(css)`, который проверяет, удовлетворяет ли элемент CSS-селектору. Он поддерживается большинством браузеров в префиксной форме (`ms`, `moz`, `webkit`).
|
||||
- Метод `elem.closest(css)` ищет ближайший элемент выше по иерархии DOM, подходящий под CSS-селектор css. Сам элемент тоже включается в поиск.
|
||||
- Язык запросов XPath поддерживается большинством браузеров, кроме IE, даже 9-й версии, но `querySelector` удобнее. Поэтому XPath используется редко.
|
13
contributors.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
|
||||
The file lists people who made significant contributions to the project:
|
||||
|
||||
<ul>
|
||||
<li>Alexey Maximov @amaxcz (admin)</li>
|
||||
<li>Alexey Shisterov (tutorial)</li>
|
||||
<li>Anton Vernogor @smmurf (markup)</li>
|
||||
<li>Artem Beztsenny @bezart (design)</li>
|
||||
<li>Илья Кантор @iliakan (tutorial, code)</li>
|
||||
<li>Юрий Ткаченко @tyv (markup)</li>
|
||||
</ul>
|
||||
|
||||
The project exists for a long time, I might have missed someone. If you expect to find yourself in the list, but you're not -- please mail me at mk@javascript.ru.
|