You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In this chapter we'll cover selection in the document, as well as selection in form fields, such as `<input>`.
10
10
11
-
JavaScript can do everything with it: get the existing selection, select/deselect it or its parts, remove the selected part from the document, wrap it into a tag, and so on.
11
+
JavaScript can do 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.
12
12
13
-
You can get a few ready to use recipes at the end, in "Summary" section. But you'll get much more if you read on. The underlying `Range` and `Selection` objects are easy to grasp, and then you'll need no recipes to make them do what you want.
13
+
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.
14
14
15
15
## Range
16
16
17
17
The basic concept of selection is [Range](https://dom.spec.whatwg.org/#ranges): basically, a pair of "boundary points": range start and range end.
18
18
19
-
Each point represented as a parent DOM node with the relative offset from its start. For an element node, the offset is a child number, for a text node it's the position in the text.
19
+
Each point represented as a parent DOM node with the relative offset from its start. If the parent node is an element element node, then the offset is a child number, for a text node it's the position in the text. Examples to follow.
20
+
21
+
Let's select something.
20
22
21
23
First, we can create a range (the constructor has no parameters):
22
24
23
25
```js
24
26
let range =newRange();
25
27
```
26
28
27
-
Then we can set the boundaries using `range.setStart(node, offset)` and `range.setEnd(node, offset)`.
29
+
Then we can set the selection boundaries using `range.setStart(node, offset)` and `range.setEnd(node, offset)`.
28
30
29
31
For example, consider this fragment of HTML:
30
32
@@ -123,7 +125,7 @@ E.g. selecting from `1` to `4` gives range `<i>italic</i> and <b>bold</b>`.
123
125
124
126

125
127
126
-
We don't have to use the same node in `setStart` and `setEnd`. A range may span across many unrelated nodes.
128
+
We don't have to use the same node in `setStart` and `setEnd`. A range may span across many unrelated nodes. It's only important that the end is after the start.
127
129
128
130
### Selecting parts of text nodes
129
131
@@ -135,7 +137,7 @@ That's also possible, we just need to set the start and the end as a relative of
135
137
136
138
We need to create a range, that:
137
139
- starts from position 2 in `<p>` first child (taking all but two first letters of "Ex<b>ample:</b> ")
138
-
- ends at the position 3 in `<b>` first child (taking first three letters of "<b>bol</b>d"):
140
+
- ends at the position 3 in `<b>` first child (taking first three letters of "<b>bol</b>d", but no more):
139
141
140
142
```html run
141
143
<pid="p">Example: <i>italic</i> and <b>bold</b></p>
@@ -272,7 +274,7 @@ Here's a screenshot of a selection with 3 ranges, made in Firefox:
272
274
273
275

274
276
275
-
Other browsers support at maximum 1 range per selection. As we'll see, some of `Selection` methods imply that there may be many ranges, but again, in all browsers except Firefox, there's at maximum 1.
277
+
Other browsers support at maximum 1 range. As we'll see, some of `Selection` methods imply that there may be many ranges, but again, in all browsers except Firefox, there's at maximum 1.
276
278
277
279
## Selection properties
278
280
@@ -309,13 +311,12 @@ That's different from `Range` objects that are always directed forward: the rang
309
311
310
312
There are events on to keep track of selection:
311
313
312
-
-`elem.onselectstart` -- when a selection starts.
313
-
- May trigger on any element.
314
-
- Preventing default action makes the selection not start.
315
-
-`document.onselectionchange` -- when a selection changes.
316
-
- Triggers only on `document`.
314
+
-`elem.onselectstart` -- when a selection starts on `elem`, e.g. the user starts moving mouse with pressed button.
315
+
- Preventing the default action makes the selection not start.
316
+
-`document.onselectionchange` -- whenever a selection changes.
317
+
- Please note: this handler can be set only on `document`.
317
318
318
-
## Selection tracking demo
319
+
###Selection tracking demo
319
320
320
321
Here's a small demo that shows selection boundaries
321
322
dynamically as it changes:
@@ -334,6 +335,8 @@ From <input id="from" disabled> – To <input id="to" disabled>
334
335
</script>
335
336
```
336
337
338
+
### Selection getting demo
339
+
337
340
To get the whole selection:
338
341
- As text: just call `document.getSelection().toString()`.
339
342
- As DOM nodes: get the underlying ranges and call their `cloneContents()` method (only first range if we don't support Firefox multiselection).
@@ -374,21 +377,21 @@ Selection methods to add/remove ranges:
374
377
-`removeAllRanges()` -- remove all ranges.
375
378
-`empty()` -- alias to `removeAllRanges`.
376
379
377
-
Also, there are methods to manipulate the selection range directly:
380
+
Also, there are convenience methods to manipulate the selection range directly, without `Range`:
378
381
379
382
-`collapse(node, offset)` -- replace selected range with a new one that starts and ends at the given `node`, at position `offset`.
380
383
-`setPosition(node, offset)` -- alias to `collapse`.
381
384
-`collapseToStart()` - collapse (replace with an empty range) to selection start,
382
385
-`collapseToEnd()` - collapse to selection end,
383
386
-`extend(node, offset)` - move focus of the selection to the given `node`, position `offset`,
384
-
-`setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset)` - replace selection range with the given anchor and focus. All content in-between them is selected.
387
+
-`setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset)` - replace selection range with the given start `anchorNode/anchorOffset`and end `focusNode/focusOffset`. All content in-between them is selected.
385
388
-`selectAllChildren(node)` -- select all children of the `node`.
386
389
-`deleteFromDocument()` -- remove selected content from the document.
387
390
-`containsNode(node, allowPartialContainment = false)` -- checks whether the selection contains `node` (partically if the second argument is `true`)
388
391
389
392
So, for many tasks we can call `Selection` methods, no need to access the underlying `Range` object.
390
393
391
-
For example, selecting the whole contents of the paragraph:
394
+
For example, selecting the whole contents of the paragraph`<p>`:
392
395
393
396
```html run
394
397
<pid="p">Select me: <i>italic</i> and <b>bold</b></p>
@@ -421,31 +424,41 @@ The exception is some selection methods, that replace the existing selection, li
421
424
422
425
## Selection in form controls
423
426
424
-
Form elements, such as `input` and `textarea` provide [API for selection in their values](https://html.spec.whatwg.org/#textFieldSelection).
425
-
426
-
As the value is a pure text, not HTML, these methods to not use `Selection` or `Range` objects, they are much simpler.
427
+
Form elements, such as `input` and `textarea` provide [special API for selection](https://html.spec.whatwg.org/#textFieldSelection), without `Selection` or `Range` objects. As an input value is a pure text, not HTML, there's no need for such objects, everything's much simpler.
427
428
428
-
-`input.select()` -- selects everything in the text control,
429
+
Properties:
429
430
-`input.selectionStart` -- position of selection start (writeable),
430
431
-`input.selectionEnd` -- position of selection start (writeable),
431
-
-`input.selectionDirection` -- direction, one of: "forward", "backward" or "none" (if e.g. selected with a double mouse click),
432
-
-`input.setSelectionRange(start, end, [direction])` -- change the selection to span from `start` till `end`, in the given direction (optional).
432
+
-`input.selectionDirection` -- selection direction, one of: "forward", "backward" or "none" (if e.g. selected with a double mouse click),
433
+
434
+
Events:
435
+
-`input.onselect` -- triggers when something is selected.
436
+
437
+
Methods:
438
+
439
+
-`input.select()` -- selects everything in the text control (can be `textarea` instead of `input`),
440
+
-`input.setSelectionRange(start, end, [direction])` -- change the selection to span from position `start` till `end`, in the given direction (optional).
441
+
-`input.setRangeText(replacement, [start], [end], [selectionMode])` -- replace a range of text with the new text.
433
442
434
-
To modify the content of the selection:
443
+
Optional arguments `start` and `end`, if provided, set the range start and end, otherwise user selection is used.
435
444
436
-
-`input.setRangeText(replacement, [start], [end], [selectionMode])` -- replace a range of text with the new text. If the `start` and `end` arguments are not provided, the range is assumed to be the selection.
445
+
The last argument, `selectionMode`, determines how the selection will be set after the text has been replaced. The possible values are:
437
446
438
-
The last argument, `selectionMode`, determines how the selection will be set after the text has been replaced. The possible values are:
447
+
-`"select"` -- the newly inserted text will be selected.
448
+
-`"start"` -- the selection range collapses just before the inserted text.
449
+
-`"end"` -- the selection range collapses just after the inserted text.
450
+
-`"preserve"` -- attempts to preserve the selection. This is the default.
439
451
440
-
-`"select"` -- the newly inserted text will be selected.
441
-
-`"start"` -- the selection range collapses just before the inserted text.
442
-
-`"end"` -- the selection range collapses just after the inserted text.
443
-
-`"preserve"` -- attempts to preserve the selection. This is the default.
452
+
Now let's see these methods in action.
453
+
454
+
### Example: tracking selection
444
455
445
456
For example, this code uses `onselect` event to track selection:
446
457
447
-
```html run
448
-
<textareaid="area"style="width:80%;height:60px">Select this text</textarea>
458
+
```html run autorun
459
+
<textareaid="area"style="width:80%;height:60px">
460
+
Selecting in this text updates values below.
461
+
</textarea>
449
462
<br>
450
463
From <inputid="from"disabled> – To <inputid="to"disabled>
451
464
@@ -457,23 +470,29 @@ From <input id="from" disabled> – To <input id="to" disabled>
457
470
</script>
458
471
```
459
472
460
-
The `document.onselectionchange` event should not trigger for selections inside a form control, according to the [spec](https://w3c.github.io/selection-api/#dfn-selectionchange), as it's not related to `document` selection and ranges. Some browsers generate it though.
473
+
Please note:
474
+
-`onselect` triggers when something is selected, but not when the selection is removed.
475
+
-`document.onselectionchange` event should not trigger for selections inside a form control, according to the [spec](https://w3c.github.io/selection-api/#dfn-selectionchange), as it's not related to `document` selection and ranges. Some browsers generate it, but we shouldn't rely on it.
461
476
462
-
**When nothing is selected, `selectionStart` and `selectionEnd` both equal the cursor position.**
463
477
464
-
Or, to rephrase, when nothing is selected, the selection is collapsed at cursor position.
478
+
### Example: moving cursor
465
479
466
-
We can use it to move cursor:
480
+
We can change `selectionStart` and `selectionEnd`, that sets the selection.
467
481
468
-
```html run
482
+
An important edge case is when `selectionStart` and `selectionEnd` equal each other. Then it's exactly the cursor position. Or, to rephrase, when nothing is selected, the selection is collapsed at the cursor position.
483
+
484
+
So, by setting `selectionStart` and `selectionEnd` to the same value, we move the cursor.
485
+
486
+
For example:
487
+
488
+
```html run autorun
469
489
<textareaid="area"style="width:80%;height:60px">
470
490
Focus on me, the cursor will be at position 10.
471
491
</textarea>
472
492
473
493
<script>
474
494
area.onfocus= () => {
475
-
// zero delay setTimeout is needed
476
-
// to trigger after browser focus action
495
+
// zero delay setTimeout to run after browser "focus" action finishes
477
496
setTimeout(() => {
478
497
// we can set any selection
479
498
// if start=end, the cursor it exactly at that place
@@ -483,23 +502,67 @@ Focus on me, the cursor will be at position 10.
483
502
</script>
484
503
```
485
504
486
-
...Or to insert something "at the cursor" using `setRangeText`.
505
+
### Example: modifying selection
487
506
488
-
Here's an button that replaces the selection with `"TEXT"` and puts the cursor immediately after it. If the selection is empty, the text is just inserted at the cursor position:
507
+
To modify the content of the selection, we can use `input.setRangeText`. Of course, we can read `selectionStart/End` and can just change `value`, but `setRangeText` is more powerful.
If nothing is selected, or we use equal `start` and `end` in `setRangeText`, then the new text is just inserted, nothing is removed.
551
+
552
+
We can also insert something "at the cursor" using `setRangeText`.
553
+
554
+
Here's an button that inserts `"HELLO"` at the cursor position and puts the cursor immediately after it. If the selection is not empty, then it gets replaced (we can do detect in by comparing `selectionStart=selectionEnd` and do something else instead):
555
+
556
+
```html run autorun
557
+
<inputid="input"style="width:200px"value="Text Text Text Text Text">
558
+
<buttonid="button">Insert "HELLO" at cursor</button>
495
559
496
560
<script>
497
561
button.onclick= () => {
498
-
// replace range with TEXT and collapse the selection at its end
0 commit comments