This commit is contained in:
Ilya Kantor 2017-03-21 14:41:49 +03:00
parent 4ae129054e
commit ab9ab64bd5
476 changed files with 3370 additions and 532 deletions

View file

@ -0,0 +1,20 @@
```html run height=100
<!DOCTYLE HTML>
<html>
<body>
<div data-widget-name="menu">Choose the genre</div>
<script>
// getting it
let elem = document.querySelector('[data-widget-name]');
// reading the value
alert(elem.dataset.widgetName);
// or
alert(elem.getAttribute('data-widget-name'));
</script>
</body>
</html>
```

View file

@ -0,0 +1,21 @@
importance: 5
---
# Get the attribute
Write the code to select the element with `data-widget-name` attribute from the document and to read its value.
```html run
<!DOCTYLE HTML>
<html>
<body>
<div data-widget-name="menu">Choose the genre</div>
<script>
/* your code */
</script>
</body>
</html>
```

View file

@ -0,0 +1,36 @@
First, we need to find all external references.
There are two ways.
The first is to find all links using `document.querySelectorAll('a')` and then filter out what we need:
```js
let links = document.querySelectorAll('a');
for (let link of links) {
*!*
let href = link.getAttribute('href');
*/!*
if (!href) continue; // no attribute
if (!href.includes('://')) continue; // no protocol
if (href.startsWith('http://internal.com')) continue; // internal
link.style.color = 'orange';
}
```
Please note: we use `link.getAttribute('href')`. Not `link.href`, because we need the value from HTML.
...Another, simpler way would be to add the checks to CSS selector:
```js
// look for all links that have :// in href
// but href doesn't start with http://internal.com
let selector = 'a[href*="://"]:not([href^="http://internal.com"])';
let links = document.querySelectorAll(selector);
links.forEach(link => link.style.color = 'orange');
```

View file

@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<body>
<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>
<li><a href="local/path">local/path</a></li>
<li><a href="ftp://ftp.com/my.zip">ftp://ftp.com/my.zip</a></li>
<li><a href="http://nodejs.org">http://nodejs.org</a></li>
<li><a href="http://internal.com/test">http://internal.com/test</a></li>
</ul>
<script>
let selector = 'a[href*="://"]:not([href^="http://internal.com"])';
let links = document.querySelectorAll(selector);
links.forEach(link => link.style.color = 'orange');
</script>
</body>
</html>

View file

@ -0,0 +1,20 @@
<!DOCTYPE HTML>
<html>
<body>
<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>
<li><a href="local/path">local/path</a></li>
<li><a href="ftp://ftp.com/my.zip">ftp://ftp.com/my.zip</a></li>
<li><a href="http://nodejs.org">http://nodejs.org</a></li>
<li><a href="http://internal.com/test">http://internal.com/test</a></li>
</ul>
<script>
// ...your code...
</script>
</body>
</html>

View file

@ -0,0 +1,35 @@
importance: 3
---
# Make external links orange
Make all external links orange by altering their `style` property.
A link is external if:
- It's `href` has `://` in it
- But doesn't start with `http://internal.com`.
Example:
```html run
<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>
<li><a href="local/path">local/path</a></li>
<li><a href="ftp://ftp.com/my.zip">ftp://ftp.com/my.zip</a></li>
<li><a href="http://nodejs.org">http://nodejs.org</a></li>
<li><a href="http://internal.com/test">http://internal.com/test</a></li>
</ul>
<script>
// setting style for a single link
let link = document.querySelector('a');
link.style.color = 'orange';
</script>
```
The result should be:
[iframe border=1 height=180 src="solution"]

View file

