Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/dom/selection.js
2868 views
1
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview Utilities for working with selections in input boxes and text
17
* areas.
18
*
19
* @author [email protected] (Erik Arvidsson)
20
* @see ../demos/dom_selection.html
21
*/
22
23
24
goog.provide('goog.dom.selection');
25
26
goog.require('goog.dom.InputType');
27
goog.require('goog.string');
28
goog.require('goog.userAgent');
29
30
31
/**
32
* Sets the place where the selection should start inside a textarea or a text
33
* input
34
* @param {Element} textfield A textarea or text input.
35
* @param {number} pos The position to set the start of the selection at.
36
*/
37
goog.dom.selection.setStart = function(textfield, pos) {
38
if (goog.dom.selection.useSelectionProperties_(textfield)) {
39
textfield.selectionStart = pos;
40
} else if (goog.dom.selection.isLegacyIe_()) {
41
// destructuring assignment would have been sweet
42
var tmp = goog.dom.selection.getRangeIe_(textfield);
43
var range = tmp[0];
44
var selectionRange = tmp[1];
45
46
if (range.inRange(selectionRange)) {
47
pos = goog.dom.selection.canonicalizePositionIe_(textfield, pos);
48
49
range.collapse(true);
50
range.move('character', pos);
51
range.select();
52
}
53
}
54
};
55
56
57
/**
58
* Return the place where the selection starts inside a textarea or a text
59
* input
60
* @param {Element} textfield A textarea or text input.
61
* @return {number} The position where the selection starts or 0 if it was
62
* unable to find the position or no selection exists. Note that we can't
63
* reliably tell the difference between an element that has no selection and
64
* one where it starts at 0.
65
*/
66
goog.dom.selection.getStart = function(textfield) {
67
return goog.dom.selection.getEndPoints_(textfield, true)[0];
68
};
69
70
71
/**
72
* Returns the start and end points of the selection within a textarea in IE.
73
* IE treats newline characters as \r\n characters, and we need to check for
74
* these characters at the edge of our selection, to ensure that we return the
75
* right cursor position.
76
* @param {TextRange} range Complete range object, e.g., "Hello\r\n".
77
* @param {TextRange} selRange Selected range object.
78
* @param {boolean} getOnlyStart Value indicating if only start
79
* cursor position is to be returned. In IE, obtaining the end position
80
* involves extra work, hence we have this parameter for calls which need
81
* only start position.
82
* @return {!Array<number>} An array with the start and end positions where the
83
* selection starts and ends or [0,0] if it was unable to find the
84
* positions or no selection exists. Note that we can't reliably tell the
85
* difference between an element that has no selection and one where
86
* it starts and ends at 0. If getOnlyStart was true, we return
87
* -1 as end offset.
88
* @private
89
*/
90
goog.dom.selection.getEndPointsTextareaIe_ = function(
91
range, selRange, getOnlyStart) {
92
// Create a duplicate of the selected range object to perform our actions
93
// against. Example of selectionRange = "" (assuming that the cursor is
94
// just after the \r\n combination)
95
var selectionRange = selRange.duplicate();
96
97
// Text before the selection start, e.g.,"Hello" (notice how range.text
98
// excludes the \r\n sequence)
99
var beforeSelectionText = range.text;
100
// Text before the selection start, e.g., "Hello" (this will later include
101
// the \r\n sequences also)
102
var untrimmedBeforeSelectionText = beforeSelectionText;
103
// Text within the selection , e.g. "" assuming that the cursor is just after
104
// the \r\n combination.
105
var selectionText = selectionRange.text;
106
// Text within the selection, e.g., "" (this will later include the \r\n
107
// sequences also)
108
var untrimmedSelectionText = selectionText;
109
110
// Boolean indicating whether we are done dealing with the text before the
111
// selection's beginning.
112
var isRangeEndTrimmed = false;
113
// Go over the range until it becomes a 0-lengthed range or until the range
114
// text starts changing when we move the end back by one character.
115
// If after moving the end back by one character, the text remains the same,
116
// then we need to add a "\r\n" at the end to get the actual text.
117
while (!isRangeEndTrimmed) {
118
if (range.compareEndPoints('StartToEnd', range) == 0) {
119
isRangeEndTrimmed = true;
120
} else {
121
range.moveEnd('character', -1);
122
if (range.text == beforeSelectionText) {
123
// If the start position of the cursor was after a \r\n string,
124
// we would skip over it in one go with the moveEnd call, but
125
// range.text will still show "Hello" (because of the IE range.text
126
// bug) - this implies that we should add a \r\n to our
127
// untrimmedBeforeSelectionText string.
128
untrimmedBeforeSelectionText += '\r\n';
129
} else {
130
isRangeEndTrimmed = true;
131
}
132
}
133
}
134
135
if (getOnlyStart) {
136
// We return -1 as end, since the caller is only interested in the start
137
// value.
138
return [untrimmedBeforeSelectionText.length, -1];
139
}
140
// Boolean indicating whether we are done dealing with the text inside the
141
// selection.
142
var isSelectionRangeEndTrimmed = false;
143
// Go over the selected range until it becomes a 0-lengthed range or until
144
// the range text starts changing when we move the end back by one character.
145
// If after moving the end back by one character, the text remains the same,
146
// then we need to add a "\r\n" at the end to get the actual text.
147
while (!isSelectionRangeEndTrimmed) {
148
if (selectionRange.compareEndPoints('StartToEnd', selectionRange) == 0) {
149
isSelectionRangeEndTrimmed = true;
150
} else {
151
selectionRange.moveEnd('character', -1);
152
if (selectionRange.text == selectionText) {
153
// If the selection was not empty, and the end point of the selection
154
// was just after a \r\n, we would have skipped it in one go with the
155
// moveEnd call, and this implies that we should add a \r\n to the
156
// untrimmedSelectionText string.
157
untrimmedSelectionText += '\r\n';
158
} else {
159
isSelectionRangeEndTrimmed = true;
160
}
161
}
162
}
163
return [
164
untrimmedBeforeSelectionText.length,
165
untrimmedBeforeSelectionText.length + untrimmedSelectionText.length
166
];
167
};
168
169
170
/**
171
* Returns the start and end points of the selection inside a textarea or a
172
* text input.
173
* @param {Element} textfield A textarea or text input.
174
* @return {!Array<number>} An array with the start and end positions where the
175
* selection starts and ends or [0,0] if it was unable to find the
176
* positions or no selection exists. Note that we can't reliably tell the
177
* difference between an element that has no selection and one where
178
* it starts and ends at 0.
179
*/
180
goog.dom.selection.getEndPoints = function(textfield) {
181
return goog.dom.selection.getEndPoints_(textfield, false);
182
};
183
184
185
/**
186
* Returns the start and end points of the selection inside a textarea or a
187
* text input.
188
* @param {Element} textfield A textarea or text input.
189
* @param {boolean} getOnlyStart Value indicating if only start
190
* cursor position is to be returned. In IE, obtaining the end position
191
* involves extra work, hence we have this parameter. In FF, there is not
192
* much extra effort involved.
193
* @return {!Array<number>} An array with the start and end positions where the
194
* selection starts and ends or [0,0] if it was unable to find the
195
* positions or no selection exists. Note that we can't reliably tell the
196
* difference between an element that has no selection and one where
197
* it starts and ends at 0. If getOnlyStart was true, we return
198
* -1 as end offset.
199
* @private
200
*/
201
goog.dom.selection.getEndPoints_ = function(textfield, getOnlyStart) {
202
textfield = /** @type {!HTMLInputElement|!HTMLTextAreaElement} */ (textfield);
203
var startPos = 0;
204
var endPos = 0;
205
if (goog.dom.selection.useSelectionProperties_(textfield)) {
206
startPos = textfield.selectionStart;
207
endPos = getOnlyStart ? -1 : textfield.selectionEnd;
208
} else if (goog.dom.selection.isLegacyIe_()) {
209
var tmp = goog.dom.selection.getRangeIe_(textfield);
210
var range = tmp[0];
211
var selectionRange = tmp[1];
212
213
if (range.inRange(selectionRange)) {
214
range.setEndPoint('EndToStart', selectionRange);
215
if (textfield.type == goog.dom.InputType.TEXTAREA) {
216
return goog.dom.selection.getEndPointsTextareaIe_(
217
range, selectionRange, getOnlyStart);
218
}
219
startPos = range.text.length;
220
if (!getOnlyStart) {
221
endPos = range.text.length + selectionRange.text.length;
222
} else {
223
endPos = -1; // caller did not ask for end position
224
}
225
}
226
}
227
return [startPos, endPos];
228
};
229
230
231
/**
232
* Sets the place where the selection should end inside a text area or a text
233
* input
234
* @param {Element} textfield A textarea or text input.
235
* @param {number} pos The position to end the selection at.
236
*/
237
goog.dom.selection.setEnd = function(textfield, pos) {
238
if (goog.dom.selection.useSelectionProperties_(textfield)) {
239
textfield.selectionEnd = pos;
240
} else if (goog.dom.selection.isLegacyIe_()) {
241
var tmp = goog.dom.selection.getRangeIe_(textfield);
242
var range = tmp[0];
243
var selectionRange = tmp[1];
244
245
if (range.inRange(selectionRange)) {
246
// Both the current position and the start cursor position need
247
// to be canonicalized to take care of possible \r\n miscounts.
248
pos = goog.dom.selection.canonicalizePositionIe_(textfield, pos);
249
var startCursorPos = goog.dom.selection.canonicalizePositionIe_(
250
textfield, goog.dom.selection.getStart(textfield));
251
252
selectionRange.collapse(true);
253
selectionRange.moveEnd('character', pos - startCursorPos);
254
selectionRange.select();
255
}
256
}
257
};
258
259
260
/**
261
* Returns the place where the selection ends inside a textarea or a text input
262
* @param {Element} textfield A textarea or text input.
263
* @return {number} The position where the selection ends or 0 if it was
264
* unable to find the position or no selection exists.
265
*/
266
goog.dom.selection.getEnd = function(textfield) {
267
return goog.dom.selection.getEndPoints_(textfield, false)[1];
268
};
269
270
271
/**
272
* Sets the cursor position within a textfield.
273
* @param {Element} textfield A textarea or text input.
274
* @param {number} pos The position within the text field.
275
*/
276
goog.dom.selection.setCursorPosition = function(textfield, pos) {
277
if (goog.dom.selection.useSelectionProperties_(textfield)) {
278
// Mozilla directly supports this
279
textfield.selectionStart = pos;
280
textfield.selectionEnd = pos;
281
282
} else if (goog.dom.selection.isLegacyIe_()) {
283
pos = goog.dom.selection.canonicalizePositionIe_(textfield, pos);
284
285
// IE has textranges. A textfield's textrange encompasses the
286
// entire textfield's text by default
287
var sel = textfield.createTextRange();
288
289
sel.collapse(true);
290
sel.move('character', pos);
291
sel.select();
292
}
293
};
294
295
296
/**
297
* Sets the selected text inside a textarea or a text input
298
* @param {Element} textfield A textarea or text input.
299
* @param {string} text The text to change the selection to.
300
*/
301
goog.dom.selection.setText = function(textfield, text) {
302
textfield = /** @type {!HTMLInputElement|!HTMLTextAreaElement} */ (textfield);
303
if (goog.dom.selection.useSelectionProperties_(textfield)) {
304
var value = textfield.value;
305
var oldSelectionStart = textfield.selectionStart;
306
var before = value.substr(0, oldSelectionStart);
307
var after = value.substr(textfield.selectionEnd);
308
textfield.value = before + text + after;
309
textfield.selectionStart = oldSelectionStart;
310
textfield.selectionEnd = oldSelectionStart + text.length;
311
} else if (goog.dom.selection.isLegacyIe_()) {
312
var tmp = goog.dom.selection.getRangeIe_(textfield);
313
var range = tmp[0];
314
var selectionRange = tmp[1];
315
316
if (!range.inRange(selectionRange)) {
317
return;
318
}
319
// When we set the selection text the selection range is collapsed to the
320
// end. We therefore duplicate the current selection so we know where it
321
// started. Once we've set the selection text we move the start of the
322
// selection range to the old start
323
var range2 = selectionRange.duplicate();
324
selectionRange.text = text;
325
selectionRange.setEndPoint('StartToStart', range2);
326
selectionRange.select();
327
} else {
328
throw Error('Cannot set the selection end');
329
}
330
};
331
332
333
/**
334
* Returns the selected text inside a textarea or a text input
335
* @param {Element} textfield A textarea or text input.
336
* @return {string} The selected text.
337
*/
338
goog.dom.selection.getText = function(textfield) {
339
textfield = /** @type {!HTMLInputElement|!HTMLTextAreaElement} */ (textfield);
340
if (goog.dom.selection.useSelectionProperties_(textfield)) {
341
var s = textfield.value;
342
return s.substring(textfield.selectionStart, textfield.selectionEnd);
343
}
344
345
if (goog.dom.selection.isLegacyIe_()) {
346
var tmp = goog.dom.selection.getRangeIe_(textfield);
347
var range = tmp[0];
348
var selectionRange = tmp[1];
349
350
if (!range.inRange(selectionRange)) {
351
return '';
352
} else if (textfield.type == goog.dom.InputType.TEXTAREA) {
353
return goog.dom.selection.getSelectionRangeText_(selectionRange);
354
}
355
return selectionRange.text;
356
}
357
358
throw Error('Cannot get the selection text');
359
};
360
361
362
/**
363
* Returns the selected text within a textarea in IE.
364
* IE treats newline characters as \r\n characters, and we need to check for
365
* these characters at the edge of our selection, to ensure that we return the
366
* right string.
367
* @param {TextRange} selRange Selected range object.
368
* @return {string} Selected text in the textarea.
369
* @private
370
*/
371
goog.dom.selection.getSelectionRangeText_ = function(selRange) {
372
// Create a duplicate of the selected range object to perform our actions
373
// against. Suppose the text in the textarea is "Hello\r\nWorld" and the
374
// selection encompasses the "o\r\n" bit, initial selectionRange will be "o"
375
// (assuming that the cursor is just after the \r\n combination)
376
var selectionRange = selRange.duplicate();
377
378
// Text within the selection , e.g. "o" assuming that the cursor is just after
379
// the \r\n combination.
380
var selectionText = selectionRange.text;
381
// Text within the selection, e.g., "o" (this will later include the \r\n
382
// sequences also)
383
var untrimmedSelectionText = selectionText;
384
385
// Boolean indicating whether we are done dealing with the text inside the
386
// selection.
387
var isSelectionRangeEndTrimmed = false;
388
// Go over the selected range until it becomes a 0-lengthed range or until
389
// the range text starts changing when we move the end back by one character.
390
// If after moving the end back by one character, the text remains the same,
391
// then we need to add a "\r\n" at the end to get the actual text.
392
while (!isSelectionRangeEndTrimmed) {
393
if (selectionRange.compareEndPoints('StartToEnd', selectionRange) == 0) {
394
isSelectionRangeEndTrimmed = true;
395
} else {
396
selectionRange.moveEnd('character', -1);
397
if (selectionRange.text == selectionText) {
398
// If the selection was not empty, and the end point of the selection
399
// was just after a \r\n, we would have skipped it in one go with the
400
// moveEnd call, and this implies that we should add a \r\n to the
401
// untrimmedSelectionText string.
402
untrimmedSelectionText += '\r\n';
403
} else {
404
isSelectionRangeEndTrimmed = true;
405
}
406
}
407
}
408
return untrimmedSelectionText;
409
};
410
411
412
/**
413
* Helper function for returning the range for an object as well as the
414
* selection range
415
* @private
416
* @param {Element} el The element to get the range for.
417
* @return {!Array<TextRange>} Range of object and selection range in two
418
* element array.
419
*/
420
goog.dom.selection.getRangeIe_ = function(el) {
421
var doc = el.ownerDocument || el.document;
422
423
var selectionRange = doc.selection.createRange();
424
// el.createTextRange() doesn't work on textareas
425
var range;
426
427
if (/** @type {?} */ (el).type == goog.dom.InputType.TEXTAREA) {
428
range = doc.body.createTextRange();
429
range.moveToElementText(el);
430
} else {
431
range = el.createTextRange();
432
}
433
434
return [range, selectionRange];
435
};
436
437
438
/**
439
* Helper function for canonicalizing a position inside a textfield in IE.
440
* Deals with the issue that \r\n counts as 2 characters, but
441
* move('character', n) passes over both characters in one move.
442
* @private
443
* @param {Element} textfield The text element.
444
* @param {number} pos The position desired in that element.
445
* @return {number} The canonicalized position that will work properly with
446
* move('character', pos).
447
*/
448
goog.dom.selection.canonicalizePositionIe_ = function(textfield, pos) {
449
textfield = /** @type {!HTMLTextAreaElement} */ (textfield);
450
if (textfield.type == goog.dom.InputType.TEXTAREA) {
451
// We do this only for textarea because it is the only one which can
452
// have a \r\n (input cannot have this).
453
var value = textfield.value.substring(0, pos);
454
pos = goog.string.canonicalizeNewlines(value).length;
455
}
456
return pos;
457
};
458
459
460
/**
461
* Helper function to determine whether it's okay to use
462
* selectionStart/selectionEnd.
463
*
464
* @param {Element} el The element to check for.
465
* @return {boolean} Whether it's okay to use the selectionStart and
466
* selectionEnd properties on {@code el}.
467
* @private
468
*/
469
goog.dom.selection.useSelectionProperties_ = function(el) {
470
try {
471
return typeof el.selectionStart == 'number';
472
} catch (e) {
473
// Firefox throws an exception if you try to access selectionStart
474
// on an element with display: none.
475
return false;
476
}
477
};
478
479
480
/**
481
* Whether the client is legacy IE which does not support
482
* selectionStart/selectionEnd properties of a text input element.
483
*
484
* @see https://msdn.microsoft.com/en-us/library/ff974768(v=vs.85).aspx
485
*
486
* @return {boolean} Whether the client is a legacy version of IE.
487
* @private
488
*/
489
goog.dom.selection.isLegacyIe_ = function() {
490
return goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9');
491
};
492
493