Path: blob/trunk/third_party/closure/goog/dom/selection.js
4111 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview Utilities for working with selections in input boxes and text8* areas.9*10* @see ../demos/dom_selection.html11*/121314goog.provide('goog.dom.selection');1516goog.require('goog.dom.InputType');17goog.require('goog.string');181920/**21* Sets the place where the selection should start inside a textarea or a text22* input23* @param {Element} textfield A textarea or text input.24* @param {number} pos The position to set the start of the selection at.25*/26goog.dom.selection.setStart = function(textfield, pos) {27'use strict';28if (goog.dom.selection.useSelectionProperties_(textfield)) {29/** @suppress {strictMissingProperties} Added to tighten compiler checks */30textfield.selectionStart = pos;31}32};333435/**36* Return the place where the selection starts inside a textarea or a text37* input38* @param {Element} textfield A textarea or text input.39* @return {number} The position where the selection starts or 0 if it was40* unable to find the position or no selection exists. Note that we can't41* reliably tell the difference between an element that has no selection and42* one where it starts at 0.43*/44goog.dom.selection.getStart = function(textfield) {45'use strict';46return goog.dom.selection.getEndPoints_(textfield, true)[0];47};484950/**51* Returns the start and end points of the selection within a textarea in IE.52* IE treats newline characters as \r\n characters, and we need to check for53* these characters at the edge of our selection, to ensure that we return the54* right cursor position.55* @param {TextRange} range Complete range object, e.g., "Hello\r\n".56* @param {TextRange} selRange Selected range object.57* @param {boolean} getOnlyStart Value indicating if only start58* cursor position is to be returned. In IE, obtaining the end position59* involves extra work, hence we have this parameter for calls which need60* only start position.61* @return {!Array<number>} An array with the start and end positions where the62* selection starts and ends or [0,0] if it was unable to find the63* positions or no selection exists. Note that we can't reliably tell the64* difference between an element that has no selection and one where65* it starts and ends at 0. If getOnlyStart was true, we return66* -1 as end offset.67* @private68*/69goog.dom.selection.getEndPointsTextareaIe_ = function(70range, selRange, getOnlyStart) {71'use strict';72// Create a duplicate of the selected range object to perform our actions73// against. Example of selectionRange = "" (assuming that the cursor is74// just after the \r\n combination)75var selectionRange = selRange.duplicate();7677// Text before the selection start, e.g.,"Hello" (notice how range.text78// excludes the \r\n sequence)79var beforeSelectionText = range.text;80// Text before the selection start, e.g., "Hello" (this will later include81// the \r\n sequences also)82var untrimmedBeforeSelectionText = beforeSelectionText;83// Text within the selection , e.g. "" assuming that the cursor is just after84// the \r\n combination.85var selectionText = selectionRange.text;86// Text within the selection, e.g., "" (this will later include the \r\n87// sequences also)88var untrimmedSelectionText = selectionText;8990// Boolean indicating whether we are done dealing with the text before the91// selection's beginning.92var isRangeEndTrimmed = false;93// Go over the range until it becomes a 0-lengthed range or until the range94// text starts changing when we move the end back by one character.95// If after moving the end back by one character, the text remains the same,96// then we need to add a "\r\n" at the end to get the actual text.97while (!isRangeEndTrimmed) {98if (range.compareEndPoints('StartToEnd', range) == 0) {99isRangeEndTrimmed = true;100} else {101range.moveEnd('character', -1);102if (range.text == beforeSelectionText) {103// If the start position of the cursor was after a \r\n string,104// we would skip over it in one go with the moveEnd call, but105// range.text will still show "Hello" (because of the IE range.text106// bug) - this implies that we should add a \r\n to our107// untrimmedBeforeSelectionText string.108untrimmedBeforeSelectionText += '\r\n';109} else {110isRangeEndTrimmed = true;111}112}113}114115if (getOnlyStart) {116// We return -1 as end, since the caller is only interested in the start117// value.118return [untrimmedBeforeSelectionText.length, -1];119}120// Boolean indicating whether we are done dealing with the text inside the121// selection.122var isSelectionRangeEndTrimmed = false;123// Go over the selected range until it becomes a 0-lengthed range or until124// the range text starts changing when we move the end back by one character.125// If after moving the end back by one character, the text remains the same,126// then we need to add a "\r\n" at the end to get the actual text.127while (!isSelectionRangeEndTrimmed) {128if (selectionRange.compareEndPoints('StartToEnd', selectionRange) == 0) {129isSelectionRangeEndTrimmed = true;130} else {131selectionRange.moveEnd('character', -1);132if (selectionRange.text == selectionText) {133// If the selection was not empty, and the end point of the selection134// was just after a \r\n, we would have skipped it in one go with the135// moveEnd call, and this implies that we should add a \r\n to the136// untrimmedSelectionText string.137untrimmedSelectionText += '\r\n';138} else {139isSelectionRangeEndTrimmed = true;140}141}142}143return [144untrimmedBeforeSelectionText.length,145untrimmedBeforeSelectionText.length + untrimmedSelectionText.length146];147};148149150/**151* Returns the start and end points of the selection inside a textarea or a152* text input.153* @param {Element} textfield A textarea or text input.154* @return {!Array<number>} An array with the start and end positions where the155* selection starts and ends or [0,0] if it was unable to find the156* positions or no selection exists. Note that we can't reliably tell the157* difference between an element that has no selection and one where158* it starts and ends at 0.159*/160goog.dom.selection.getEndPoints = function(textfield) {161'use strict';162return goog.dom.selection.getEndPoints_(textfield, false);163};164165166/**167* Returns the start and end points of the selection inside a textarea or a168* text input.169* @param {Element} textfield A textarea or text input.170* @param {boolean} getOnlyStart Value indicating if only start171* cursor position is to be returned. In IE, obtaining the end position172* involves extra work, hence we have this parameter. In FF, there is not173* much extra effort involved.174* @return {!Array<number>} An array with the start and end positions where the175* selection starts and ends or [0,0] if it was unable to find the176* positions or no selection exists. Note that we can't reliably tell the177* difference between an element that has no selection and one where178* it starts and ends at 0. If getOnlyStart was true, we return179* -1 as end offset.180* @private181*/182goog.dom.selection.getEndPoints_ = function(textfield, getOnlyStart) {183'use strict';184textfield = /** @type {!HTMLInputElement|!HTMLTextAreaElement} */ (textfield);185var startPos = 0;186var endPos = 0;187if (goog.dom.selection.useSelectionProperties_(textfield)) {188startPos = textfield.selectionStart;189endPos = getOnlyStart ? -1 : textfield.selectionEnd;190}191return [startPos, endPos];192};193194195/**196* Sets the place where the selection should end inside a text area or a text197* input198* @param {Element} textfield A textarea or text input.199* @param {number} pos The position to end the selection at.200*/201goog.dom.selection.setEnd = function(textfield, pos) {202'use strict';203if (goog.dom.selection.useSelectionProperties_(textfield)) {204/** @suppress {strictMissingProperties} Added to tighten compiler checks */205textfield.selectionEnd = pos;206}207};208209210/**211* Returns the place where the selection ends inside a textarea or a text input212* @param {Element} textfield A textarea or text input.213* @return {number} The position where the selection ends or 0 if it was214* unable to find the position or no selection exists.215*/216goog.dom.selection.getEnd = function(textfield) {217'use strict';218return goog.dom.selection.getEndPoints_(textfield, false)[1];219};220221222/**223* Sets the cursor position within a textfield.224* @param {Element} textfield A textarea or text input.225* @param {number} pos The position within the text field.226*/227goog.dom.selection.setCursorPosition = function(textfield, pos) {228'use strict';229if (goog.dom.selection.useSelectionProperties_(textfield)) {230// Mozilla directly supports this231/** @suppress {strictMissingProperties} Added to tighten compiler checks */232textfield.selectionStart = pos;233/** @suppress {strictMissingProperties} Added to tighten compiler checks */234textfield.selectionEnd = pos;235}236};237238239/**240* Sets the selected text inside a textarea or a text input241* @param {Element} textfield A textarea or text input.242* @param {string} text The text to change the selection to.243*/244goog.dom.selection.setText = function(textfield, text) {245'use strict';246textfield = /** @type {!HTMLInputElement|!HTMLTextAreaElement} */ (textfield);247if (goog.dom.selection.useSelectionProperties_(textfield)) {248var value = textfield.value;249var oldSelectionStart = textfield.selectionStart;250var before = value.slice(0, oldSelectionStart);251var after = value.slice(textfield.selectionEnd);252textfield.value = before + text + after;253textfield.selectionStart = oldSelectionStart;254textfield.selectionEnd = oldSelectionStart + text.length;255} else {256throw new Error('Cannot set the selection end');257}258};259260261/**262* Returns the selected text inside a textarea or a text input263* @param {Element} textfield A textarea or text input.264* @return {string} The selected text.265*/266goog.dom.selection.getText = function(textfield) {267'use strict';268textfield = /** @type {!HTMLInputElement|!HTMLTextAreaElement} */ (textfield);269if (goog.dom.selection.useSelectionProperties_(textfield)) {270var s = textfield.value;271return s.substring(textfield.selectionStart, textfield.selectionEnd);272}273274throw new Error('Cannot get the selection text');275};276277278/**279* Returns the selected text within a textarea in IE.280* IE treats newline characters as \r\n characters, and we need to check for281* these characters at the edge of our selection, to ensure that we return the282* right string.283* @param {TextRange} selRange Selected range object.284* @return {string} Selected text in the textarea.285* @private286*/287goog.dom.selection.getSelectionRangeText_ = function(selRange) {288'use strict';289// Create a duplicate of the selected range object to perform our actions290// against. Suppose the text in the textarea is "Hello\r\nWorld" and the291// selection encompasses the "o\r\n" bit, initial selectionRange will be "o"292// (assuming that the cursor is just after the \r\n combination)293var selectionRange = selRange.duplicate();294295// Text within the selection , e.g. "o" assuming that the cursor is just after296// the \r\n combination.297var selectionText = selectionRange.text;298// Text within the selection, e.g., "o" (this will later include the \r\n299// sequences also)300var untrimmedSelectionText = selectionText;301302// Boolean indicating whether we are done dealing with the text inside the303// selection.304var isSelectionRangeEndTrimmed = false;305// Go over the selected range until it becomes a 0-lengthed range or until306// the range text starts changing when we move the end back by one character.307// If after moving the end back by one character, the text remains the same,308// then we need to add a "\r\n" at the end to get the actual text.309while (!isSelectionRangeEndTrimmed) {310if (selectionRange.compareEndPoints('StartToEnd', selectionRange) == 0) {311isSelectionRangeEndTrimmed = true;312} else {313selectionRange.moveEnd('character', -1);314if (selectionRange.text == selectionText) {315// If the selection was not empty, and the end point of the selection316// was just after a \r\n, we would have skipped it in one go with the317// moveEnd call, and this implies that we should add a \r\n to the318// untrimmedSelectionText string.319untrimmedSelectionText += '\r\n';320} else {321isSelectionRangeEndTrimmed = true;322}323}324}325return untrimmedSelectionText;326};327328329/**330* Helper function for returning the range for an object as well as the331* selection range332* @private333* @param {Element} el The element to get the range for.334* @return {!Array<TextRange>} Range of object and selection range in two335* element array.336*/337goog.dom.selection.getRangeIe_ = function(el) {338'use strict';339var doc = el.ownerDocument || el.document;340341var selectionRange = doc.selection.createRange();342// el.createTextRange() doesn't work on textareas343var range;344345if (/** @type {?} */ (el).type == goog.dom.InputType.TEXTAREA) {346range = doc.body.createTextRange();347range.moveToElementText(el);348} else {349range = el.createTextRange();350}351352return [range, selectionRange];353};354355356/**357* Helper function for canonicalizing a position inside a textfield in IE.358* Deals with the issue that \r\n counts as 2 characters, but359* move('character', n) passes over both characters in one move.360* @private361* @param {Element} textfield The text element.362* @param {number} pos The position desired in that element.363* @return {number} The canonicalized position that will work properly with364* move('character', pos).365*/366goog.dom.selection.canonicalizePositionIe_ = function(textfield, pos) {367'use strict';368textfield = /** @type {!HTMLTextAreaElement} */ (textfield);369if (textfield.type == goog.dom.InputType.TEXTAREA) {370// We do this only for textarea because it is the only one which can371// have a \r\n (input cannot have this).372var value = textfield.value.substring(0, pos);373pos = goog.string.canonicalizeNewlines(value).length;374}375return pos;376};377378379/**380* Helper function to determine whether it's okay to use381* selectionStart/selectionEnd.382*383* @param {Element} el The element to check for.384* @return {boolean} Whether it's okay to use the selectionStart and385* selectionEnd properties on `el`.386* @private387*/388goog.dom.selection.useSelectionProperties_ = function(el) {389'use strict';390try {391return typeof el.selectionStart == 'number';392} catch (e) {393// Firefox throws an exception if you try to access selectionStart394// on an element with display: none.395return false;396}397};398399400