2012-04-18 15 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、そしていくつかのMOZとMSドキュメント、およびいくつかのPPKのもの以下でこれを実行しようとしました。

アプローチは、基本的には次のとおりです。


  • は、DOMの選択やIEの選択としてSelectionLikeObjectを定義します。

  • RangeLikeObjectをDOM RangeまたはIE TextRangeとして定義します。

  • containerNodeをノードとします。

  • containerElementを要素とします。

  • containedElementsをNodeListとします。

  • elementRangeをRangeLikeObjectとします。

  • selectedRangeをRangeLikeObjectとします。

  • selectedElementsを要素の配列とします。

  • elementを要素とします。

  • selectionをSelectionLikeObjectとします。

  • selectionをユーザーが選択します。

  • selectedElementsを新しい配列に設定します。各selectedRangeselection中の場合

  • selectedRangeの共通の先祖コンテナへ

    • 設定しcontainerNode

    • containerElementを最も近い要素祖先からcontainerNodeに設定します。

    • containerElementの子孫のリストにcontainedElementsを設定します。 containedElementsの各elementに対して

      • 設定elementRangeelementから。

      • selectedRangeの 境界内elementRange秋の境界線の場合:

        • プッシュ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コードはまだテストされていませんが、何か問題があるかどうかを教えてください。

+0

「含む」を定義するのが難しい場合があります。 1つのアプローチは、選択されたテキストからすべての空白を削除し、極端な要素のテキストコンテンツを取得し、その空白を減らしたテキストコンテンツを取得し、選択肢にwholyが含まれているかどうかを確認することです(test、match、トリックをやる)。そうであれば、要素全体が選択されます。そうでない場合、そうではありません。 – RobG

+0

@RobGこれは 'anchorOffset'と' focusOffset'をチェックすることで可能でしょうか?要素のコンテンツ全体が選択されている場合は、「完全に選択された」とみなしたい、そうでない場合は「部分的にしか選択しない」と考えます。ほとんどの場合、開始要素と終了要素の一部が選択されます。実際には、私はいつもそれらを部分的に選択されたものとして扱うことができるかもしれません。 –

+0

@RobG:それは必要ありません。すべての主要なブラウザでは選択範囲を合理的に正確に取得できます。 –

答えて

1

もう一度睡眠をとり、compareBoundaryPointsを読んだ後、私は答えがあると思います。

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

これは、ユーザーの選択内に完全に落ちるの要素についてtrueに評価するようです。

関連する問題