Skip to content

Instantly share code, notes, and snippets.

@gitbuh
Created April 18, 2012 21:32
Show Gist options
  • Save gitbuh/2416742 to your computer and use it in GitHub Desktop.
Save gitbuh/2416742 to your computer and use it in GitHub Desktop.
Get elements in user selection
/*
Get elements in user selection.
- Define SelectionLikeObject as a DOM Selection or an IE Selection.
- Define RangeLikeObject as a DOM Range or an IE TextRange.
- Let `containerNode` be a Node.
- Let `containerElement` be an Element.
- Let `containedElements` be a NodeList.
- Let `elementRange` be a RangeLikeObject.
- Let `selectedRange` be a RangeLikeObject.
- Let `selectedElements` be an Array of Elements.
- Let `element` be an Element.
- Let `selection` be a SelectionLikeObject.
- Set `selection` from the user's selection.
- Set `selectedElements` to a new Array.
- 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`.
- Return `selectedElements`.
@fileinfo
*/
/**
Get elements that fall within the user's selection.
@param {boolean} includePartial
Optionally include partially selected elements.
@param {Window} win
Optional window or frame to select elements from.
@return {Array.<Element>}
*/
function getSelectedElements (includePartial, win) {
return getSelectedElements.fromHost(
includePartial, win || getSelectedElements.mainWindow
);
}
/** @type {Window} */
getSelectedElements.mainWindow = (function(){ return this || [eval][0]('this'); }());
/**
DOM branch
@param {boolean} includePartial
@param {Window} win
@return {Array.<Element>}
*/
getSelectedElements.fromDOM = function (includePartial, win) {
/** @type {Document} */
var doc = win.document;
/** @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 = win.getSelection();
/** @type {number} */
var rangeCount = selection.rangeCount;
/** @type {number} */
var elementCount;
/** @type {number} */
var i;
/** @type {number} */
var startCmp = Range.START_TO_START;
/** @type {number} */
var endCmp = Range.END_TO_END;
if (includePartial) {
startCmp = Range.START_TO_END;
endCmp = Range.END_TO_START;
}
// 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(startCmp, selectedRange) > -1 &&
elementRange.compareBoundaryPoints(endCmp, selectedRange) < 1) {
selectedElements.push(element);
}
}
}
elementRange.detach();
return selectedElements;
};
/**
IE branch
@param {boolean} includePartial
@param {Window} win
@return {Array.<Element>}
*/
getSelectedElements.fromIE = function (includePartial, win) {
// 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 {Document} */
var doc = win.document;
/** @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;
/** @type {string} */
var startCmp = "StartToStart";
/** @type {string} */
var endCmp = "EndToEnd";
if (includePartial) {
startCmp = "StartToEnd";
endCmp = "EndToStart";
}
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(startCmp, selectedRange) > -1 &&
elementRange.compareEndPoints(endCmp, selectedRange) < 1) {
selectedElements.push(element);
}
}
return /** @type {Array.<Element>} */ selectedElements;
};
getSelectedElements.fromHost = getSelectedElements.mainWindow.getSelection ?
getSelectedElements.fromDOM :
getSelectedElements.fromIE;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment