2012-04-18 94 views
1

我想獲取用戶選擇中包含的所有元素(如在DOM 2範圍/ MS TextRanges中)。獲取文本選擇中的元素

/** @return {Array.<Element>} */ 
function getSelectedElements() { 

    var elements = []; 

    // get elements in the user selection somehow 

    return elements; 

} 

我試着按照蒂姆唐氏優秀solution to a similar question,有的萬盎司和MS文檔,有些PPK東西做到這一點。

該方法基本上是:


  • 定義SelectionLikeObject作爲DOM選擇或IE選擇。

  • 將RangeLikeObject定義爲DOM範圍或IE TextRange。

  • containerNode成爲節點。

  • containerElement成爲元素。

  • containedElements成爲NodeList。

  • elementRange成爲RangeLikeObject。

  • selectedRange成爲RangeLikeObject。

  • selectedElements成爲一個元素數組。

  • element成爲元素。

  • selection成爲SelectionLikeObject。

  • 從用戶的選擇中設置selection

  • selectedElements設置爲新的數組。

  • 對於每個selectedRangeselection

    • 設置containerNode到的selectedRange共同祖先容器。

    • containerElement設置爲最接近的元素祖先到containerNode

    • containedElements設置爲containerElement的後代列表。

    • 對於每個elementcontainedElements

      • element設置elementRange

      • 如果elementRange下降selectedRange的 邊界內的邊界:

        • elementselectedElements

的DOM分支看起來是這樣的:

/** 
    @param {Document} doc 
    @return {Array.<Element>} 
*/ 
getSelectedElements.fromDOM = function (doc) { 

    /** @type {Range} */ 
    var selectedRange; 

    /** @type {Array.<Element>} */ 
    var selectedElements = []; 

    /** @type {Node} */ 
    var containerNode; 

    /** @type {Element} */ 
    var containerElement; 

    /** @type {NodeList} */ 
    var containedElements; 

    /** @type {Range} */ 
    var elementRange; 

    /** @type {Element} */ 
    var element; 

    /** @type {Selection} */ 
    var selection = doc.defaultView.getSelection(); 

    /** @type {number} */ 
    var rangeCount = selection.rangeCount; 

    /** @type {number} */ 
    var elementCount; 

    /** @type {number} */ 
    var i; 

    // hack for browsers without getRangeAt 
    // see http://www.quirksmode.org/dom/range_intro.html 

    if (!selection.getRangeAt) { 

    selection.getRangeAt = function (i) { 
     /** @type {Range} */ 
     var range = doc.createRange(); 
     if (i || !selection.anchorNode) { 
     return range; 
     } 
     range.setStart(selection.anchorNode, selection.anchorOffset); 
     range.setEnd(selection.focusNode, selection.focusOffset); 
     return range; 

    }; 

    selection.rangeCount = 1; 

    } 

    elementRange = doc.createRange(); 

    for (i = 0; i < rangeCount; ++i) { 

    selectedRange = selection.getRangeAt(i); 

    containerNode = selectedRange.commonAncestorContainer; 

    while (containerNode && containerNode.nodeType != 1) { 

     containerNode = containerNode.parentNode; 

    } 

    if (!containerNode) { 

     return selectedElements; // something went wrong... 

    } 

    containerElement = /** @type {Element} */ containerNode; 

    containedElements = containerElement.getElementsByTagName('*'); 

    elementCount = containedElements.length; 

    for (var i = 0; i < elementCount; ++i) { 

     element = containedElements[i]; 

     elementRange.selectNodeContents(element); 

     if (elementRange.compareBoundaryPoints(selectedRange.END_TO_START, selectedRange) < 1 && 
      elementRange.compareBoundaryPoints(selectedRange.START_TO_END, selectedRange) > -1) { 

     selectedElements.push(element); 

     } 
    } 
    } 

    elementRange.detach(); 

    return selectedElements; 

}; 

的IE分支看起來是這樣的:

/** 
    @param {Document} doc 
    @return {Array.<Element>} 
*/ 
getSelectedElements.fromIE = function (doc) { 

    // Selection - http://msdn.microsoft.com/en-us/library/ie/dd347133(v=vs.85).aspx 
    // TextRange - http://msdn.microsoft.com/en-us/library/dd347140(v=vs.85).aspx 
    // ControlRange - http://msdn.microsoft.com/en-us/library/ie/ms537447(v=vs.85).aspx 

    /** @type {TextRange|ControlRange} */ 
    var ieRange = doc.selection && doc.selection.createRange(); 

    /** @type {Array.<Element>} */ 
    var selectedElements = []; 

    /** @type {TextRange} */ 
    var selectedRange; 

    /** @type {Element} */ 
    var containerElement; 

    /** @type {NodeList} */ 
    var containedElements; 

    /** @type {TextRange} */ 
    var elementRange; 

    /** @type {Element} */ 
    var element; 

    /** @type {Selection} */ 
    var selection; 

    /** @type {number} */ 
    var i = -1; 


    if (ieRange.text === void 0) { 

    return []; // FIXME: It's a ControlRange, give up. 

    } 

    selectedRange = /** @type {TextRange} */ ieRange; 

    containerElement = selectedRange.parentElement(); 

    containedElements = containerElement.getElementsByTagName('*'); 

    elementRange = doc.body.createTextRange(); 

    while ((element = containedElements[++i])) { 

     elementRange.moveToElementText(element); 

     if (elementRange.compareEndPoints("StartToEnd", selectedRange) > -1 && 
      elementRange.compareEndPoints("EndToStart", selectedRange) < 1) { 

     selectedElements.push(element); 

     } 
    } 

    return /** @type {Array.<Element>} */ selectedElements; 

}; 

現在,我想要解決的問題是:如果只選擇元素中的部分文本,它將出現在返回的數組中,即使它僅爲partly selected

我想添加一個參數,改變行爲只包含完全選定的元素。我有一種感覺,答案在於compareBoundaryPoints,我現在還不明白它的意義。另外,IE代碼目前尚未測試,但請讓我知道是否有任何東西看起來有問題(或DOM分支)。

+0

您可能難以定義「包含」。一種方法可能是從所選文本中刪除所有空格,然後獲取文本內容以獲取極端元素的文本內容,並獲取其空格縮小的文本內容,並查看它是否包含在選擇內容中(測試,匹配或索引應該做的伎倆)。如果是,則選擇整個元素。如果不是,那不是。 – RobG 2012-04-18 03:43:55

+0

@RobG也許可以通過檢查'anchorOffset'和'focusOffset'完成嗎?如果選擇了一個元素的全部內容,我想將其視爲「完全選定」,否則只是「部分選定」。在大多數情況下,開始和結束元素將被部分選中。事實上,也許我總是可以把它們當作部分選擇的...... – 2012-04-18 03:54:57

+0

@RobG:沒有必要:你可以在所有主流瀏覽器中獲得合理精確的界限。 – 2012-04-18 08:28:03

回答

1

經過一段時間的睡眠後再次讀到compareBoundaryPoints,我想我已經有了答案。

if (elementRange.compareBoundaryPoints(Range.START_TO_START, selectedRange) > -1 && 
    elementRange.compareBoundaryPoints(Range.END_TO_END, selectedRange) < 1) { 

這似乎只能評估到true爲用戶選擇中完全落元素。