diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index ce6fe0ba..6fd596b9 100644 --- a/2-ui/1-document/01-browser-environment/article.md +++ b/2-ui/1-document/01-browser-environment/article.md @@ -1,10 +1,10 @@ # Browser environment, specs -The Javascript language was born for web browsers. But as of now, it evolved and became a language with many use cases, not tied to browsers. +The Javascript language was initially created for web browsers. But as of now, it evolved and became a language with many uses and platforms. -The Javascript standard though assumes that the execution happens in a *host environment*, say Node.JS server, or a web-browser. +A platform may be either a browser or a web-server or a washing machine or another *host*. Each of them provides platform-specific functionality. The Javascript standard called that a *host environment*. -That host environment may provide additional objects and functions though. Web browsers provide means to control web pages. Node.JS provides server-side features. There are other host environments too. +That host environment provides additional objects and functions to the language core. Web browsers provide means to control web pages. Node.JS provides server-side features. There are other host environments too. [cut] @@ -12,83 +12,101 @@ Here's a bird-eye view of what we have when Javascript runs in a web-browser: ![](windowObjects.png) -On the top, there's `window` -- the object has two meanings: +The "root object" called `window` has two roles: -1. First, it is a [global object](info:global-object) in terms of Javascript. -2. Second, it represents the "browser window" object, supports methods to control it and read its parameters. +1. First, it is a [global object](info:global-object) for JavaScript code. +2. Second, it represents the "browser window" object, provides methods to control it. -For instance, to see the window height: +For instance, here we use it as a global object: + +```js run +function sayHi() { + alert("Hello"); +} + +alert(window.f); // global function is a property of window +``` + +And here we use it as a browser window, to see the window height: ```js run alert(window.innerHeight); // some number ``` -Now we go from left to right. - ## Document Object Model (DOM) -The `document` object gives access to a webpage contents. We can change or create literally anything. +The `document` object gives access to a page contents. We can change or create literally anything. For instance: ```js run +// change the background color to red document.body.style.background = 'red'; -alert('The element became red! In 1 second it will become normal.'); + +// change it back after 1 second setTimeout(() => document.body.style.background = '', 1000); ``` -There are two working groups who develop the standard: +Here we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification. + +There are two working groups who develop it: 1. [W3C](https://en.wikipedia.org/wiki/World_Wide_Web_Consortium) -- the documentation is at . 2. [WhatWG](https://en.wikipedia.org/wiki/WHATWG), publishing at . -About 99.9% of time these two groups agree, so the actual documentation is almost the same. There are very minor differences that you shouldn't even notice that in practice. +As it happens, the two groups don't always agree, so we have like 2 sets of standards. But they are in tight contact and eventually things merge. So the documentation that you can find on the given resources is very similar, like 99%. There are very minor differences, but you probably won't notice them. -I find more pleasant to use. +I find more pleasant to use, and so recommend it. -Historically, once there was no standard at all -- each browser did whatever it wanted. So different browsers had different methods and properties, and we had to find different code for each of them. Yeah, terrible, please don't remind. +In the ancient past, once there was no standard at all -- each browser did whatever it wanted. So different browsers had different sets methods and properties for the same thing, and developers had to write different code for each of them. Dark times indeed. -Then the DOM standard appeared, in an attempt to bring them to an agreement. The first version is now called DOM Level 1. Then it was extended by DOM Level 2, new methods got added, then DOM Level 3, and now DOM Level 4. +Even now we can sometimes meet old code that uses browser-specific properties and works around incompatibilities. But in this tutorial we'll use modern stuff: there's no need to learn old things until you really need those (chances are high you won't). -People from WhatWG got tired of version and are calling that just "DOM", without a number. So will do we. +Then the DOM standard appeared, in an attempt to bring everyone to an agreement. The first version was DOM Level 1, then it was extended by DOM Level 2, then DOM Level 3, and now it's DOM Level 4. + +People from WhatWG group got tired of version and are calling that just "DOM", without a number. So will do we. ```smart header="DOM is not only for browsers" -The DOM specification explains the tree structure of a document and methods to manipulate it. There are non-browser instruments that use it as well. For instance, server-side tools that download HTML pages and process them. They may support only a part of the specification though. +The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use it too. + +For instance, server-side tools that download HTML pages and process them. They may support only a part of the DOM specification though. ``` ```smart header="CSSOM for styling" -CSS styles and stylesheets are organized not like HTML. They form a standalone domain. +CSS styles and stylesheets are structured not like HTML. So there's a separate specification [CSSOM](https://www.w3.org/TR/cssom-1/) that explains how CSS styles and rules can be represented as objects, how to read and write them. -So there's a separate specification [CSSOM](https://www.w3.org/TR/cssom-1/) that explains how CSS styles and rules can be read and modified as objects, how to read and write them in the document. - -If you're interested to read about general document structure, creating and modifying elements -- then it's DOM. But if it's about styles, then it's CSSOM. +Usually we take a style somewhere from the document and apply it to the document, so CSSOM is used together with DOM. But CSSOM is applied not often, because we rarely need to modify CSS rules from JavaScript, so we won't cover it right now. ``` ## BOM (part of HTML spec) -Browser Object Model (BOM) are objects to work with anything except the document. +Browser Object Model (BOM) are additional objects provided by the browser (host environment) to work with everything except the document. For instance: -- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operation system. There are many properties, but two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differ between Windows/Linux/Mac etc). -- The [location](mdn:api/Window/location) object allows to read the current URL and redirect the browser to a new one. -- Functions `alert/confirm/prompt` -- are also a part of BOM, they are not related to "document". -- ...and so on. +- [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operation system. There are many properties, but two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differ between Windows/Linux/Mac etc). +- [location](mdn:api/Window/location) object allows to read the current URL and redirect the browser to a new one. -Here's how we can use the `location`: +Here's how we can use the `location` object: ```js run alert(location.href); // shows current URL +if (confirm("Go to wikipedia?")) { + location.href = 'https://wikipedia.org'; // redirect the browser to another URL +} ``` +Functions `alert/confirm/prompt` -- are also a part of BOM: they are directly not related to the document, but represent pure browser methods of communicating with the user. + + ```smart header="HTML specification" BOM is the part of the general [HTML specification](https://html.spec.whatwg.org). -Yes, right. The HTML spec at covers not only the "HTML language" (tags, attributes), but also a bunch of objects, methods and browser-specific DOM extensions, including those listed above. +Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods and browser-specific DOM extensions. That's "HTML in broad terms". ``` ## Summary -So, talking about general standards, we have: +Talking about standards, we have: DOM specification : Describes the document structure, manipulations and events, see . @@ -101,4 +119,4 @@ HTML specification 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 , but looking up the corresponding spec may be 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 -- the Mozilla manual at is a nice one, but reading the corresponding spec may be better. More complex and longer to read, but will make your fundamental knowledge sound and complete. diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index 8230231f..6c52dc69 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -6,11 +6,13 @@ libs: # DOM tree -When we look at HTML we see nested tags, right? According to Document Object Model (DOM), every HTML-tag is an object. Nested tags are his "children". And the text inside it is an object as well. All these objects are accessible using Javascript. +The essential part of HTML is tags, right? + +According to Document Object Model (DOM), every HTML-tag is an object. Nested tags are called "children". And the text inside it is an object as well. All these objects are accessible using Javascript, we'll see that now. ## An example of DOM -For instance, let's see the DOM tree for this document: +For instance, let's explore the DOM for this document: ```html run no-beautify @@ -24,37 +26,40 @@ For instance, let's see the DOM tree for this document: ``` -Here's how it looks: +The DOM represents HTML as a tree structure of tags. Here's how it looks:
-There are two types of tree nodes in the example: - -1. Tags are called *element nodes* (or just elements). Naturally, nested tags become children of the enclosing ones. Because of that we have a tree. -2. The text inside elements forms *text nodes*, labelled as `#text`. A text node contains only a string. It may not have children and is always a leaf of the tree. - ```online -**On the picture above element nodes you can click on element nodes. Their children will open/collapse.** +On the picture above element nodes you can click on element nodes. Their children will open/collapse. ``` +Tags are called *element nodes* (or just elements). Nested tags become children of the enclosing ones. As a result we have a tree of elements: `` is at the root, then `` and `` are its children etc. + +The text inside elements forms *text nodes*, labelled as `#text`. A text node contains only a string. It may not have children and is always a leaf of the tree. + +For instance, the `` tag has the text `"About elks"`. + Please note the special characters in text nodes: - a newline: `↵` (in Javascript known as `\n`) - a space: `␣` -**Spaces and newlines -- are all valid characters, they form text nodes and become a part of the DOM.** +Spaces and newlines -- are totally valid characters, they form text nodes and become a part of the DOM. So, for instance, in the example above the `<head>` tag contains come spaces before `<title>`, and that text becomes a `#text` node (it contains a newline and some spaces only). -For instance, in the example above `<html>` contains not only elements `<head>` and `<body>`, but also the `#text` (spaces, line breaks) between them. +There are only two top-level exclusions: +1. Spaces and newlines before `<head>` are ignored for historical reasons, +2. If we put something after `</body>`, then that is automatically moved inside the `body`, at the end, as HTML spec requires that all content must be inside `<body>`. So there may be no spaces after `</body>`. -However, on the topmost level there are exclusions of that rule: spaces and newlines before `<head>` are ignored for historical reasons, and if we put something after `</body>`, then it is considered a malformed HTML, and that text is moved inside the `body`, at the end (there may be nothing after the `body`). +In other cases everything's honest -- if there are spaces (just like any character) in the document, then they text nodes in DOM, and if we remove them, then there won't be any. -In other cases everything's honest -- if there are spaces (just like any character) in the document, then they text nodes in DOM, and if we remove them, then there won't be any in DOM, like here: +Here are no space-only text nodes: ```html no-beautify <!DOCTYPE HTML> @@ -69,10 +74,12 @@ var node = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1," drawHtmlTree(node, 'div.domtree', 690, 210); </script> -```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. +```smart header="Edge spaces and in-between empty text are usually hidden in tools" +Browser tools (to be covered soon) that work with DOM usually do not show spaces at start/end of the text and empty text nodes (line-breaks) between tags. . -On our DOM pictures we'll omit them too where they are not important, to keep things short. +That's because they are mainly used to decorate HTML, and do not affect (in most cases) how it is shown. + +On further DOM pictures we'll sometimes omit them where they are irrelevant, to keep things short. ``` @@ -82,9 +89,18 @@ If the browser encounters malformed HTML, it automatically corrects it when maki For instance, the top tag is always `<html>`. Even if it doesn't exist in the document -- it will be in DOM, the browser will create it. The same about `<body>`. -Like, if the HTML file is a single word `"Hello"`, the browser will wrap it into `<html>` and `<body>`. +As an example, if the HTML file is a single word `"Hello"`, the browser will wrap it into `<html>` and `<body>`, add the required `<head>`, and the DOM will be: -**While generating DOM, browser automatically processes errors in the document, closes tags and so on.** + +<div class="domtree"></div> + +<script> +var node = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1,"children":[]},{"name":"BODY","nodeType":1,"children":[{"name":"#text","nodeType":3,"content":"Hello"}]}]} + +drawHtmlTree(node, 'div.domtree', 690, 150); +</script> + +While generating DOM, browser automatically processes errors in the document, closes tags and so on. Such an "invalid" document: @@ -106,7 +122,7 @@ drawHtmlTree(node, 'div.domtree', 690, 360); </script> ````warn header="Tables always have `<tbody>`" -An interesting "special case" is tables. By the DOM specification they must have `<tbody>`, but HTML text may omit it. Then the browser creates `<tbody>` on it's own. +An interesting "special case" is tables. By the DOM specification they must have `<tbody>`, but HTML text may (officially) omit it. Then the browser creates `<tbody>` in DOM automatically. For the HTML: @@ -154,22 +170,22 @@ var node = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1," drawHtmlTree(node, 'div.domtree', 690, 500); </script> -Here we see a new tree node type -- *comment node*. +Here we see a new tree node type -- *comment node*, labeled as `#comment`. -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. +We may think -- why a comment is added to the DOM? It doesn't affect the visual representation in any way. But there's a rule -- if something's in HTML, then it also must be in the DOM tree. **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>`. 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, we even don't draw it on diagrams for that reason, but it's there. The `document` object that represents the whole document is, formally, a DOM node as well. -There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we mainly work with 4 of them: +There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we usually 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. +3. text nodes -- contain text. +4. comments -- sometimes we can put the information there, it won't be shown, but JS can read it from DOM. ## See it yourself @@ -185,49 +201,57 @@ Should look like this: ![](elks.png) -So you can see the DOM, click on elements, see the details about them and so on. +You can see the DOM, click on elements, see their details and so on. -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. +Please note that the DOM structure 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. +Clicking the <span class="devtools" style="background-position:-328px -124px"></span> button in the left-upper corner allows to choose a node from the webpage using a mouse (or other pointer device) and "inspect" it (scroll to it in the elements tab). 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. +Another way to do it would be just right-clicking on a webpage and selecting "Inspect" in the context menu. ![](inspect.png) -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. +At the right part of the tools there are following subtabs: +- Styles -- we can 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. +- Event Listeners -- to see event listeners attached to DOM elements (we'll cover them in the next part of the tutorial). +- ...and so on. -The best way to study them is to click around. Again, please note that most values are in-place editable. +The best way to study them is to click around. 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. +As we explore the DOM, we also may want to apply Javascript to it. Like: get a node and run some code to modify it, to see how it looks. Here are few tips to travel between the Elements tab and the console. -Press `key:Esc` -- it will open console right below the Elements tab. +- Select the first `<li>` in the Elements tab. +- 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. +Now the last selected element is available as `$0`, the previously selected is `$1` etc. -We can run commands on them, like `$0.style.background = 'red'` here: +We can run commands on them. For instance, `$0.style.background = 'red'` makes the selected list item red, like this: ![](domconsole0.png) -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 other side, if we're in console and have a variable referencing a DOM node, then we can use the command `inspect(node)` to see it in the Elements pane. + +Or we can just output it in the console and explore "at-place", like `document.body` below: ![](domconsole1.png) -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. +That's for debugging purposes of course. From the next chapter on we'll access and modify DOM using Javascript. + +The browser developer tools are a great help in development: we can explore DOM, try things and see what goes wrong. ## Summary -An HTML/XML document are represented inside the browser as the DOM tree. +An HTML/XML document is represented inside the browser as the DOM tree. - Tags become element nodes and form the structure. - Text becomes text nodes. - ...etc, everything in HTML has its place in DOM, even comments. -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. +We can use developer tools to inspect DOM and modify it manually. -DOM nodes have properties and methods that allow to modify them. We'll get down to it in further chapters. +Here we covered the basics, most used and important actions to start with. There's an extensive documentation about Chrome developer tools at <https://developers.google.com/web/tools/chrome-devtools>. The best way to learn the tools is to click here and there, read menus: most options are obvious. Later, when you know them in general, read the docs and pick up the rest. + +DOM nodes have properties and methods that allow to travel between them, modify, move around the page and more. We'll get down to them in the next chapters. diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md index e7c6542d..d33e3197 100644 --- a/2-ui/1-document/03-dom-navigation/article.md +++ b/2-ui/1-document/03-dom-navigation/article.md @@ -7,13 +7,13 @@ libs: # DOM Navigation -DOM allows to do anything with HTML-element and its contents, but first we need to reach it. +DOM allows to do anything with elements and their contents, but first we need to reach the corresponding DOM object, get it into a variable, and then we are able to modify 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: +Here's a picture of links that allow to travel between DOM nodes: ![](dom-links.png) @@ -35,9 +35,9 @@ The topmost tree nodes are available directly as `document` properties: ````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. +In particular, if a script is inside `<head>`, then `document.body` is unavailable, because the browser did not read it yet. -So, in the example below `alert` will show `null`: +So, in the example below the first `alert` shows `null`: ```html run <html> @@ -61,18 +61,36 @@ So, in the example below `alert` will show `null`: ``` ```` -```smart header="In the DOM world `null` is actively used" -In the DOM, the value meaning "no such node" is `null`, not `undefined`. +```smart header="In the DOM world `null` means \"doesn't exist\"" +In the DOM, the `null` value means "doesn't exist" or "no such node". ``` ## Children: childNodes, firstChild, lastChild -There are two tree terms that we'll use: +There are two terms that we'll use from now on: -- **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. +- **Child nodes (or children)** -- elements that are direct children. In other words, they are nested exactly in 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. -The `childNodes` collection provides access to all child nodes, including text nodes. +For instance, here `<body>` has children `<div>` and `<ul>` (and few blank text nodes): + +```html run +<html> +<body> + <div>Begin</div> + + <ul> + <li> + <b>Information</b> + </li> + </ul> +</body> +</html> +``` + +...And if we ask for all descendants of `<body>`, then we get direct children `<div>`, `<ul>` and also more nested elements like `<li>` (being a child of `<ul>`) and `<b>` (being a child of `<li>`) -- the whole subtree. + +**The `childNodes` collection provides access to all child nodes, including text nodes.** The example below shows children of `document.body`: @@ -99,17 +117,17 @@ The example below shows children of `document.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. +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 script 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. +**Properties `firstChild` and `lastChild` give fast access to the first and last children.** -If there are child nodes, then the following is always true: +They are just shorthands. If there exist 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. +There's also a special function `elem.hasChildNodes()` to check whether there are any child nodes. ### DOM collections @@ -120,66 +138,83 @@ 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 + alert(node); // shows all nodes from the collection } ``` - That's because it's iterable -- supports the `Symbol.iterator` property, as required. + That's because it's iterable (provides 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: +The first thing is nice. The second is tolerable, because we can use `Array.from` to create a "real" array from the collection, if we want array methods: ```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. +DOM collections, and even more -- *all* navigation properties listed in this chapter are read-only. -We can't replace an element by assigning `childNodes[i] = ...`. +We can't replace an child by something else assigning `childNodes[i] = ...`. -Changing DOM is accomplished with other methods that we'll see in the next chapter. +Changing DOM needs other methods, we'll see them 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. +If we keep a reference to `elem.childNodes`, and add/remove nodes into DOM, then they appear in the collection automatically. ``` -```warn header="Shouldn't use `for..in` to loop over collections" +````warn header="Don'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. -``` +Please, don't. The `for..in` loop iterates over all enumerable properties. And collections have some "extra" rarely used properties that we usually do not want to get: + +```html run +<body> +<script> + // shows 0, 1, length, item, values and more. + for(let prop in document.body.childNodes) alert(prop); +</script> +</body> +```` ## 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`. +*Siblings* are nodes that are children of the same parent. For instance, `<head>` and `<body>` are siblings: + +- `<body>` is said to be the "next" or "right" sibling of `<head>`, +- `<head>` is said to be the "previous" or "left" sibling of `<body>`. + +The parent is available as `parentNode`. + +The next node in the same parent (next sibling) is `nextSibling`, and the previous one is `previousSibling`. For instance: -```js run -// <html><head>...</head><body>...</body></html> +```html run +<html><head></head><body><script> + // HTML is "dense" to evade extra "blank" text nodes. -// parent of <body> is <html> -alert( document.body.parentNode === document.documentElement ); // true + // parent of <body> is <html> + alert( document.body.parentNode === document.documentElement ); // true -// after <head> goes <body> -alert( document.head.nextSibling ); // HTMLBodyElement + // after <head> goes <body> + alert( document.head.nextSibling ); // HTMLBodyElement -// before <body> goes <head> -alert( document.body.previousSibling ); // HTMLHeadElement + // before <body> goes <head> + alert( document.body.previousSibling ); // HTMLHeadElement +</script></body></html> ``` ## 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 element nodes that represent tags and structure of the page. +But for many tasks we don't want text or comment nodes. We want to manipulate element nodes that represent tags and form the structure of the page. So let's see more navigation links that only take *element nodes* into account: @@ -193,19 +228,21 @@ The links are similar to those given above, just with `Element` word inside: - `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: +The `parentElement` property returns the "element" parent, while `parentNode` returns "any node" parent. These properties are usually the same: they both get the parent. + +With the one exception of `document.documentElement`: ```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. +In other words, the `documentElement` (`<html>`) is the root node. Formally, it has `document` as its parent. But `document` is not an element node, 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. +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 exclude it. ```` -Let's modify one of examples above: replace `childNodes` with `children`. Now it will show only elements: +Let's modify one of examples above: replace `childNodes` with `children`. Now it shows only elements: ```html run <html> @@ -232,17 +269,19 @@ Let's modify one of examples above: replace `childNodes` with `children`. Now it ## More links: tables [#dom-navigation-tables] -Till now we described the basic navigation properties. Certain types of DOM elements may provide more, specific to their type, for better convenience. +Till now we described the basic navigation properties. + +Certain types of DOM elements may provide additional properties, specific to their type, for convenience. Tables are a great example and important particular case of that. -**`<table>`** elements support these properties: +**`<table>`** element supports (in addition to the given above) 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. +**`<thead>`, `<tfoot>`, `<tbody>`** elements provide the `rows` property: +- `tbody.rows` -- the collection of `<tr>` inside. **`<tr>`:** - `tr.cells` -- the collection of `<td>` and `<th>` cells inside the given `<tr>`. diff --git a/2-ui/1-document/04-searching-elements-dom/article.md b/2-ui/1-document/04-searching-elements-dom/article.md index acd77093..9b3dcaf4 100644 --- a/2-ui/1-document/04-searching-elements-dom/article.md +++ b/2-ui/1-document/04-searching-elements-dom/article.md @@ -1,4 +1,4 @@ -# Searching with getElement*, querySelector* and others +# Searching with getElement*, querySelector* and other methods 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? @@ -20,42 +20,61 @@ We can use it to access the element, like this: 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 [...] + // for elem-content things are a bit more complex + // that has a dash inside, so it can't be a variable name + alert(window['elem-content']); // ...but accessable using square brackets [...] </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. +That's unless we declare the same-named variable by our own: + +```html run untrusted height=0 +<div id="elem"></div> + +<script> + let elem = 5; + + alert(elem); // the variable overrides the element +</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. The browser tries to help us by mixing namespaces of JS and DOM. Good for very simple scripts, but there may be name conflicts. Also, when we look in JS and don't have HTML in view, it's not obvious where the variable comes from. 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 id="elem"> + <div id="elem-content">Element</div> </div> <script> +*!* let elem = document.getElementById('elem'); +*/!* elem.style.background = 'red'; </script> ``` -```smart header="There can be only one" -By the specification the value of `id` must be unique. There can be only one element in the document with the given `id`. +Here in the tutorial we'll often use `id` to directly reference an element, but that's only to keep things short. In real life `document.getElementById` is the preferred method. -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. +```smart header="There can be only one" +The `id` must be unique. There can be only one element in the document with the given `id`. + +If there are multiple elements with the same `id`, then the behavior of corresponding methods 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. +```warn header="Only `document.getElementById`, not `anyNode.getElementById`" +The method `getElementById` that can be called only on `document` object. It looks for the given `id` in the whole document. +``` ## getElementsBy* -There are also other methods of this kind: +There are also other methods to look for nodes: -`elem.getElementsByTagName(tag)` looks for elements with the given tag and returns the collection of them. The `tag` can also be `*` for any. +- `elem.getElementsByTagName(tag)` looks for elements with the given tag and returns the collection of them. The `tag` parameter can also be a star `"*"` for "any tags". For instance: ```js @@ -63,7 +82,7 @@ For instance: let divs = document.getElementsByTagName('div'); ``` -Unlike `getElementById` that can be called only on `document`, this method can look inside any element. +This method is callable in the context of any DOM element. Let's find all `input` inside the table: @@ -100,7 +119,7 @@ Let's find all `input` inside the table: ```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. +The `"s"` letter is absent in `getElementById`, because it returns a single element. But `getElementsByTagName` returns a collection of elements, so there's `"s"` inside. ``` ````warn header="It returns a collection, not an element!" @@ -123,8 +142,8 @@ document.getElementsByTagName('input')[0].value = 5; There are also other rarely used methods of this kind: -- `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. +- `document.getElementsByName(name)` returns elements with the given `name` attribute, document-wide. Exists for historical reasons, very rarely used, we mention it here only for completeness. For instance: @@ -138,14 +157,15 @@ For instance: // find by name attribute let form = document.getElementsByName('my-form')[0]; + // find by class inside the form let articles = form.getElementsByClassName('article'); - alert( articles.length ); // 2, will find both elements with class "article" + alert(articles.length); // 2, found two elements with class "article" </script> ``` ## querySelectorAll [#querySelectorAll] -Now the heavy artillery. +Now goes 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. @@ -171,23 +191,26 @@ Here we look for all `<li>` elements that are last children: </script> ``` +This method is indeed powerful, because any CSS selector can be used. + ```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 the latter is looking for *all* elements and picking one, while `elem.querySelector` just looks for one. So it's faster. +In other words, the result is the same as `elem.querySelectorAll(css)[0]`, but the latter is looking for *all* elements and picking one, while `elem.querySelector` just looks for one. So it's faster and shorter to write. ## 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 [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given 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. +The method comes handy when we are iterating over elements (like in array or something) and trying to filter those that interest us. For instance: @@ -196,6 +219,7 @@ For instance: <a href="http://ya.ru">...</a> <script> + // can be any collection instead of document.body.children for (let elem of document.body.children) { *!* if (elem.matches('a[href$="zip"]')) { @@ -208,37 +232,44 @@ For instance: ## 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. +All elements that are directly above the given one are called its *ancestors*. -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. +In other words, ancestors are: parent, the parent of parent, its parent and so on. The ancestors together form the chain of parents from the element to the top. + +The method `elem.closest(css)` looks the nearest ancestor 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 parents. If it matches the selector, then the search stops, and the ancestor is returned. For instance: ```html run <h1>Contents</h1> -<ul class="book"> - <li class="chapter">Chapter 1</li> - <li class="chapter">Chapter 1</li> -</ul> +<div class="contents"> + <ul class="book"> + <li class="chapter">Chapter 1</li> + <li class="chapter">Chapter 1</li> + </ul> +</div> <script> let chapter = document.querySelector('.chapter'); // LI - alert(chapter.closest('.book')); // UL above our this LI + alert(chapter.closest('.book')); // UL + alert(chapter.closest('.contents')); // DIV - alert(chapter.closest('h1')); // null - // because h1 is not an ancestor + alert(chapter.closest('h1')); // null (because h1 is not an ancestor) </script> ``` ## Live collections -All methods `"getElementsBy*"` return a *live* collection. Such collections always reflect the current state of the document. +All methods `"getElementsBy*"` return a *live* collection. Such collections always reflect the current state of the document and "auto-update" when it changes. -For instance, here in the first script the length is `1`, because the browser only processed the first div. +In the example below, there are two scripts. -Then later after the second `div` it's 2: +1. The first one creates a reference to the collection of `<div>`. As of now, it's length is `1`. +2. The second scripts runs after the browser meets one more `<div>`, so it's length is `2`. ```html run <div>First div</div> @@ -281,7 +312,7 @@ If we use it instead, then both scripts output `1`: Now we can easily see the difference. The static collection did not increase after the appearance of a new `div` in the document. -Here we used separate scripts to illustrate how the element addition affects the collection. Soon we'll see more ways to alter DOM. +Here we used separate scripts to illustrate how the element addition affects the collection, but any DOM manipulations affect them. Soon we'll see more of them. ## Summary @@ -346,4 +377,4 @@ Besides that: - There is `elem.closest(css)` to look for a nearest ancestor that matches the given CSS-selector. The `elem` itself is also checked. And let's mention one more method here to check for the child-parent relationship: -- `elemA.contains(elemB)` returns true if `elemB` is inside `elemA`. Or when it's the same element. +- `elemA.contains(elemB)` returns true if `elemB` is inside `elemA` (a descendant of `elemA`) or when `elemA==elemB`. diff --git a/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/task.md b/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/task.md index 1512cced..d83602dc 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/task.md +++ b/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/task.md @@ -8,4 +8,4 @@ Which class the `document` belongs to? What's its place in the DOM hierarchy? -Does it inherit from `Node` or `Element`? +Does it inherit from `Node` or `Element`, or maybe `HTMLElement`? diff --git a/2-ui/1-document/05-basic-dom-node-properties/article.md b/2-ui/1-document/05-basic-dom-node-properties/article.md index f88aee46..0e60c59f 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/article.md +++ b/2-ui/1-document/05-basic-dom-node-properties/article.md @@ -12,34 +12,44 @@ DOM nodes have different properties depending on their class. For instance, elem Text nodes are not the same as element nodes: they have properties of their own. -Still, there's something in common between all of them, because of the inheritance. DOM objects belong to different classes which form a hierarchy. +But there are also common properties and methods between all of them, because of the inheritance. Each DOM node belongs to the corresponding built-in class. These classes form a hierarchy. -The root object is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), then goes [Node](http://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it: +The root is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](http://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it. + +Here's the picture, explanations to follow: ![](dom-class-hierarchy.png) The classes are: -- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) is the root "abstract" class. Objects of that class are never created. It serves as a base, because all DOM nodes support so-called "events", we'll study them later. -- [Node](http://dom.spec.whatwg.org/#interface-node) is also an "abstract" class, serving as a base for DOM nodes. It provides the core tree functionality like `parentNode`, `nextSibling`, `childNodes` and so on. Objects of bare `Node` are never created. But there are concrete node classes that inherit from it, namely: `Text` for text nodes, `Element` for element nodes and few more exotic ones like `Comment` for comment nodes. -- [Element](http://dom.spec.whatwg.org/#interface-element) is a base class for DOM elements. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. In the browser there may be not only HTML, but also XML and SVG documents. The `Element` class serves as a base for `SVGElement`, `XMLElement` and `HTMLElement`. -- [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) is finally the class for HTML elements. It is inherited by various HTML elements: - - For `<input>` -- `HTMLInputElement` - - For `<body>` -- `HTMLBodyElement` - - For `<a>` -- `HTMLAnchorElement`... and so on, that have their own specific properties. - +- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) is the root "abstract" class. Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later. +- [Node](http://dom.spec.whatwg.org/#interface-node) is also an "abstract" class, serving as a base for DOM nodes. It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are concrete node classes that inherit from it, namely: `Text` for text nodes, `Element` for element nodes and more exotic ones like `Comment` for comment nodes. +- [Element](http://dom.spec.whatwg.org/#interface-element) is a base class for DOM elements. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. In the browser there may be not only HTML, but also XML and SVG documents. The `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` and `HTMLElement`. +- [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) is finally the basic class for all HTML elements. It is inherited by various HTML elements: + - [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- the class for `<input>` elements, + - [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- the class for `<body>` elements, + - [HTMLAnchorElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlanchorelement) -- the class for `<a>` elements + - ...and so on, each tag has its own class that may provide specific properties and methods. So, the full set of properties and methods of a given node comes as the result of the inheritance. For instance, `<input>` element is of the [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) class: -- `HTMLInputElement` provides input-specific properties and inherits the common HTML element properties from `HTMLElement`. -- Then it has common element properties, because `HTMLElement` inherits from `Element`. -- Then it has common DOM node properties from `Node` and so on. +- `HTMLInputElement` itself provides input-specific properties. +- It inherits the common HTML element methods (and getters/setters) from `HTMLElement` class. +- Then it supports common element methods, because `HTMLElement` inherits from `Element`. +- Then it supports common DOM node properties, because it inherits from `Node`. +- Finally, it supports events (to be covered), because it inherits from `EventTarget`. +- (and for completeness: `EventTarget` inherits from `Object`) -To see the DOM node class, we can check its constructor name or just `toString` it: +To see the DOM node class name, we can remember that an object usually has the `constructor` property. It references to the class constructor, so the `constructor.name` is what we need: ```js run alert( document.body.constructor.name ); // HTMLBodyElement +``` + +...Or we can just `toString` it: + +```js run alert( document.body ); // [object HTMLBodyElement] ``` @@ -53,15 +63,15 @@ alert( document.body instanceof Node ); // true alert( document.body instanceof EventTarget ); // true ``` -As we can see, DOM nodes are regular Javascript objects. They use prototype-style classes for inheritance. That's easy to see by outputting an element with `console.dir(elem)`. There you can see `HTMLElement.prototype`, `Element.prototype` and so on. +As we can see, DOM nodes are regular Javascript objects. They use prototype-based classes for inheritance. That's easy to see by outputting an element with `console.dir(elem)`. There you can see `HTMLElement.prototype`, `Element.prototype` and so on. ```smart header="`console.dir(elem)` versus `console.log(elem)`" -Most browsers support two commands: `console.log` and `console.dir`. For Javascript objects these commands usually do the same. +Most browsers support two commands in their developer tools: `console.log` and `console.dir`. They output their arguments to the console. For Javascript objects these commands usually do the same. -But for DOM elements: +But for DOM elements they are different: - `console.log(elem)` shows the element DOM tree. -- `console.dir(elem)` shows the element as a DOM object, good for analyzing its properties. +- `console.dir(elem)` shows the element as a DOM object, good to explore its properties. Try it on `document.body`. ``` @@ -69,34 +79,44 @@ Try it on `document.body`. ````smart header="IDL in the spec" In the specification classes are described using not Javascript, but a special [Interface description language](https://en.wikipedia.org/wiki/Interface_description_language) (IDL), that is usually easy to understand. +The most important difference is that all properties are given with their types. For instance, `DOMString`, `boolean` and so on. + Here's an excerpt from it, with comments: ```js // Define HTMLInputElement +*!* // The colon means that it inherits from HTMLElement +*/!* interface HTMLInputElement: HTMLElement { - // string properties - // accept, alt, autocomplete, value +*!* + // "DOMString" means that the property is a string +*/!* attribute DOMString accept; attribute DOMString alt; attribute DOMString autocomplete; attribute DOMString value; - // and the boolean property: autofocus +*!* + // boolean property (true/false) attribute boolean autofocus; +*/!* ... - // and also the method select(), - // "void" means that that returns no value +*!* + // now the method: "void" means that that returns no value +*/!* void select(); ... } ``` + +Other classes are somewhat similar. ```` ## The "nodeType" property -The `nodeType` property provides one more, old-fashioned way to get the "type" of a DOM object. +The `nodeType` property provides an old-fashioned way to get the "type" of a DOM node. It has a numeric value: - `elem.nodeType == 1` for element nodes, @@ -123,10 +143,7 @@ For instance: </body> ``` -In the modern scripts, we can use `instanceof` and other class-based tests to see the node type, but `nodeType` may be simpler. - - -We can only read `nodeType`, not change it. +In modern scripts, we can use `instanceof` and other class-based tests to see the node type, but sometimes `nodeType` may be simpler. We can only read `nodeType`, not change it. ## Tag: nodeName and tagName @@ -139,15 +156,9 @@ alert( document.body.nodeName ); // BODY alert( document.body.tagName ); // BODY ``` -```smart header="The tag name is always uppercase except XHTML" -The browser has two modes of processing documents: HTML and XML. Usually the HTML-mode is used for webpages. And `nodeName/tagName` are always uppercase for it, both for `<body>` and `<BoDy>`. +Is there any difference between tagName and nodeName? -XML-mode is enabled when the browser receives an XML-document with the header: `Content-Type: application/xml+xhtml`. Then the case is kept "as is". But in practice XML-mode is rarely used. -``` - -### Any difference between tagName and nodeName? - -The difference is reflected in their names, but is indeed a bit subtle. +Actually, yes, the difference is reflected in their names, but is indeed a bit subtle. - The `tagName` property exists only for `Element` nodes. - The `nodeName` is defined for any `Node`: @@ -176,11 +187,21 @@ For instance let's compare `tagName` and `nodeName` for the `document` and a com If we only deal with elements, then `tagName` is the only thing we should use. + +```smart header="The tag name is always uppercase except XHTML" +The browser has two modes of processing documents: HTML and XML. Usually the HTML-mode is used for webpages. XML-mode is enabled when the browser receives an XML-document with the header: `Content-Type: application/xml+xhtml`. + +In HTML mode `tagName/nodeName` is always uppercased: it's `BODY` either for `<body>` or `<BoDy>`. + +In XML mode the case is kept "as is", but it's rarely used. +``` + + ## innerHTML: the contents The [innerHTML](https://w3c.github.io/DOM-Parsing/#widl-Element-innerHTML) property allows to get the HTML inside the element as a string. -We can also modify it. So its truly one of most powerful ways to change the page. +We can also modify it. So it's one of most powerful ways to change the page. The example shows the contents of `document.body` and then replaces it completely: @@ -213,12 +234,12 @@ We can try to insert an invalid HTML, the browser will fix our errors: ```smart header="Scripts don't execute" If `innerHTML` inserts a `<script>` tag into the document -- it doesn't execute. -It will become a part of HTML, just as a script that has already run. +It becomes a part of HTML, just as a script that has already run. ``` ### Beware: "innerHTML+=" does a full overwrite -We can add "more HTML" by using `elem.innerHTML+="something"`. +We can append "more HTML" by using `elem.innerHTML+="something"`. Like this: @@ -227,7 +248,7 @@ chatDiv.innerHTML += "<div>Hello<img src='smile.gif'/> !</div>"; chatDiv.innerHTML += "How goes?"; ``` -But in practice we should be very careful about doing it, because what's going on is *not* an addition, but a full overwrite. +But we should be very careful about doing it, because what's going on is *not* an addition, but a full overwrite. Technically, these two lines do the same: @@ -242,13 +263,13 @@ elem.innerHTML = elem.HTML + "..." In other words, `innerHTML+=` does this: 1. The old contents is removed. -2. The new `innerHTML` is written instead. +2. The new `innerHTML` is written instead (a concatenation of the old and the new one). -As the content is "zeroed-out" and rewritten from the scratch, **all images and other resources will be reloaded**. +**As the content is "zeroed-out" and rewritten from the scratch, all images and other resources will be reloaded**. -In the `chatDiv` example above the second line reloads `smile.gif` that was added before. If `chatDiv` had a lot of other text and images, then the reload will be rather noticeable. +In the `chatDiv` example above the line `chatDiv.innerHTML+="How goes?"` re-creates the HTML content and reloads `smile.gif` (hope it's cached). If `chatDiv` has a lot of other text and images, then the reload becomes clearly visible. -There are other side-effects as well. For instance, if the existing text was selected with the mouse, then most browsers will remove the selection upon rewriting `innerHTML`. And if there was `<input>` with a text entered by the visitor, then the text will be removed also. And so on. +There are other side-effects as well. For instance, if the existing text was selected with the mouse, then most browsers will remove the selection upon rewriting `innerHTML`. And if there was an `<input>` with a text entered by the visitor, then the text will be removed. And so on. Luckily, there are other ways to add HTML besides `innerHTML`, and we'll study them soon. @@ -266,7 +287,7 @@ Here's an example: </script> ``` -Unlike `innerHTML`, writing to `outerHTML` does not change the element. Instead, it replaces it as a whole in the outer context. +Unlike `innerHTML`, writing to `outerHTML` does not change the element. Instead, it replaces it as a whole in the outer context. Yeah, sounds strange, and strange it is. Take a look. Consider the example: @@ -288,22 +309,21 @@ Consider the example: </script> ``` -In the line `(*)` the `<div>...</div>` is replaced by `<p>...</p>` in the document. But the old `div` variable is still the same. The `outerHTML` assignment does not modify the element, but rather inserts a new piece of HTML instead of it. +In the line `(*)` we take the full HTML of `<div>...</div>` and replace it by `<p>...</p>`. In the outer document we can see the new content instead of the `<div>`. But the old `div` variable is still the same. + +The `outerHTML` assignment does not modify the DOM element, but extracts it from the outer context and inserts a new piece of HTML instead of it. -```warn header="Wrote into `outerHTML`? Mind the consequences!" Novice developers sometimes make an error here: they modify `div.outerHTML` and then continue to work with `div` as if it had the new content in it. That's possible with `innerHTML`, but not with `outerHTML`. -We can write to `outerHTML`, but should keep in mind that it doesn't change the element we're writing to. It creates the new content on its place instead. We can access new elements by querying DOM. - -``` +We can write to `outerHTML`, but should keep in mind that it doesn't change the element we're writing to. It creates the new content on its place instead. We can get a reference to new elements by querying DOM. ## nodeValue/data: text node content The `innerHTML` property is only valid for element nodes. -Other node types have the counterpart: `nodeValue` and `data` properties. These two are almost the same for practical use, so we'll use `data`, cause it's shorter. +Other node types have their counterpart: `nodeValue` and `data` properties. These two are almost the same for practical use, there are only minor specification differences. So we'll use `data`, because it's shorter. We can read it, like this: @@ -313,15 +333,19 @@ We can read it, like this: <!-- Comment --> <script> let text = document.body.firstChild; +*!* alert(text.data); // Hello +*/!* let comment = text.nextSibling; +*!* alert(comment.data); // Comment +*/!* </script> </body> ``` -For text nodes we can imagine why read or modify them, but why comments? Usually, they are not interesting, but sometimes developers embed information into HTML using them like this: +For text nodes we can imagine a reason to read or modify them, but why comments? Usually, they are not interesting at all, but sometimes developers embed information into HTML in them, like this: ```html <!-- if isAdmin --> @@ -329,11 +353,11 @@ For text nodes we can imagine why read or modify them, but why comments? Usually <!-- /if --> ``` -...And then Javascript can read it. +...Then Javascript can read it and process embedded instructions. ## textContent: pure text -The `textContent` provides access to *text* inside the element: only text, minus all `<tags>`. +The `textContent` provides access to the *text* inside the element: only text, minus all `<tags>`. For instance: @@ -353,9 +377,14 @@ As we can see, only text is returned, as if all `<tags>` were cut out, but the t In practice, reading such text is rarely needed. -**Writing to `textContent` is much more useful, because it allows to write the text "as text".** +**Writing to `textContent` is much more useful, because it allows to write text the "safe way".** -Compare that with writing "as HTML" with `innerHTML`: +Let's say we have an arbitrary string, for instance entered by a user, and want to show it. + +- With `innerHTML` we'll have it inserted "as HTML", with all HTML tags. +- With `textContent` we'll have it inserted "as text", all symbols are treated literally. + +Compare the two: ```html run <div id="elem1"></div> @@ -372,17 +401,19 @@ Compare that with writing "as HTML" with `innerHTML`: 1. The first `<div>` gets the name "as HTML": all tags become tags, so we see the bold name. 2. The second `<div>` gets the nams "as text", so we literally see `<b>Winnie-the-pooh!</b>`. -In most cases, if don't really want users to insert arbitrary HTML-code in our site. Using `textContent` is one of ways to defend from it. +In most cases, we expect the text from a user, and want to treat it as text. We don't want unexpected HTML in our site. An assignment to `textContent` does exactly that. ## The "hidden" property -The "hidden" attribute and the DOM property is a way to define whether the element is seen or not. +The "hidden" attribute and the DOM property specifies whether the element is visible or not. We can use it in HTML or assign using Javascript, like this: ```html run height="80" <div>Both divs below are hidden</div> + <div hidden>With the attribute "hidden"</div> + <div id="elem">Javascript assigned the property "hidden"</div> <script> @@ -390,15 +421,26 @@ We can use it in HTML or assign using Javascript, like this: </script> ``` -Technically, the `hidden` works the same as `style="display:none"`. But it's easier to set with Javascript. Also screenreaders and other special devices may use it. +Technically, `hidden` works the same as `style="display:none"`. But it's shorter to write. + +Here's a blinking element: + + +```html run height=50 +<div id="elem">A blinking element</div> + +<script> + setInterval(() => elem.hidden = !elem.hidden, 1000); +</script> +``` ## More properties -DOM elements also have additional properties, many of them depend on the class: +DOM elements also have additional properties, many of them provided by the class: -- `value` -- the value for `<input>`, `<select>` and `<textarea>`. -- `href` -- the "href" for `<a href="...">`. -- `id` -- the value of "id" attribute, for all elements. +- `value` -- the value for `<input>`, `<select>` and `<textarea>` (`HTMLInputElement`, `HTMLSelectElement`...). +- `href` -- the "href" for `<a href="...">` (`HTMLAnchorElement`). +- `id` -- the value of "id" attribute, for all elements (`HTMLElement`). - ...and much more... For instance: @@ -413,15 +455,15 @@ For instance: </script> ``` -Actually, most standard HTML attributes have the corresponding DOM property. +Most standard HTML attributes have the corresponding DOM property, and we can access it like that. -In case we want to know the full list of supported properties for a given class, we can refer to the specification. For instance, HTMLInputElement can be found at <https://html.spec.whatwg.org/#htmlinputelement>. +If we want to know the full list of supported properties for a given class, we can find them in the specification. For instance, HTMLInputElement is documented at <https://html.spec.whatwg.org/#htmlinputelement>. -Or if we're interested to see it in the concrete browser -- we can always output the element using `console.dir(elem)` or explore "DOM properties" in Elements tab of the browser developer tools. +Or if we'd like to get them fast or interested in the concrete browser -- we can always output the element using `console.dir(elem)` and read the properties. Or explore "DOM properties" in Elements tab of the browser developer tools. ## Summary -DOM objects form a class hierarchy. The full set of properties and methods comes as the result of inheritance. +Each DOM node belongs to a certain class. The classes form a hierarchy. The full set of properties and methods comes as the result of inheritance. Main DOM node properties are: @@ -446,4 +488,6 @@ Main DOM node properties are: `hidden` : When set to `true`, does the same as CSS `display:none`. -DOM nodes also have other properties depending on their class. For instance, `<input>` elements (`HTMLInputElement`) support `value`, `type`, while `<a>` elements (`HTMLAnchorElement`) support `href` etc. Most standard HTML attributes have the corresponding DOM property. But HTML attributes and DOM properties are not the same (to be explained in the next chapters). +DOM nodes also have other properties depending on their class. For instance, `<input>` elements (`HTMLInputElement`) support `value`, `type`, while `<a>` elements (`HTMLAnchorElement`) support `href` etc. Most standard HTML attributes have the corresponding DOM property. + +But HTML attributes and DOM properties are not always the same, as we'll see in the next chapter. diff --git a/2-ui/1-document/06-attributes-and-properties/2-yellow-links/solution.md b/2-ui/1-document/06-attributes-and-properties/2-yellow-links/solution.md index fc68da7d..726be4c8 100644 --- a/2-ui/1-document/06-attributes-and-properties/2-yellow-links/solution.md +++ b/2-ui/1-document/06-attributes-and-properties/2-yellow-links/solution.md @@ -18,7 +18,7 @@ for (let link of links) { if (href.startsWith('http://internal.com')) continue; // internal - link.style.color = 'yellow'; + link.style.color = 'orange'; } ``` @@ -32,5 +32,5 @@ Please note: we use `link.getAttribute('href')`. Not `link.href`, because we nee let selector = 'a[href*="://"]:not([href^="http://internal.com"])'; let links = document.querySelectorAll(selector); -links.forEach(link => link.style.color = 'yellow'); +links.forEach(link => link.style.color = 'orange'); ``` diff --git a/2-ui/1-document/06-attributes-and-properties/2-yellow-links/solution.view/index.html b/2-ui/1-document/06-attributes-and-properties/2-yellow-links/solution.view/index.html index 7de9418b..4209a5f3 100644 --- a/2-ui/1-document/06-attributes-and-properties/2-yellow-links/solution.view/index.html +++ b/2-ui/1-document/06-attributes-and-properties/2-yellow-links/solution.view/index.html @@ -2,7 +2,7 @@ <html> <body> - <a name="list">the list</a> + <a name="list">The list:</a> <ul> <li><a href="http://google.com">http://google.com</a></li> <li><a href="/tutorial">/tutorial.html</a></li> @@ -16,7 +16,7 @@ let selector = 'a[href*="://"]:not([href^="http://internal.com"])'; let links = document.querySelectorAll(selector); - links.forEach(link => link.style.color = 'yellow'); + links.forEach(link => link.style.color = 'orange'); </script> </body> diff --git a/2-ui/1-document/06-attributes-and-properties/2-yellow-links/task.md b/2-ui/1-document/06-attributes-and-properties/2-yellow-links/task.md index 732e3dc9..f7db9a04 100644 --- a/2-ui/1-document/06-attributes-and-properties/2-yellow-links/task.md +++ b/2-ui/1-document/06-attributes-and-properties/2-yellow-links/task.md @@ -2,9 +2,9 @@ importance: 3 --- -# Make external links yellow +# Make external links orange -Make all external links yellow by altering their `style` property. +Make all external links orange by altering their `style` property. A link is external if: - It's `href` has `://` in it @@ -26,7 +26,7 @@ Example: <script> // setting style for a single link let link = document.querySelector('a'); - link.style.color = 'yellow'; + link.style.color = 'orange'; </script> ``` diff --git a/2-ui/1-document/06-attributes-and-properties/article.md b/2-ui/1-document/06-attributes-and-properties/article.md index 0d17db15..974bc750 100644 --- a/2-ui/1-document/06-attributes-and-properties/article.md +++ b/2-ui/1-document/06-attributes-and-properties/article.md @@ -1,16 +1,16 @@ # DOM: attributes and properties -The browser "reads" HTML text and generates DOM objects from it. For element nodes most standard HTML attributes automatically become properties of DOM objects. +When the browser loads the page, it "reads" (another word: "parses") HTML text and generates DOM objects from it. For element nodes most standard HTML attributes automatically become properties of DOM objects. For instance, if the tag is `<body id="page">`, then the DOM object will have `body.id="page"`. -But the mapping is not one-to-one! In this chapter we'll see that DOM properties and attributes are linked, but they are still different things. +But the mapping is not one-to-one! In this chapter we'll see that DOM properties and attributes are linked, but they may be different. [cut] ## DOM properties -We've already seen built-in DOM properties. But technically no one limits us -- we can add own own. +We've already seen built-in DOM properties. There's a lot. But technically no one limits us, and if it's not enough -- we can add own own. DOM nodes are regular Javascript objects. We can alter them. @@ -46,11 +46,10 @@ document.documentElement.sayHi(); // Hello, I'm HTML document.body.sayHi(); // Hello, I'm BODY ``` -So, DOM properties: +So, DOM properties and methods behave just like those of regular Javascript objects: -- Can have any value. -- Are case-sensitive (`elem.nodeType`, not `elem.NoDeTyPe`). -- Work, because DOM nodes are objects. +- They can have any value. +- They are case-sensitive (write `elem.nodeType`, not `elem.NoDeTyPe`). ## HTML attributes @@ -71,9 +70,9 @@ For instance: </body> ``` -Please note that a standard attribute for one element can be unknown for another one. +Please note that a standard attribute for one element can be unknown for another one. For instance, `"type"` is standard for `<input>` ([HTMLInputElement](https://html.spec.whatwg.org/#htmlinputelement)), but not for `<body>` ([HTMLBodyElement](https://html.spec.whatwg.org/#htmlbodyelement)). Standard attributes are described in the specification for the corresponding class. -Standard attributes are described in the specification for the corresponding class. For instance, `"type"` is standard for `<input>` ([HTMLInputElement](https://html.spec.whatwg.org/#htmlinputelement)), but not for `<body>` ([HTMLBodyElement](https://html.spec.whatwg.org/#htmlbodyelement)): +Here we can see it: ```html run <body id="body" type="..."> <input id="input" type="text"> @@ -95,10 +94,10 @@ Sure. All attributes are accessible using following methods: - `elem.setAttribute(name, value)` -- sets the value. - `elem.removeAttribute(name)` -- removes the attribute. -Also one can read all attributes using `elem.attributes`. It's a collection of [Attr](https://dom.spec.whatwg.org/#attr) objects, each one with `name` and `value`. - These methods operate exactly with what's written in HTML. +Also one can read all attributes using `elem.attributes`: a collection of objects that belong to a built-in [Attr](https://dom.spec.whatwg.org/#attr) class. It's enough to know that each of them has `name` and `value` properties. + Here's a demo of reading a non-standard property: ```html run @@ -143,7 +142,7 @@ Please note: 3. All attributes including ones that we set are seen in `innerHTML`. 4. The `attributes` collection is iterable and has all attributes with `name` and `value`. -## Property-attribute sync +## Property-attribute synchronization When a standard attribute changes, the corresponding property is auto-updated, and (with some exceptions) vise-versa. @@ -189,7 +188,7 @@ In the example above: - Changing the attribute `value` updates the property. - But the property change does not affect the attribute. -Speaking about `value`, that actually can come in handy, because the user may modify `value` as he wants, and so do we. Then, if we want to recover the "original" value from HTML, it's in the attribute. +That actually can come in handy, because the user may modify `value`, then, if we want to recover the "original" value from HTML, it's in the attribute. ## DOM properties are typed @@ -199,29 +198,31 @@ DOM properties are not always strings. For instance, `input.checked` property (f <input id="input" type="checkbox" checked> checkbox <script> - alert(input.getAttribute('checked')); // empty string - alert(input.checked); // true + alert(input.getAttribute('checked')); // the attribute value is: empty string + alert(input.checked); // the property value is: true </script> ``` -There are more advanced examples. As we've already seen, `style` property is an object: +There are other examples. The `style` attribute is a string, but `style` property is an object: ```html run <div id="div" style="color:red;font-size:120%">Hello</div> <script> + // string alert(div.getAttribute('style')); // color:red;font-size:120% + // object alert(div.style); // [object CSSStyleDeclaration] alert(div.style.color); // red </script> ``` -**But even if a DOM property type is a string, it may differ from the attribute.** +But even if a DOM property type is a string, it may differ from the attribute. -For instance, the `href` DOM property is always a full URL (by the standard), even if the attribute has a relative URL or just a `#hash` part. +For instance, the `href` DOM property is always a full URL (by the standard), even if the attribute has a relative URL or just a `#hash`. -Here we can see that: +Here's an example: ```html height=30 run <a id="a" href="#hello">link</a> @@ -230,42 +231,49 @@ Here we can see that: alert(a.getAttribute('href')); // #hello // property - alert(a.href ); // full URL like http://site.com/page#hello + alert(a.href ); // full URL in the form http://site.com/page#hello </script> ``` -Let's note again: if we need the value exactly as written in the HTML, we need to use `getAttribute`. +If we need the value of `href` or anything else exactly as written in the HTML, we need to use `getAttribute`. ## Non-standard attributes, dataset -When writing HTML, we use a lot of standard attributes. But what about non-standard, custom ones? May they be useful? What for? +When writing HTML, we use a lot of standard attributes. But what about non-standard, custom ones? First, let's see whether they are useful or not? What for? -Sometimes they are used to pass custom data from HTML to Javascript, or "mark" elements like this: +Sometimes non-standard attributes are used to pass custom data from HTML to Javascript, or "mark" elements. + +Like this: ```html run -<!-- marking to show the "name" here --> +<!-- mark the div to show "name" here --> <div *!*show-info="name"*/!*></div> +<!-- and age here --> +<div *!*show-info="age"*/!*></div> <script> // the code finds an element with the mark and shows what's requested - let info = { - name: "Pete" + let user = { + name: "Pete", + age: 25 }; - let div = document.querySelector('[show-info]'); - - // insert info.name into the div - div.innerHTML = info[div.getAttribute('show-info')]; // Pete + for(let div of document.querySelectorAll('[show-info]')) { + // insert the corresponding info into the field + let field = div.getAttribute('show-info'); + div.innerHTML = user[field]; // Pete, then age + } </script> ``` -Also, they can be used to style the element. +Also they can be used to style an element. For instance, here for the order state the attribute `order-state` is used: ```html run <style> + /* styles rely on the custom attribute "order-state" */ .order[order-state="new"] { color: green; } @@ -294,19 +302,19 @@ For instance, here for the order state the attribute `order-state` is used: Why the attribute was chosen in the example above, not classes like `.order-state-new`, `.order-state-pending`, `order-state-canceled`? -That's because an attribute is more convenient to manage. If we want to change the order state, we can modify it like this: +That's because an attribute is more convenient to manage. The state can be changed as easy as: ```js div.setAttribute('order-state', 'canceled'); ``` -...But there may be a possible problem here. What if we use a non-standard attribute for our purposes and later the standard introduces it and makes it do something? The HTML language is alive, it grows, more attributes appear to suit the needs of developers. There may be unexpected side-effects. +But there may be a possible problem. What if we use a non-standard attribute for our purposes and later the standard introduces it and makes it do something? The HTML language is alive, it grows, more attributes appear to suit the needs of developers. There may be unexpected side-effects in case of such conflict. To evade conflicts, there exist [data-*](https://html.spec.whatwg.org/#embedding-custom-non-visible-data-with-the-data-*-attributes) attributes. **All attributes starting with "data-" are reserved for programmers' use. They are available in `dataset` property.** -So, if an `elem` has an attribute named `"data-about"`, it's available as `elem.dataset.about`. +For instance, if an `elem` has an attribute named `"data-about"`, it's available as `elem.dataset.about`. Like this: @@ -341,41 +349,29 @@ Here's a rewritten "order state" example: </div> <script> + // read alert(order.dataset.orderState); // new + + // modify order.dataset.orderState = "pending"; </script> ``` -Using `data-*` attributes for custom purposes is a valid, safe way. +Using `data-*` attributes is a valid, safe way to pass custom data. -Please note that we can not only read, but modify data-attributes. Then CSS updates the view accordingly: in the example above the last line changes the color to blue. +Please note that we can not only read, but also modify data-attributes. Then CSS updates the view accordingly: in the example above the last line changes the color to blue. ## Summary - Attributes -- is what's written in HTML. - Properties -- is what's in DOM objects. -<table> -<thead> -<tr> -<th></th> -<th>Properties</th> -<th>Attributes</th> -</tr> -</thead> -<tbody> -<tr> -<td>Type</td> -<td>Any value, standard properties have types described in the spec</td> -<td>A string</td> -</tr> -<tr> -<td>Name</td> -<td>Name is case-sensitive</td> -<td>Name is case-insensitive</td> -</tr> -</tbody> -</table> +A small comparison: + +| | Properties | Attributes | +|------------|------------|------------| +|Type|Any value, standard properties have types described in the spec|A string| +|Name|Name is case-sensitive|Name is case-insensitive| Methods to work with attributes are: @@ -385,9 +381,7 @@ Methods to work with attributes are: - `elem.removeAttribute(name)` -- to remove the attribute. - `elem.attributes` is a collection of all attributes. -We use those in cases when DOM properties do not suit us and we need exactly attributes for some reasons. +We use attributes when DOM properties do not suit us and we need exactly attributes for some reasons, for instance: -Some use cases: - -- We want to access a non-standard attribute. But if it starts with `data-`, then we should use `dataset`. +- We need a non-standard attribute. But if it starts with `data-`, then we should use `dataset`. - We want to read the value "as written" in HTML. The value of the DOM property may be different, for instance `href` property is always a full URL, and we may want to get the "original" value. diff --git a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/solution.md b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/solution.md index 35b51d9e..93ae862f 100644 --- a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/solution.md +++ b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/solution.md @@ -1,6 +1,6 @@ Answer: **1 and 3**. -Both commands result in adding `text` "as text" into the `elem`. +Both commands result in adding the `text` "as text" into the `elem`. Here's an example: diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/task.md b/2-ui/1-document/07-modifying-document/10-clock-setinterval/task.md index f5c52557..ba6e3a84 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/task.md +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/task.md @@ -6,4 +6,4 @@ importance: 4 Create a colored clock like here: -[iframe src="solution" height=100] +[iframe src="solution" height=60] diff --git a/2-ui/1-document/07-modifying-document/11-append-to-list/solution.md b/2-ui/1-document/07-modifying-document/11-append-to-list/solution.md index a7b703e7..4e77fb5c 100644 --- a/2-ui/1-document/07-modifying-document/11-append-to-list/solution.md +++ b/2-ui/1-document/07-modifying-document/11-append-to-list/solution.md @@ -1,6 +1,8 @@ -There are many possible solutions here. +When we need to insert a piece of HTML somewhere, `insertAdjacentHTML` is the best fit. + +The solution: -For instance: - -- `one.insertAdjacentHTML('afterend', '<li>2</li><li>3</li>')` +```js +one.insertAdjacentHTML('afterend', '<li>2</li><li>3</li>'); +``` diff --git a/2-ui/1-document/07-modifying-document/4-clear-elem/solution.md b/2-ui/1-document/07-modifying-document/4-clear-elem/solution.md index 437e8b58..62c3386d 100644 --- a/2-ui/1-document/07-modifying-document/4-clear-elem/solution.md +++ b/2-ui/1-document/07-modifying-document/4-clear-elem/solution.md @@ -1,5 +1,5 @@ -First, let's see how not to do it: +First, let's see how *not* to do it: ```js function clear(elem) { @@ -9,11 +9,11 @@ function clear(elem) { } ``` -That won't work, because the call to `remove()` shifts the collection `elem.childNodes` every time, so elements every time start from index `0`. So `i` should not increase in the loop at all. +That won't work, because the call to `remove()` shifts the collection `elem.childNodes`, so elements start from the index `0` every time. But `i` increases, and some elements will be skipped. The `for..of` loop also does the same. -The right variant would be: +The right variant could be: ```js function clear(elem) { @@ -23,7 +23,7 @@ function clear(elem) { } ``` -And also there's a simpler variant: +And also there's a simpler way to do the same: ```js function clear(elem) { diff --git a/2-ui/1-document/07-modifying-document/4-clear-elem/task.md b/2-ui/1-document/07-modifying-document/4-clear-elem/task.md index 5c14caa3..938d5347 100644 --- a/2-ui/1-document/07-modifying-document/4-clear-elem/task.md +++ b/2-ui/1-document/07-modifying-document/4-clear-elem/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# clear +# Clear the element -Create a function `clear(elem)` that removes everything from element. +Create a function `clear(elem)` that removes everything from the element. -```html run +```html run height=60 <ol id="elem"> <li>Hello</li> <li>World</li> diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md index 940c57e9..25c1e819 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md @@ -1,5 +1,9 @@ -The HTML in the task is incorrect. That's the matter. There may be no text inside the `<table>`, only table-specific tags. +The HTML in the task is incorrect. That's the reason of the odd thing. -The question can be easily solved by exploring the DOM in the browser tools. Then we'll see that the browser placed the text `"aaa"` *before* the table. +The browser has to fix it automatically. But there may be no text inside the `<table>`: according to the spec only table-specific tags are allowed. So the browser adds `"aaa"` *before* the `<table>`. -The HTML standard thoroughly specifies how to process bad HTML, and the behavior of the browser here is correct. +Now it's obvious that when we remove the table, it remains. + +The question can be easily answered by exploring DOM using the browser tools. They show `"aaa"` before the `<table>`. + +The HTML standard specifies in detail how to process bad HTML, and such behavior of the browser is correct. diff --git a/2-ui/1-document/07-modifying-document/6-create-list/solution.md b/2-ui/1-document/07-modifying-document/6-create-list/solution.md index cd3037c8..1669be18 100644 --- a/2-ui/1-document/07-modifying-document/6-create-list/solution.md +++ b/2-ui/1-document/07-modifying-document/6-create-list/solution.md @@ -1 +1 @@ -Please note using `textContent` to assign the `<li>` content. +Please note the usage of `textContent` to assign the `<li>` content. diff --git a/2-ui/1-document/07-modifying-document/9-calendar-table/task.md b/2-ui/1-document/07-modifying-document/9-calendar-table/task.md index 5dbb0ab8..37b1a60d 100644 --- a/2-ui/1-document/07-modifying-document/9-calendar-table/task.md +++ b/2-ui/1-document/07-modifying-document/9-calendar-table/task.md @@ -10,7 +10,7 @@ The call should create a calendar for the given year/month and put it inside `el The calendar should be a table, where a week is `<tr>`, and a day is `<td>`. The table top should be `<th>` with weekday names: the first day should be Monday, and so on till Sunday. -For instance, `createCalendar(cal, 2012, 9)` should generate in <code><div id='cal'></div></code> the following calendar: +For instance, `createCalendar(cal, 2012, 9)` should generate in element `cal` the following calendar: [iframe height=210 src="solution"] diff --git a/2-ui/1-document/07-modifying-document/article.md b/2-ui/1-document/07-modifying-document/article.md index 8c54dd03..153e5cd9 100644 --- a/2-ui/1-document/07-modifying-document/article.md +++ b/2-ui/1-document/07-modifying-document/article.md @@ -1,16 +1,16 @@ # Modifying DOM -Modifying DOM is the key to create "live" pages. +DOM modifications is the key to create "live" pages. Here we'll see how to create new elements "on the fly" and modify the existing page content. -There are many methods for that. First we'll see a simple example and then explain them. +First we'll see a simple example and then explain the methods. [cut] ## Example: show a message -For the start, let's see how to add a message on the page, that looks nicer than `alert`. +For the start, let's see how to add a message on the page that looks nicer than `alert`. Here's how it will look: @@ -32,7 +32,7 @@ Here's how it will look: */!* ``` -Now let's create the same `div` with Javascript. +That was an HTML example. Now let's create the same `div` with Javascript (assuming that the styles are still in the HTML or an external CSS). ## Creating an element @@ -55,7 +55,7 @@ To create DOM nodes, there are two methods: ### Creating the message -In our case we want to make a `div`, add classes and the message into it: +In our case we want to make a `div` with given classes and the message in it: ```js let div = document.createElement('div'); @@ -63,18 +63,42 @@ div.className = "alert alert-success"; div.innerHTML = "<strong>Hi there!</strong> You've read an important message."; ``` -After that, we have a ready DOM element. Right now it's in the variable `div`, but not yet seen, because not inserted into the page. +After that, we have a ready DOM element. Right now it's in the variable, but can not be seen, because not inserted into the page yet. ## Insertion methods -To make the `div` show up, we need to insert it somewhere into `document`. +To make the `div` show up, we need to insert it somewhere into `document`. For instance, in `document.body`. -Let's say we want to insert it into `parentElem`, like `let parentElem=document.body`. +There's a special method for that: `document.body.appendChild(div)`. -There exist following methods to insert a node: +Here's the full code: -`parentElem.appendChild(elem)` -: Appends `elem` as the last child of `parentElem`. +```html run height="80" +<style> +.alert { + padding: 15px; + border: 1px solid #d6e9c6; + border-radius: 4px; + color: #3c763d; + background-color: #dff0d8; +} +</style> + +<script> + let div = document.createElement('div'); + div.className = "alert alert-success"; + div.innerHTML = "<strong>Hi there!</strong> You've read an important message."; + +*!* + document.body.appendChild(div); +*/!* +</script> +``` + +Here's a brief list of methods to insert a node into a parent element (`parentElem` for short): + +`parentElem.appendChild(node)` +: Appends `node` as the last child of `parentElem`. The following example adds a new `<li>` to the end of `<ol>`: @@ -93,8 +117,8 @@ There exist following methods to insert a node: </script> ``` -`parentElem.insertBefore(elem, nextSibling)` -: Inserts `elem` before `nextSibling` into `parentElem`. +`parentElem.insertBefore(node, nextSibling)` +: Inserts `node` before `nextSibling` into `parentElem`. The following code inserts a new list item before the second `<li>`: @@ -120,42 +144,14 @@ There exist following methods to insert a node: list.insertBefore(newLi, list.firstChild); ``` -`parentElem.replaceChild(elem, oldChild)` -: Replaces `oldChild` with `elem` among children of `parentElem`. +`parentElem.replaceChild(node, oldChild)` +: Replaces `oldChild` with `node` among children of `parentElem`. -All these methods return the inserted node. In other words, `parentElem.appendChild(elem)` returns `elem`. But usually the returned value is not used, we just run the method. +All these methods return the inserted node. In other words, `parentElem.appendChild(node)` returns `node`. But usually the returned value is not used, we just run the method. -For our example, it would be like this: +These methods are "old school": they exist from the ancient times and we can meet them in many old scripts. Unfortunately, there are some tasks that are hard to solve with them. -```html run height="80" -<style> -.alert { - padding: 15px; - border: 1px solid #d6e9c6; - border-radius: 4px; - color: #3c763d; - background-color: #dff0d8; -} -</style> - -<script> -*!* - let div = document.createElement('div'); - div.className = "alert alert-success"; - div.innerHTML = "<strong>Hi there!</strong> You've read an important message."; - - document.body.appendChild(div); -*/!* -</script> -``` - -These methods are "old school": they exist from the ancient times and we can meet them in many old scripts. - -Unfortunately, there are some tasks that are hard to solve with them. - -For instance, how to insert *html* if we have it as a string? Or, given a node, how to insert something not into it, but *before* it? - -Of course, all that is solvable, but not in an elegant way. +For instance, how to insert *html* if we have it as a string? Or, given a node, how to insert another node *before* it? Of course, all that is doable, but not in an elegant way. So there exist two other sets of insertion methods to handle all cases easily. @@ -169,7 +165,7 @@ This set of methods provides more flexible insertions: - `node.after(...nodes or strings)` –- insert nodes or strings after the `node`, - `node.replaceWith(...nodes or strings)` –- replaces `node` with the given nodes or strings. -Let's say we have a list, like this: +Here's an example of using these methods to add more items to a list and the text before/after it: ```html autorun <ol id="ol"> @@ -192,7 +188,7 @@ Let's say we have a list, like this: </script> ``` -Here's where the insertions will go: +Here's a small picture what methods do: ![](before-prepend-append-after.png) @@ -210,7 +206,7 @@ before after ``` -These methods can insert a list of nodes and text pieces. But please note: all text is inserted *as text*. +These methods can insert multiple list of nodes and text pieces in a single call. For instance, here a string and an element are inserted: @@ -221,7 +217,9 @@ For instance, here a string and an element are inserted: </script> ``` -The final HTML would be: +All text is inserted *as text*. + +So the final HTML is: ```html run *!* @@ -231,15 +229,15 @@ The final HTML would be: <div id="div"></div> ``` -In other words, strings are inserted exactly "as text", in a safe way, like `elem.textContent` does it. +In other words, strings are inserted in a safe way, like `elem.textContent` does it. -So, these methods allow to insert DOM nodes or text pieces at given places. +So, these methods can only be used to insert DOM nodes or text pieces. -But what if we want to insert HTML "as html", with all tags and stuff working, like `elem.innerHTML` does it? +But what if we want to insert HTML "as html", with all tags and stuff working, like `elem.innerHTML`? ### insertAdjacentHTML/Text/Element -There's a versatile method `elem.insertAdjacentHTML(where, html)`. +There's another, pretty versatile method: `elem.insertAdjacentHTML(where, html)`. The first parameter is a string, specifying where to insert, must be one of the following: @@ -248,7 +246,7 @@ The first parameter is a string, specifying where to insert, must be one of the - `"beforeend"` -- insert `html` into `elem`, at the end, - `"afterend"` -- insert `html` after `elem`. -The second parameter `html` is a HTML string, inserted "as is". +The second parameter is an HTML string, inserted "as is". For instance: @@ -256,6 +254,7 @@ For instance: <div id="div"></div> <script> div.insertAdjacentHTML('beforebegin', '<p>Hello</p>'); + div.insertAdjacentHTML('afterend', '<p>Bye</p>'); </script> ``` @@ -264,6 +263,7 @@ For instance: ```html run <p>Hello</p> <div id="div"></div> +<p>Bye</p> ``` That's how we can append an arbitrary HTML to our page. @@ -272,14 +272,14 @@ Here's the picture of insertion variants: ![](insert-adjacent.png) -We definitely can notice similarities between this and the previous picture. The insertion points are actually the same, but here we can insert HTML. +We can easily notice similarities between this and the previous picture. The insertion points are actually the same, but this method inserts HTML. The method has two brothers: - `elem.insertAdjacentText(where, text)` -- the same syntax, but a string of `text` in inserted "as text" instead of HTML, - `elem.insertAdjacentElement(where, elem)` -- the same syntax, but inserts an element. -They exist mainly to make the syntax "uniform". In practice, most of time only `insertAdjacentHTML` is used, because for elements and text we have methods `append/prepend/before/after` -- they are just shorter to write. +They exist mainly to make the syntax "uniform". In practice, most of time only `insertAdjacentHTML` is used, because for elements and text we have methods `append/prepend/before/after` -- they are shorter to write and can insert nodes/text pieces. So here's an alternative variant of showing a message: @@ -305,11 +305,11 @@ So here's an alternative variant of showing a message: How to insert one more similar message? -We could do a message-generating function and put the code there. But the alternative way would be to *clone* the existing `div` and modify the text inside it. +We could do a function and put the code there. But the alternative way would be to *clone* the existing `div` and modify the text inside it (if needed). Sometimes when we have a big element, that may be faster and simpler. -The call `elem.cloneNode(true)` creates a "deep" clone of the element -- with all attributes and subelements. If we call it with `false`, then there would be no child elements. +- The call `elem.cloneNode(true)` creates a "deep" clone of the element -- with all attributes and subelements. If we call `elem.clonseNode(false)`, then the clone is made without child elements. An example of copying the message: @@ -363,12 +363,12 @@ For instance, let's swap elements: <div id="second">Second</div> <script> // no need to call remove - second.after(first); // after second insert first + second.after(first); // take #second and after it - insert #first </script> ``` ```` -Let's make our message to disappear after a second: +Let's make our message disappear after a second: ```html run untrusted <style> @@ -396,7 +396,7 @@ Let's make our message to disappear after a second: ## A word about "document.write" -There's one more, quite ancient method of adding something to a web-page: `document.write`. +There's one more, very ancient method of adding something to a web-page: `document.write`. The syntax: @@ -410,7 +410,7 @@ The syntax: <p>The end</p> ``` -The call to `document.write(html)` writes the `html` into page "right here and now". The `html` string can be dynamically generated, so it's kind of flexible. +The call to `document.write(html)` writes the `html` into page "right here and now". The `html` string can be dynamically generated, so it's kind of flexible. We can use Javascript to create a full-fledged webpage and write it. The method comes from times when there were no DOM, no standards... Really old times. It still lives, because there are scripts using it. @@ -418,7 +418,7 @@ In modern scripts we can rarely see it, because of the important limitation. **The call to `document.write` only works while the page is loading.** -If we call it after it, the existing document will be erased. +If we call it afterwards, the existing document content is erased. For instance: @@ -427,20 +427,21 @@ For instance: *!* <script> // document.write after 1 second + // that's after the page loaded, so it erases the existing content setTimeout(() => document.write('<b>...By this.</b>'), 1000); </script> */!* ``` -The method `document.write` works at the "reading HTML" phase. It appends something to the page and the browser consumes it along with the rest of HTML. - So it's kind of unusable at "after loaded" stage, unlike other DOM methods we covered above. That was the downside. -The upside -- it works blazingly fast, because it writes directly into the text, without interfering with complex DOM structures. +Technically, when `document.write` is called while the browser is still reading HTML, it appends something to it, and the browser consumes it just as it were initially there. -So if we need to add a lot of text into HTML dynamically, and we're at page loading phase, and the speed matters, it may help. But in practice that's a really rare use case. Mostly we can see this method in scripts just because they are old. +That gives us the upside -- it works blazingly fast, because there's *no DOM modification*. It writes directly into the page text, while the DOM is not yet built, and the browser puts it into DOM at generation-time. + +So if we need to add a lot of text into HTML dynamically, and we're at page loading phase, and the speed matters, it may help. But in practice these requirements rarely come together. And usually we can see this method in scripts just because they are old. ## Summary @@ -453,14 +454,14 @@ Methods to create new nodes: Insertion and removal of nodes: - From the parent: - - `parent.appendChild(elem)` - - `parent.insertBefore(elem, nextSibling)` - - `parent.removeChild(elem)` - - `parent.replaceChild(newElem, elem)` + - `parent.appendChild(node)` + - `parent.insertBefore(node, nextSibling)` + - `parent.removeChild(node)` + - `parent.replaceChild(newElem, node)` - all thes methods return `elem`. + All these methods return `node`. -- Given a node: +- Given a list of nodes and strings: - `node.append(...nodes or strings)` -- insert into `node`, at the end, - `node.prepend(...nodes or strings)` -- insert into `node`, at the beginning, - `node.before(...nodes or strings)` –- insert right before `node`, @@ -468,9 +469,9 @@ Insertion and removal of nodes: - `node.replaceWith(...nodes or strings)` –- replace `node`. - `node.remove()` –- remove the `node`. - All these methods accept a list of DOM nodes or text strings. Text strings are inserted "as text". + Text strings are inserted "as text". -- To insert HTML: `elem.insertAdjacentHTML(where, html)`, inserts depending on where: +- Given a piece of HTML: `elem.insertAdjacentHTML(where, html)`, inserts depending on where: - `"beforebegin"` -- insert `html` right before `elem`, - `"afterbegin"` -- insert `html` into `elem`, at the beginning, - `"beforeend"` -- insert `html` into `elem`, at the end, diff --git a/2-ui/1-document/08-styles-and-classes/article.md b/2-ui/1-document/08-styles-and-classes/article.md index 21051f51..bf0fdf10 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -1,17 +1,19 @@ # Styles and classes -Before we get to Javascript ways of dealing with styles and classes -- here's an important rule. +Before we get to Javascript ways of dealing with styles and classes -- here's an important rule. Hopefully it's obvious enough, but we still have to mention it. -There are generally two ways to style an element, both in HTML and Javascript: +There are generally two ways to style an element: 1. Create a class in CSS and add it: `<div class="...">` 2. Write properties directly into `style`: `<div style="...">`. [cut] -CSS is always the preferred way, both in HTML and Javascript. We should only use `style` if classes "can't handle it". +CSS is always the preferred way -- not only for HTML, but in Javascript as well. -For instance, `style` is acceptable if we calculated coordinates for an element dynamically and want to set them from Javascript, like here: +We should only manipulate the `style` property if classes "can't handle it". + +For instance, `style` is acceptable if we calculate coordinates of an element dynamically and want to set them from Javascript, like this: ```js let top = /* complex calculations */; @@ -26,9 +28,9 @@ For other cases, like making the text red, adding a background icon -- describe Changing a class is one of the most often actions in scripts. -In the ancient time, there was a limitation in Javascript: a reserved word like `"class"` could not be an object property. That limitation does not exist now, but at that time it was impossible to use `elem.class`. +In the ancient time, there was a limitation in Javascript: a reserved word like `"class"` could not be an object property. That limitation does not exist now, but at that time it was impossible to have a `"class"` property, like `elem.class`. -So instead of `elem.class` we have `elem.className` property. It's the string with all classes, the same value as in the `"class"` attribute. +So for classes the similar-looking property `"className"` was introduced: the `elem.className` corresponds to the `"class"` attribute. For instance: @@ -40,7 +42,9 @@ For instance: </body> ``` -Adding/removing a class is a widespread operation. Using a string for such purpose is cumbersome, so there's another property for that: `elem.classList`. +If we assign something to `elem.className`, it replaces the whole strings of classes. Sometimes that's what we need, but often we want to add/remove a single class. + +There's another property for that: `elem.classList`. The `elem.classList` is a special object with methods to `add/remove/toggle` classes. @@ -49,7 +53,11 @@ For instance: ```html run <body class="main page"> <script> +*!* + // add a class document.body.classList.add('article'); +*/!* + alert(document.body.className); // main page article </script> </body> @@ -104,25 +112,25 @@ button.style.WebkitBorderRadius = '5px'; That is: a dash `"-"` becomes an uppercase. ```` -## Resetting the style +## Resetting the style property -To "reset" the style property, we should assign an empty line to it. For instance, if we set a `width` and now want to remove it, then `elem.style.width=""`. +Sometimes we want to assign a style property, and later remove it. For instance, to hide an element, we can set `elem.style.display = "none"`. -And to show it back, we should not set another `display` like `elem.style.display = "block"`. To return the "default" `display`: `elem.style.display = ""`. +Then later we may want to remove the `style.display` as if it were not set. Instead of `delete elem.style.display` we should assign an empty line to it: `elem.style.display = ""`. ```js run -// if we run this code, the <body> would "blink" -document.body.style.display = "none"; +// if we run this code, the <body> "blinks" +document.body.style.display = "none"; // hide -setTimeout(() => document.body.style.display = "", 1000); +setTimeout(() => document.body.style.display = "", 1000); // back to normal ``` -If we set `display` to an empty string, then the browser applies CSS classes and its built-in styles normally, as if there were no such `style` property. +If we set `display` to an empty string, then the browser applies CSS classes and its built-in styles normally, as if there were no such `style` property at all. ````smart header="Full rewrite with `style.cssText`" -Normally, `style.*` assign individual style properties. We can't set the full style like `div.style="color: red; width: 100px"`, because `div.style` is an object. +Normally, we use `style.*` to assign individual style properties. We can't set the full style like `div.style="color: red; width: 100px"`, because `div.style` is an object, and it's read-only. To set the full style as a string, there's a special property `style.cssText`: @@ -141,27 +149,27 @@ To set the full style as a string, there's a special property `style.cssText`: </script> ``` -We rarely use it, because such a setting removes all existing styles: not adds, but rather replaces them. But still can be done for new elements when we know we don't delete something important. +We rarely use it, because such assignment removes all existing styles: it does not add, but replaces them. May occasionally delete something needed. But still can be done for new elements when we know we don't delete something important. -The same can be accomplished by setting an attribute: `div.setAttribute('style', "color: red...")`. +The same can be accomplished by setting an attribute: `div.setAttribute('style', 'color: red...')`. ```` ## Mind the units -CSS units must exist in values. We should not set `elem.style.top` to `10`, but rather to `10px`. Otherwise it wouldn't work. +CSS units must be provided in style values. -For instance: +For instance, we should not set `elem.style.top` to `10`, but rather to `10px`. Otherwise it wouldn't work: ```html run height=100 <body> <script> *!* - // won't work! + // doesn't work! document.body.style.margin = 20; - alert(document.body.style.margin); // '' (empty string) + alert(document.body.style.margin); // '' (empty string, the assignment is ignored) */!* - // now the right way + // now add the CSS unit (px) - and it works document.body.style.margin = '20px'; alert(document.body.style.margin); // 20px @@ -171,7 +179,7 @@ For instance: </body> ``` -Please note how the browser "unpacks" the property `style.margin` and infers `style.marginLeft` and `style.marginTop` (and other partial margins) from it. +Please note how the browser "unpacks" the property `style.margin` in the last lines and infers `style.marginLeft` and `style.marginTop` (and other partial margins) from it. ## Computed styles: getComputedStyle @@ -179,11 +187,11 @@ Modifying a style is easy. But how to *read* it? For instance, we want to know the size, margins, the color of an element. How to do it? -**The `style` property contains only the style in the `"style"` attribute, without any CSS cascade.** +**The `style` property operates only on the value of the `"style"` attribute, without any CSS cascade.** -So we can't read anything that comes from CSS classes. +So we can't read anything that comes from CSS classes using `elem.style`. -For instance, here `style` won't see the margin: +For instance, here `style` doesn't see the margin: ```html run height=60 no-beautify <head> @@ -201,7 +209,7 @@ For instance, here `style` won't see the margin: </body> ``` -...But what if we need, say, increase the margin by 20px? We want the current value for that. +...But what if we need, say, increase the margin by 20px? We want the current value for the start. There's another method for that: `getComputedStyle`. @@ -230,7 +238,7 @@ For instance: <script> let computedStyle = getComputedStyle(document.body); - // now can read the margin and the color from it + // now we can read the margin and the color from it alert( computedStyle.marginTop ); // 5px alert( computedStyle.color ); // rgb(255, 0, 0) @@ -242,20 +250,20 @@ For instance: ```smart header="Computed and resolved values" There are two concepts in [CSS](https://drafts.csswg.org/cssom/#resolved-values): -1. A *computed* style value is the one after all CSS rules and CSS inheritance is applied. If can look like `width: auto` or `font-size: 125%`. -2. A *resolved* style value is the one finally applied to the element. The browser takes the computed value and makes all units fixed and absolute, for instance: `width: 212px` or `font-size: 16px`. In some browsers values can have a floating point. +1. A *computed* style value is the value after all CSS rules and CSS inheritance is applied, as the result of the CSS cascade. If can look like `width:auto` or `font-size:125%`. +2. A *resolved* style value is the one finally applied to the element. Values like `auto` or `125%` are still abstract. The browser takes the computed value and makes all units fixed and absolute, for instance: `width:212px` or `font-size:16px`. For geometry properties resolved values may have a floating point, like `width:50.5px`. -Long time ago `getComputedStyle` was created to get computed values, but it turned out that resolved values are much more convenient. +Long time ago `getComputedStyle` was created to get computed values, but it turned out that resolved values are much more convenient, and the standard changed. -So nowadays `getComputedStyle` actually returns the final, resolved value. +So nowadays `getComputedStyle` actually returns the final, resolved value in absolute units. ``` ````warn header="`getComputedStyle` requires the full property name" We should always ask for the exact property that we want, like `paddingLeft` or `marginTop` or `borderTopWidth`. Otherwise the correct result is not guaranteed. -For instance, if properties `paddingLeft/paddingTop` come from the different CSS classes, then what should we get for `getComputedStyle(elem).padding`? +For instance, if there are properties `paddingLeft/paddingTop`, then what should we get for `getComputedStyle(elem).padding`? Nothing, or maybe a "generated" value from known paddings? There's no standard rule here. -Some browsers (Chrome) show `10px` in the document below, and some of them (Firefox) -- do not: +There are other inconsistencies. As an example, some browsers (Chrome) show `10px` in the document below, and some of them (Firefox) -- do not: ```html run <style> @@ -275,12 +283,13 @@ Visited links may be colored using `:visited` CSS pseudoclass. But `getComputedStyle` does not give access to that color, because otherwise an arbitrary page could find out whether the user visited a link by creating it on the page and checking the styles. -Javascript we may not see the styles applied by `:visited`. And also, there's a limitation in CSS that forbids to apply geometry-changing styles in `:visited`. That's to guarantee that there's no side way for an evil page to see if a link was visited and hence to break the privacy. +Javascript we may not see the styles applied by `:visited`. And also, there's a limitation in CSS that forbids to apply geometry-changing styles in `:visited`. That's to guarantee that there's no side way for an evil page to test if a link was visited and hence to break the privacy. ``` ## Summary To manage classes, there are two DOM properties: + - `className` -- the string value, good to manage the whole set of classes. - `classList` -- the object with methods `add/remove/toggle/contains`, good for individual classes. diff --git a/2-ui/1-document/09-size-and-scroll/article.md b/2-ui/1-document/09-size-and-scroll/article.md index f302f538..42c2b4e6 100644 --- a/2-ui/1-document/09-size-and-scroll/article.md +++ b/2-ui/1-document/09-size-and-scroll/article.md @@ -1,15 +1,15 @@ # Element size and scrolling -To show elements at arbitrary places in the page we should: +There are many Javascript properties that allow to read information about element width, height and other geometry features. -1. First, know CSS positioning. -2. Second, know how to handle "geometry" properties in Javascript. +We often need them when moving or positioning elements in Javascript, to correctly calculate coordinates. [cut] + ## Sample element -For the example we'll use the element with the border, padding and scrolling: +As a sample element to demonstrate properties we'll use the one given below: ```html no-beautify <div id="example"> @@ -26,7 +26,7 @@ For the example we'll use the element with the border, padding and scrolling: </style> ``` -It has no margins, because they are irrelevant here for us, as they are not the part of the element itself. +It has the border, padding and scrolling. The full set of features. There are no margins, as they are not the part of the element itself, and there are no special properties for them. The element looks like this: @@ -37,11 +37,11 @@ You can [open the document in the sandbox](sandbox:metric). ```smart header="Mind the scrollbar" The picture above demonstrates the most complex case when the element has a scrollbar. Some browsers (not all) reserve the space for it by taking it from the content. -So, without scrollbar the content width would be `300px`, but if the scrollbar is `16px` wide (the width may vary for devices and browsers) then only `300-16 = 284px` remains. Our code should work well if the scrollbar exists and occupies some place, so we consider it the case here. +So, without scrollbar the content width would be `300px`, but if the scrollbar is `16px` wide (the width may vary between devices and browsers) then only `300-16 = 284px` remains, and we should take it into account. That's why examples from this chapter assume that there's a scrollbar. If there's no scrollbar, then things are just a bit simpler. ``` ```smart header="The `padding-bottom` may be filled with text" -Usually paddings are shown empty on illustrations, but if there's a lot of text in the element and it overflows, then the browsers show it at `padding-bottom`. +Usually paddings are shown empty on illustrations, but if there's a lot of text in the element and it overflows, then browsers show the "overflowing" text at `padding-bottom`, so you can see that in examples. But the padding is still there, unless specified otherwise. ``` ## Geometry @@ -52,21 +52,21 @@ Here's the overall picture: ![](metric-all.png) -All properties hardly fit in the picture, but as we'll see soon, their values are simple and easy to understand. +They are many properties, it's difficult to fit them all in the single picture, but their values are simple and easy to understand. -Let's start exploring them from the outer side of the element. +Let's start exploring them from the outside of the element. ## offsetParent, offsetLeft/Top -These properties are rarely needed. But still they are the "most outer" geometry properties, so we'll start with them. +These properties are rarely needed, but still they are the "most outer" geometry properties, so we'll start with them. The `offsetParent` is the nearest ancestor that is: -1. CSS-positioned (`position` is `absolute`, `relative` or `fixed`). -2. or `<td>`, `<th>`, `<table>`. -2. or `<body>` +1. CSS-positioned (`position` is `absolute`, `relative` or `fixed`), +2. or `<td>`, `<th>`, `<table>`, +2. or `<body>`. -The `offsetParent` alone has no use. But `offsetLeft/offsetTop` provide x/y coordinates relative to it's left-upper corner. +In most practical cases we can use `offsetParent` to get the nearest CSS-positioned ancestor. And `offsetLeft/offsetTop` provide x/y coordinates relative to it's left-upper corner. In the example below the inner `<div>` has `<main>` as `offsetParent` and `offsetLeft/offsetTop` are shifts from its left-upper corner (`180`): @@ -108,7 +108,9 @@ For our sample element: ````smart header="Geometry properties for not shown elements are zero/null" Geometry properties are calculated only for shown elements. -If an element (or any of its ancestors) has `display:none` or is not in the document, then `offsetParent` is `null` and `offsetWidth`, `offsetHeight` and other numeric properties are `0`. +If an element (or any of its ancestors) has `display:none` or is not in the document, then all geometry properties are zero or `null` depending on what it is. + +For example, `offsetParent` is `null`, and `offsetWidth`, `offsetHeight` are `0`. We can use this to check if an element is hidden, like this: @@ -118,7 +120,7 @@ function isHidden(elem) { } ``` -Should keep in mind that such `isHidden` returns `true` for elements that are on-screen, but have zero sizes (like an empty `<div>`). +Please note that such `isHidden` returns `true` for elements that are on-screen, but have zero sizes (like an empty `<div>`). ```` ## clientTop/Left @@ -138,7 +140,7 @@ In our example: What's the difference? -It becomes visible when the document is right-to-left (OS in arabic or hebrew languages). The scrollbar is then not on the right, but on the left, and then `clientLeft` also includes the scrollbar width. +It becomes visible when the document is right-to-left (the operation system is in arabic or hebrew languages). The scrollbar is then not on the right, but on the left, and then `clientLeft` also includes the scrollbar width. In that case `clientLeft` in our example would be not `25`, but with the scrollbar width `25+16=41`: diff --git a/2-ui/1-document/index.md b/2-ui/1-document/index.md index eedfdb9a..31ab4920 100644 --- a/2-ui/1-document/index.md +++ b/2-ui/1-document/index.md @@ -1,3 +1,3 @@ -# Document and the web-page. +# Document Here we'll learn to manipulate a web-page using Javascript. diff --git a/2-ui/3-event-details/10-onload-ondomcontentloaded/article.md b/2-ui/3-event-details/10-onload-ondomcontentloaded/article.md index bb5f4e17..7e096984 100644 --- a/2-ui/3-event-details/10-onload-ondomcontentloaded/article.md +++ b/2-ui/3-event-details/10-onload-ondomcontentloaded/article.md @@ -1,38 +1,38 @@ -# Загрузка документа: DOMContentLoaded, load, beforeunload, unload +# Page loading: DOMContentLoaded, load, beforeunload, unload [todo: where put async defer scripts? in DOM?] -Процесс загрузки HTML-документа, условно, состоит из трёх стадий: +The process of loading an HTML document may be split into three stages: -- `DOMContentLoaded` -- браузер полностью загрузил HTML и построил DOM-дерево. -- `load` -- браузер загрузил все ресурсы. -- `beforeunload/unload` -- уход со страницы. +- `DOMContentLoaded` -- the browser fully loaded HTML and built DOM. +- `load` -- the browser loaded all resources (images, styles etc). +- `beforeunload/unload` -- leaving the page. -Все эти стадии очень важны. На каждую можно повесить обработчик, чтобы совершить полезные действия: +We can set a handler on every stage: -- `DOMContentLoaded` -- означает, что все DOM-элементы разметки уже созданы, можно их искать, вешать обработчики, создавать интерфейс, но при этом, возможно, ещё не догрузились какие-то картинки или стили. -- `load` -- страница и все ресурсы загружены, используется редко, обычно нет нужды ждать этого момента. -- `beforeunload/unload` -- можно проверить, сохранил ли посетитель изменения, уточнить, действительно ли он хочет покинуть страницу. +- `DOMContentLoaded` event -- DOM is ready, we can lookup DOM nodes, initialize the interface. But images and styles may be not yet loaded. +- `load` event -- the page and additional resources are loaded, it's rarely used, because usually we don't want to wait for that moment. +- `beforeunload/unload` event -- we can check if the user saved changes he did in the page, ask him whether he's sure. -Далее мы рассмотрим важные детали этих событий. +Let's explore the details of these events. [cut] ## DOMContentLoaded -Событие `DOMContentLoaded` происходит на `document` и поддерживается во всех браузерах, кроме IE8-. Про поддержку аналогичного функционала в старых IE мы поговорим в конце главы. +The `DOMContentLoaded` event happens on the `document` object. -Обработчик на него вешается только через `addEventListener`: +We must use `addEventListener` to catch it: ```js document.addEventListener("DOMContentLoaded", ready); ``` -Пример: +For instance: ```html run height=150 <script> function ready() { - alert( 'DOM готов' ); - alert( "Размеры картинки: " + img.offsetWidth + "x" + img.offsetHeight ); + alert('DOM is ready'); + alert(`Image sizes: ${img.offsetWidth}x${img.offsetHeight}`); } *!* @@ -40,16 +40,29 @@ document.addEventListener("DOMContentLoaded", ready); */!* </script> -<img id="img" src="https://js.cx/clipart/yozhik.jpg?speed=1"> +<img id="img" src="https://en.js.cx/clipart/hedgehog.jpg?speed=1&cache=0"> ``` -В примере выше обработчик `DOMContentLoaded` сработает сразу после загрузки документа, не дожидаясь получения картинки. +In the example the `DOMContentLoaded` handler runs when the document is loaded, not waits for the page load. So `alert` shows zero sizes. + +At the first sight `DOMContentLoaded` event is very simple. The DOM tree is ready -- here's the event. But there are few pecularities. + +### DOMContentLoaded and scripts + +If there are `<script>...</script>` tags in the document, then the browser must execute them "at place" while building DOM. + +External scripts `<script src="...">` also put "DOM building" to pause while the script is loading and executing. + +The exceptions are external scripts with `async` or `defer` attributes. + +- An async external script `<script async src="...">` is loaded and executed fully asynchronously, it doesn't pause anything. +- A deferred external script `<script defer src="...">` is loaded and executed fully asynchronously, it doesn't pause anything, with two differences from `async`: + 1. If there are many external scripts with `defer` + 2. lba + -Поэтому на момент вывода `alert` и сама картинка будет невидна и её размеры -- неизвестны (кроме случая, когда картинка взята из кеша браузера). -В своей сути, событие `onDOMContentLoaded` -- простое, как пробка. Полностью создано DOM-дерево -- и вот событие. Но с ним связан ряд существенных тонкостей. -### DOMContentLoaded и скрипты Если в документе есть теги `<script>`, то браузер обязан их выполнить до того, как построит DOM. Поэтому событие `DOMContentLoaded` ждёт загрузки и выполнения таких скриптов. @@ -201,4 +214,3 @@ document.documentElement.doScroll("left"); - Событие `window.onload` используют редко, поскольку обычно нет нужды ждать подгрузки *всех* ресурсов. Если же нужен конкретный ресурс (картинка или ифрейм), то можно поставить событие `onload` непосредственно на нём, мы посмотрим, как это сделать, далее. - Событие `window.onunload` почти не используется, как правило, оно бесполезно -- мало что можно сделать, зная, что окно браузера прямо сейчас закроется. - Гораздо чаще применяется `window.onbeforeunload` -- это де-факто стандарт для того, чтобы проверить, сохранил ли посетитель данные, действительно ли он хочет покинуть страницу. В системах редактирования документов оно используется повсеместно. - diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.png index 3a56239b..ea572330 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside@2x.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside@2x.png index 4b852fa1..6a17ca99 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside@2x.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside@2x.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.png index 5df6be24..babbea50 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems@2x.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems@2x.png index 50a381b9..e61891a5 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems@2x.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems@2x.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.png index 024a1a7f..628aa330 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout@2x.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout@2x.png index 4d0ca726..c320e2cc 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout@2x.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout@2x.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.png index cdc37a2d..627f9584 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.png differ diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child@2x.png b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child@2x.png index 3f2ec02b..51a917d8 100644 Binary files a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child@2x.png and b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child@2x.png differ