@ -0,0 +1,387 @@
# Attributes and properties
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 may be different.
[cut]
## DOM properties
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.
For instance, let's create a new property in `document.body`:
```js run
document.body.myData = {
name: 'Caesar',
title: 'Imperator'
};
alert(document.body.myData.title); // Imperator
```
We can add a method as well:
```js run
document.body.sayHi = function() {
alert(this.tagName);
};
document.body.sayHi(); // BODY (the value of "this" in the method is document.body)
```
We can also modify built-in prototypes like `Element.prototype` and add new methods to all elements:
```js run
Element.prototype.sayHi = function() {
alert(`Hello, I'm ${this.tagName}`);
};
document.documentElement.sayHi(); // Hello, I'm HTML
document.body.sayHi(); // Hello, I'm BODY
```
So, DOM properties and methods behave just like those of regular JavaScript objects:
- They can have any value.
- They are case-sensitive (write `elem.nodeType`, not `elem.NoDeTyPe`).
## HTML attributes
In HTML language, tags may have attributes. When the browser reads HTML text and creates DOM objects for tags, it recognizes *standard* attributes and creates DOM properties from them.
So when an element has `id` or another standard attribute, the corresponding property gets created. But that doesn't happen if the attribute is non-standard.
For instance:
```html run
<body id="test" something="non-standard">
<script>
alert(document.body.id); // test
*!*
// non-standard attribute does not yield a property
alert(document.body.something); // undefined
*/!*
</script>
</body>
```
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.
Here we can see it:
```html run
<body id="body" type="...">
<input id="input" type="text">
<script>
alert(input.type); // text
*!*
alert(body.type); // undefined: DOM property not created, because it's non-standard
*/!*
</script>
</body>
```
So, if an attribute is non-standard, there won't be DOM-property for it. Is there a way to access such attributes?
Sure. All attributes are accessible using following methods:
- `elem.hasAttribute(name)` -- checks for existance.
- `elem.getAttribute(name)` -- gets the value.
- `elem.setAttribute(name, value)` -- sets the value.
- `elem.removeAttribute(name)` -- removes the attribute.
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
<body something="non-standard">
<script>
*!*
alert(document.body.getAttribute('something')); // non-standard
*/!*
</script>
</body>
```
HTML attributes have following features:
- Their name is case-insensitive (that's HTML: `id` is same as `ID`).
- They are always strings.
Here's an extended demo of working with attributes:
```html run
<body>
<div id="elem" about="Elephant"></div>
<script>
alert( elem.getAttribute('About') ); // (1) 'Elephant', reading
elem.setAttribute('Test', 123); // (2), writing
alert( elem.outerHTML ); // (3), see it's there
for (let attr of elem.attributes) { // (4) list all
alert( attr.name + " = " + attr.value );
}
</script>
</body>
```
Please note:
1. `getAttribute('About')` -- the first letter is uppercase here, and in HTML it's all lowercase. But that doesn't matter: attribute names are case-insensitive.
2. We can assign anything to an attribute, but that becomes a string. So here we have `"123"` as the value.
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 synchronization
When a standard attribute changes, the corresponding property is auto-updated, and (with some exceptions) vise-versa.
In the example below `id` is modified as an attribute, and we can see the property change too. And then the same backwards:
```html run
<input>
<script>
let input = document.querySelector('input');
// attribute => property
input.setAttribute('id', 'id');
alert(input.id); // id (updated)
// property => attribute
input.id = 'newId';
alert(input.getAttribute('id')); // newId (updated)
</script>
```
But there are exclusions, for instance `input.value` synchronizes only from attribute to property:
```html run
<input>
<script>
let input = document.querySelector('input');
// attribute => property
input.setAttribute('value', 'text');
alert(input.value); // text
*!*
// NOT property => attribute
input.value = 'newValue';
alert(input.getAttribute('value')); // text (not updated!)
*/!*
</script>
```
In the example above:
- Changing the attribute `value` updates the property.
- But the property change does not affect 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
DOM properties are not always strings. For instance, `input.checked` property (for checkboxes) and other similar properties are boolean:
```html run
<input id="input" type="checkbox" checked> checkbox
<script>
alert(input.getAttribute('checked')); // the attribute value is: empty string
alert(input.checked); // the property value is: true
</script>
```
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.
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's an example:
```html height=30 run
<a id="a" href="#hello">link</a>
<script>
// attribute
alert(a.getAttribute('href')); // #hello
// property
alert(a.href ); // full URL in the form http://site.com/page#hello
</script>
```
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? First, let's see whether they are useful or not? What for?
Sometimes non-standard attributes are used to pass custom data from HTML to JavaScript, or "mark" elements.
Like this:
```html run
<!-- 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 user = {
name: "Pete",
age: 25
};
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 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;
}
.order[order-state="pending"] {
color: blue;
}
.order[order-state="canceled"] {
color: red;
}
</style>
<div class="order" order-state="new">
A new order.
</div>
<div class="order" order-state="pending">
A pending order.
</div>
<div class="order" order-state="canceled">
A canceled order.
</div>
```
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. The state can be changed as easy as:
```js
div.setAttribute('order-state', 'canceled');
```
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.**
For instance, if an `elem` has an attribute named `"data-about"`, it's available as `elem.dataset.about`.
Like this:
```html run
<body data-about="Elephants">
<script>
alert(document.body.dataset.about); // Elephants
</script>
```
Multiword attributes like `data-order-state` become camel-cased: `dataset.orderState`.
Here's a rewritten "order state" example:
```html run
<style>
.order[data-order-state="new"] {
color: green;
}
.order[data-order-state="pending"] {
color: blue;
}
.order[data-order-state="canceled"] {
color: red;
}
</style>
<div id="order" class="order" data-order-state="new">
A new order.
</div>
<script>
// read
alert(order.dataset.orderState); // new
// modify
order.dataset.orderState = "pending";
</script>
```
Using `data-*` attributes is a valid, safe way to pass custom data.
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.
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:
- `elem.hasAttribute(name)` -- to check for existance.
- `elem.getAttribute(name)` -- to get the value.
- `elem.setAttribute(name, value)` -- to set the value.
- `elem.removeAttribute(name)` -- to remove the attribute.
- `elem.attributes` is a collection of all attributes.
We use attributes when DOM properties do not suit us and we need exactly attributes for some reasons, for instance:
- 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.