ユーザー選択に含まれるすべての要素を取得したい (DOM 2 範囲/MS TextRanges のように)。
/** @return {Array.<Element>} */
function getSelectedElements() {
var elements = [];
// get elements in the user selection somehow
return elements;
}
同様の質問に対するTim Downの優れた解決策、およびいくつかのMozとMSのドキュメント、およびいくつかのPPKのものに従って、これを実行しようとしました。
アプローチは基本的に次のとおりです。
SelectionLikeObject を DOM 選択または IE 選択として定義します。
RangeLikeObject を DOM Range または IE TextRange として定義します。
containerNode
ノードにしましょう。containerElement
要素にしましょう。NodeList
containedElements
にします。RangeLikeObject
elementRange
にします。RangeLikeObject
selectedRange
にします。selectedElements
要素の配列としましょう。element
要素にしましょう。SelectionLikeObject
selection
にします。selection
ユーザーの選択から設定します。selectedElements
新しい配列に設定します。のそれぞれ
selectedRange
についてselection
:containerNode
の共通の祖先コンテナに設定しselectedRange
ます。containerElement
に最も近い Element の祖先に設定しcontainerNode
ます。containedElements
の子孫のリストに設定しcontainerElement
ます。のそれぞれ
element
についてcontainedElements
:から設定
elementRange
しelement
ます。の境界が の
elementRange
境界内にある場合selectedRange
:- に押し込み
element
ますselectedElements
。
- に押し込み
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;
};
ここで、解決したい問題は次のとおりです。要素内のテキストの一部のみが選択されている場合、部分的にしか選択されていないにもかかわらず、返された配列に表示されます。
完全に選択された要素のみを含めるように動作を変更するパラメーターを追加したいと思います。答えはcompareBoundaryPointsにあると感じていますが、まだ理解できるほどよく理解していません。
また、IE コードは今のところテストされていませんが、それ (または DOM ブランチ) に問題があるように見える場合はお知らせください。