selection

This commit is contained in:
Ilya Kantor 2020-12-04 18:15:41 +03:00
parent e1a3f634a4
commit 44f4795a4c
4 changed files with 32 additions and 27 deletions

View file

@ -8,19 +8,17 @@ libs:
In this chapter we'll cover selection in the document, as well as selection in form fields, such as `<input>`. In this chapter we'll cover selection in the document, as well as selection in form fields, such as `<input>`.
JavaScript can get the existing selection, select/deselect both as a whole or partially, remove the selected part from the document, wrap it into a tag, and so on. JavaScript can access an existing selection, select/deselect DOM nodes as a whole or partially, remove the selected content from the document, wrap it into a tag, and so on.
You can get ready to use recipes at the end, in "Summary" section. But you'll get much more if you read the whole chapter. The underlying `Range` and `Selection` objects are easy to grasp, and then you'll need no recipes to make them do what you want. You can find some recipes for common tasks at the end of the chapter, in "Summary" section. Maybe that covers your current needs, but you'll get much more if you read the whole text.
The underlying `Range` and `Selection` objects are easy to grasp, and then you'll need no recipes to make them do what you want.
## Range ## Range
The basic concept of selection is [Range](https://dom.spec.whatwg.org/#ranges): basically, a pair of "boundary points": range start and range end. The basic concept of selection is [Range](https://dom.spec.whatwg.org/#ranges), that is essentially a pair of "boundary points": range start and range end.
Each point represented as a parent DOM node with the relative offset from its start. If the parent node is an element node, then the offset is a child number, for a text node it's the position in the text. Examples to follow. A `Range` object is created without parameters:
Let's select something.
First, we can create a range (the constructor has no parameters):
```js ```js
let range = new Range(); let range = new Range();
@ -28,13 +26,18 @@ let range = new Range();
Then we can set the selection boundaries using `range.setStart(node, offset)` and `range.setEnd(node, offset)`. Then we can set the selection boundaries using `range.setStart(node, offset)` and `range.setEnd(node, offset)`.
For example, consider this fragment of HTML: The first argument `node` can be either a text node or an element node. The meaning of the second argument depends on that:
```html - If `node` is a text node, then `offset` must be the position in the text.
- If `node` is an element node, then `offset` must be the child number.
For example, let's create a range in this fragment:
```html autorun
<p id="p">Example: <i>italic</i> and <b>bold</b></p> <p id="p">Example: <i>italic</i> and <b>bold</b></p>
``` ```
Here's its DOM structure, note that here text nodes are important for us: Here's its DOM structure:
<div class="select-p-domtree"></div> <div class="select-p-domtree"></div>
@ -72,10 +75,17 @@ let selectPDomtree = {
drawHtmlTree(selectPDomtree, 'div.select-p-domtree', 690, 320); drawHtmlTree(selectPDomtree, 'div.select-p-domtree', 690, 320);
</script> </script>
Let's select `"Example: <i>italic</i>"`. That's two first children of `<p>` (counting text nodes): Let's make a range for `"Example: <i>italic</i>"`.
As we can see, this phrase consists of exactly the first and the second children of `<p>`:
![](range-example-p-0-1.svg) ![](range-example-p-0-1.svg)
- The starting point has `<p>` as the parent `node`, and `0` as the offset.
- The ending point also has `<p>` as the parent `node`, but `2` as the offset (it specifies the range up to, but not including `offset`).
Here's the demo, if you run it, you can see that the text gets selected:
```html run ```html run
<p id="p">Example: <i>italic</i> and <b>bold</b></p> <p id="p">Example: <i>italic</i> and <b>bold</b></p>
@ -87,17 +97,14 @@ Let's select `"Example: <i>italic</i>"`. That's two first children of `<p>` (cou
range.setEnd(p, 2); range.setEnd(p, 2);
*/!* */!*
// toString of a range returns its content as text (without tags) // toString of a range returns its content as text, without tags
alert(range); // Example: italic console.log(range); // Example: italic
// apply this range for document selection (explained later) // let's apply this range for document selection (explained later)
document.getSelection().addRange(range); document.getSelection().addRange(range);
</script> </script>
``` ```
- `range.setStart(p, 0)` -- sets the start at the 0th child of `<p>` (that's the text node `"Example: "`).
- `range.setEnd(p, 2)` -- spans the range up to (but not including) 2nd child of `<p>` (that's the text node `" and "`, but as the end is not included, so the last selected node is `<i>`).
Here's a more flexible test stand where you try more variants: Here's a more flexible test stand where you try more variants:
```html run autorun ```html run autorun
@ -121,7 +128,7 @@ From <input id="start" type="number" value=1> To <input id="end" type="numbe
</script> </script>
``` ```
E.g. selecting from `1` to `4` gives range `<i>italic</i> and <b>bold</b>`. E.g. selecting in the same `<p>` from offset `1` to `4` gives range `<i>italic</i> and <b>bold</b>`:
![](range-example-p-1-3.svg) ![](range-example-p-1-3.svg)
@ -264,11 +271,9 @@ There also exist methods to compare ranges, but these are rarely used. When you
## Selection ## Selection
`Range` is a generic object for managing selection ranges. We may create such objects, pass them around -- they do not visually select anything on their own. `Range` is a generic object for managing selection ranges. We may create `Range` objects, pass them around -- they do not visually select anything on their own.
The document selection is represented by `Selection` object, that can be obtained as `window.getSelection()` or `document.getSelection()`. The document selection is represented by `Selection` object, that can be obtained as `window.getSelection()` or `document.getSelection()`. A selection may include zero or more ranges. At least, the [Selection API specification](https://www.w3.org/TR/selection-api/) says so. In practice though, only Firefox allows to select multiple ranges in the document by using `key:Ctrl+click` (`key:Cmd+click` for Mac).
A selection may include zero or more ranges. At least, the [Selection API specification](https://www.w3.org/TR/selection-api/) says so. In practice though, only Firefox allows to select multiple ranges in the document by using `key:Ctrl+click` (`key:Cmd+click` for Mac).
Here's a screenshot of a selection with 3 ranges, made in Firefox: Here's a screenshot of a selection with 3 ranges, made in Firefox:
@ -289,7 +294,7 @@ The main selection properties are:
- `isCollapsed` -- `true` if selection selects nothing (empty range), or doesn't exist. - `isCollapsed` -- `true` if selection selects nothing (empty range), or doesn't exist.
- `rangeCount` -- count of ranges in the selection, maximum `1` in all browsers except Firefox. - `rangeCount` -- count of ranges in the selection, maximum `1` in all browsers except Firefox.
````smart header="Selection end may be in the document before start" ````smart header="Usually, the selection end `focusNode` is after its start `anchorNode`, but it's not always the case"
There are many ways to select the content, depending on the user agent: mouse, hotkeys, taps on a mobile etc. There are many ways to select the content, depending on the user agent: mouse, hotkeys, taps on a mobile etc.
Some of them, such as a mouse, allow the same selection can be created in two directions: "left-to-right" and "right-to-left". Some of them, such as a mouse, allow the same selection can be created in two directions: "left-to-right" and "right-to-left".

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

View file

@ -41,13 +41,13 @@ We can search for characters with a property, written as `pattern:\p{…}`. To u
For instance, `\p{Letter}` denotes a letter in any of language. We can also use `\p{L}`, as `L` is an alias of `Letter`. There are shorter aliases for almost every property. For instance, `\p{Letter}` denotes a letter in any of language. We can also use `\p{L}`, as `L` is an alias of `Letter`. There are shorter aliases for almost every property.
In the example below three kinds of letters will be found: English, Georgean and Korean. In the example below three kinds of letters will be found: English, Georgian and Korean.
```js run ```js run
let str = "A ბ ㄱ"; let str = "A ბ ㄱ";
alert( str.match(/\p{L}/gu) ); // A,ბ,ㄱ alert( str.match(/\p{L}/gu) ); // A,ბ,ㄱ
alert( str.match(/\p{L}/g) ); // null (no matches, as there's no flag "u") alert( str.match(/\p{L}/g) ); // null (no matches, \p doesn't work without the flag "u")
``` ```
Here's the main character categories and their subcategories: Here's the main character categories and their subcategories:

Binary file not shown.