Created
April 18, 2012 21:32
-
-
Save gitbuh/2416742 to your computer and use it in GitHub Desktop.
Get elements in user selection
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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