Skip to content

Instantly share code, notes, and snippets.

@tcr
Created June 13, 2011 01:28
Show Gist options
  • Save tcr/1022198 to your computer and use it in GitHub Desktop.
Save tcr/1022198 to your computer and use it in GitHub Desktop.
Cross-Browser Selection Utilities
########################################################################
# Cross-Browser Selection Utilities
# Provides a 'selection' object with an API supporting
# all modern browsers (using window.getSelection())
# and IE5+ (using TextRanges)
########################################################################
root = this
if root.getSelection
# DOMSelection
root.selection =
hasSelection: (win) ->
return (sel = win.getSelection()) and sel.focusNode? and sel.anchorNode?
getOrigin: (win) ->
return null unless (sel = win.getSelection()) and sel.anchorNode?
return [sel.anchorNode, sel.anchorOffset]
getFocus: (win) ->
return null unless (sel = win.getSelection()) and sel.focusNode?
return [sel.focusNode, sel.focusOffset]
getStart: (win) ->
return null unless root.selection.hasSelection(win)
[n1, o1] = root.selection.getOrigin(win)
[n2, o2] = root.selection.getFocus(win)
if util.dom.isPreceding(n1, n2) or (n1 == n2 and o1 < o2)
return [n1, o1]
return [n2, o2]
getEnd: (win) ->
return null unless root.selection.hasSelection(win)
[n1, o1] = root.selection.getOrigin(win)
[n2, o2] = root.selection.getFocus(win)
if util.dom.isPreceding(n1, n2) or (n1 == n2 and o1 < o2)
return [n2, o2]
return [n1, o1]
setSelection: (win, orgn, orgo, focn, foco) ->
# not using Selection methods as IE9 doesn't support extend()
#win.getSelection()?.collapse(orgn, orgo)
#win.getSelection()?.extend(focn, foco)
r = win.document.createRange()
r.setStart(orgn, orgo)
r.setEnd(focn, foco)
try
win.getSelection()?.removeAllRanges()
catch e
# IE9 throws error sometimes
win.getSelection()?.addRange(r)
else if root.document.selection
# TextRanges (IE5+)
(->
getBoundary = (doc, textRange, bStart) ->
# iterate backwards through parent element to find anchor location
cursorNode = doc.createElement('a')
cursor = textRange.duplicate()
cursor.collapse(bStart)
parent = cursor.parentElement()
loop
parent.insertBefore(cursorNode, cursorNode.previousSibling);
cursor.moveToElementText(cursorNode);
unless cursor.compareEndPoints((if bStart then 'StartToStart' else 'StartToEnd'), textRange) > 0 and
cursorNode.previousSibling?
break
# when we exceed or meet the cursor, we've found the node
if cursor.compareEndPoints((if bStart then 'StartToStart' else 'StartToEnd'), textRange) == -1 and
cursorNode.nextSibling
# data node
cursor.setEndPoint((if bStart then 'EndToStart' else 'EndToEnd'), textRange);
node = cursorNode.nextSibling
offset = cursor.text.length
else
# element
node = cursorNode.parentNode
offset = util.dom.getChildIndex(cursorNode)
cursorNode.parentNode.removeChild(cursorNode)
return [node, offset]
moveBoundary = (doc, textRange, bStart, node, offset) ->
# find anchor node and offset
textOffset = 0
anchorNode = if util.dom.isText(node) then node else node.childNodes[offset]
anchorParent = if util.dom.isText(node) then node.parentNode else node
# visible data nodes need a text offset
if util.dom.isText(node)
textOffset = offset
# create a cursor element node to position range (since we can't select text nodes)
cursorNode = doc.createElement('a')
anchorParent.insertBefore(cursorNode, anchorNode or null)
cursor = doc.body.createTextRange()
cursor.moveToElementText(cursorNode)
cursorNode.parentNode.removeChild(cursorNode)
# move range
textRange.setEndPoint((if bStart then 'StartToStart' else 'EndToEnd'), cursor)
textRange[if bStart then 'moveStart' else 'moveEnd']('character', textOffset)
root.selection =
hasSelection: (win) ->
win.focus()
return false unless win.document.selection
range = win.document.selection.createRange()
return range && range.parentElement().document == win.document
getStart: (win) ->
win.focus()
return null unless root.selection.hasSelection(win)
range = win.document.selection.createRange()
return getBoundary(win.document, range, yes)
getEnd: (win) ->
win.focus()
return null unless root.selection.hasSelection(win)
range = win.document.selection.createRange()
return getBoundary(win.document, range, no)
# TextRange has no forward or backward indicator;
# just assume origin is start, focus end
getOrigin: (win) -> root.selection.getStart(win)
getFocus: (win) -> root.selection.getEnd(win)
setSelection: (win, orgn, orgo, focn, foco) ->
range = win.document.body.createTextRange()
# intentionally [end, start] order
moveBoundary(win.document, range, false, focn, foco)
moveBoundary(win.document, range, true, orgn, orgo)
range.select()
)()
else
# not supported
throw new Exception('Browser not supported: no selection support.')
var root;
root = this;
if (root.getSelection) {
root.selection = {
hasSelection: function(win) {
var sel;
return (sel = win.getSelection()) && (sel.focusNode != null) && (sel.anchorNode != null);
},
getOrigin: function(win) {
var sel;
if (!((sel = win.getSelection()) && (sel.anchorNode != null))) {
return null;
}
return [sel.anchorNode, sel.anchorOffset];
},
getFocus: function(win) {
var sel;
if (!((sel = win.getSelection()) && (sel.focusNode != null))) {
return null;
}
return [sel.focusNode, sel.focusOffset];
},
getStart: function(win) {
var n1, n2, o1, o2, _ref, _ref2;
if (!root.selection.hasSelection(win)) {
return null;
}
_ref = root.selection.getOrigin(win), n1 = _ref[0], o1 = _ref[1];
_ref2 = root.selection.getFocus(win), n2 = _ref2[0], o2 = _ref2[1];
if (util.dom.isPreceding(n1, n2) || (n1 === n2 && o1 < o2)) {
return [n1, o1];
}
return [n2, o2];
},
getEnd: function(win) {
var n1, n2, o1, o2, _ref, _ref2;
if (!root.selection.hasSelection(win)) {
return null;
}
_ref = root.selection.getOrigin(win), n1 = _ref[0], o1 = _ref[1];
_ref2 = root.selection.getFocus(win), n2 = _ref2[0], o2 = _ref2[1];
if (util.dom.isPreceding(n1, n2) || (n1 === n2 && o1 < o2)) {
return [n2, o2];
}
return [n1, o1];
},
setSelection: function(win, orgn, orgo, focn, foco) {
var r, _ref, _ref2;
r = win.document.createRange();
r.setStart(orgn, orgo);
r.setEnd(focn, foco);
try {
if ((_ref = win.getSelection()) != null) {
_ref.removeAllRanges();
}
} catch (e) {
}
return (_ref2 = win.getSelection()) != null ? _ref2.addRange(r) : void 0;
}
};
} else if (root.document.selection) {
(function() {
var getBoundary, moveBoundary;
getBoundary = function(doc, textRange, bStart) {
var cursor, cursorNode, node, offset, parent;
cursorNode = doc.createElement('a');
cursor = textRange.duplicate();
cursor.collapse(bStart);
parent = cursor.parentElement();
while (true) {
parent.insertBefore(cursorNode, cursorNode.previousSibling);
cursor.moveToElementText(cursorNode);
if (!(cursor.compareEndPoints((bStart ? 'StartToStart' : 'StartToEnd'), textRange) > 0 && (cursorNode.previousSibling != null))) {
break;
}
}
if (cursor.compareEndPoints((bStart ? 'StartToStart' : 'StartToEnd'), textRange) === -1 && cursorNode.nextSibling) {
cursor.setEndPoint((bStart ? 'EndToStart' : 'EndToEnd'), textRange);
node = cursorNode.nextSibling;
offset = cursor.text.length;
} else {
node = cursorNode.parentNode;
offset = util.dom.getChildIndex(cursorNode);
}
cursorNode.parentNode.removeChild(cursorNode);
return [node, offset];
};
moveBoundary = function(doc, textRange, bStart, node, offset) {
var anchorNode, anchorParent, cursor, cursorNode, textOffset;
textOffset = 0;
anchorNode = util.dom.isText(node) ? node : node.childNodes[offset];
anchorParent = util.dom.isText(node) ? node.parentNode : node;
if (util.dom.isText(node)) {
textOffset = offset;
}
cursorNode = doc.createElement('a');
anchorParent.insertBefore(cursorNode, anchorNode || null);
cursor = doc.body.createTextRange();
cursor.moveToElementText(cursorNode);
cursorNode.parentNode.removeChild(cursorNode);
textRange.setEndPoint((bStart ? 'StartToStart' : 'EndToEnd'), cursor);
return textRange[bStart ? 'moveStart' : 'moveEnd']('character', textOffset);
};
return root.selection = {
hasSelection: function(win) {
var range;
win.focus();
if (!win.document.selection) {
return false;
}
range = win.document.selection.createRange();
return range && range.parentElement().document === win.document;
},
getStart: function(win) {
var range;
win.focus();
if (!root.selection.hasSelection(win)) {
return null;
}
range = win.document.selection.createRange();
return getBoundary(win.document, range, true);
},
getEnd: function(win) {
var range;
win.focus();
if (!root.selection.hasSelection(win)) {
return null;
}
range = win.document.selection.createRange();
return getBoundary(win.document, range, false);
},
getOrigin: function(win) {
return root.selection.getStart(win);
},
getFocus: function(win) {
return root.selection.getEnd(win);
},
setSelection: function(win, orgn, orgo, focn, foco) {
var range;
range = win.document.body.createTextRange();
moveBoundary(win.document, range, false, focn, foco);
moveBoundary(win.document, range, true, orgn, orgo);
return range.select();
}
};
})();
} else {
throw new Exception('Browser not supported: no selection support.');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment