1

我想获取用户选择中包含的所有元素(如 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成为一个元素。

  • 让我们containedElements成为一个 NodeList。

  • 让我们elementRange成为一个 RangeLikeObject。

  • 让我们selectedRange成为一个 RangeLikeObject。

  • 让我们selectedElements成为一个元素数组。

  • 让我们element成为一个元素。

  • 让我们selection成为一个 SelectionLikeObject。

  • selection从用户的选择中设置。

  • 设置selectedElements为新数组。

  • For each selectedRange in selection:

    • Set containerNode to the common ancestor container of selectedRange.

    • Set containerElement to the closest Element ancestor to containerNode.

    • Set containedElements to a list of descendants of containerElement.

    • For each element in containedElements:

      • Set elementRange from element.

      • If the boundaries of elementRange fall within the boundaries of selectedRange:

        • Push element onto selectedElements.

The DOM branch looks like this:

/** 
    @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;

};

The IE branch looks like this:

/** 
    @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;

};

Now, the issue I want to solve is this: if only part of the text in an element is selected, it appears in the returned array, even though it is only partly selected.

I'd like to add a parameter that changes the behavior to only include fully-selected elements. I have a feeling the answer lies with compareBoundaryPoints, I just don't understand it well enough to figure it out yet.

Also, the IE code is untested so far, but please let me know if anything looks wrong with it (or the DOM branch).

4

1 回答 1

1

After getting some sleep and reading up on compareBoundaryPoints again, I think I have the answer.

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

This seems to only evaluate to true for elements that fall completely within the user selection.

于 2012-04-18T19:37:01.093 回答