Skip to content

Instantly share code, notes, and snippets.

@tcr
Created June 13, 2011 01:28

Revisions

  1. Tim Cameron Ryan revised this gist Jun 13, 2011. 2 changed files with 35 additions and 32 deletions.
    32 changes: 17 additions & 15 deletions selection.coffee
    Original file line number Diff line number Diff line change
    @@ -5,10 +5,12 @@
    # and IE5+ (using TextRanges)
    ########################################################################

    if this.getSelection
    root = this

    if root.getSelection
    # DOMSelection

    this.selection =
    root.selection =
    hasSelection: (win) ->
    return (sel = win.getSelection()) and sel.focusNode? and sel.anchorNode?

    @@ -21,17 +23,17 @@ if this.getSelection
    return [sel.focusNode, sel.focusOffset]

    getStart: (win) ->
    return null unless util.selection.hasSelection(win)
    [n1, o1] = util.selection.getOrigin(win)
    [n2, o2] = util.selection.getFocus(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 util.selection.hasSelection(win)
    [n1, o1] = util.selection.getOrigin(win)
    [n2, o2] = util.selection.getFocus(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]
    @@ -50,10 +52,10 @@ if this.getSelection
    # IE9 throws error sometimes
    win.getSelection()?.addRange(r)

    else if this.document.selection
    else if root.document.selection
    # TextRanges (IE5+)

    (=>
    (->
    getBoundary = (doc, textRange, bStart) ->
    # iterate backwards through parent element to find anchor location
    cursorNode = doc.createElement('a')
    @@ -101,7 +103,7 @@ else if this.document.selection
    textRange.setEndPoint((if bStart then 'StartToStart' else 'EndToEnd'), cursor)
    textRange[if bStart then 'moveStart' else 'moveEnd']('character', textOffset)

    this.selection =
    root.selection =
    hasSelection: (win) ->
    win.focus()
    return false unless win.document.selection
    @@ -110,21 +112,21 @@ else if this.document.selection

    getStart: (win) ->
    win.focus()
    return null unless util.selection.hasSelection(win)
    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 util.selection.hasSelection(win)
    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) -> util.selection.getStart(win)
    getFocus: (win) -> util.selection.getEnd(win)
    getOrigin: (win) -> root.selection.getStart(win)
    getFocus: (win) -> root.selection.getEnd(win)

    setSelection: (win, orgn, orgo, focn, foco) ->
    range = win.document.body.createTextRange()
    35 changes: 18 additions & 17 deletions selection.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,7 @@
    var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
    if (this.getSelection) {
    this.selection = {
    var root;
    root = this;
    if (root.getSelection) {
    root.selection = {
    hasSelection: function(win) {
    var sel;
    return (sel = win.getSelection()) && (sel.focusNode != null) && (sel.anchorNode != null);
    @@ -21,23 +22,23 @@ if (this.getSelection) {
    },
    getStart: function(win) {
    var n1, n2, o1, o2, _ref, _ref2;
    if (!util.selection.hasSelection(win)) {
    if (!root.selection.hasSelection(win)) {
    return null;
    }
    _ref = util.selection.getOrigin(win), n1 = _ref[0], o1 = _ref[1];
    _ref2 = util.selection.getFocus(win), n2 = _ref2[0], o2 = _ref2[1];
    _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 (!util.selection.hasSelection(win)) {
    if (!root.selection.hasSelection(win)) {
    return null;
    }
    _ref = util.selection.getOrigin(win), n1 = _ref[0], o1 = _ref[1];
    _ref2 = util.selection.getFocus(win), n2 = _ref2[0], o2 = _ref2[1];
    _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];
    }
    @@ -58,8 +59,8 @@ if (this.getSelection) {
    return (_ref2 = win.getSelection()) != null ? _ref2.addRange(r) : void 0;
    }
    };
    } else if (this.document.selection) {
    (__bind(function() {
    } else if (root.document.selection) {
    (function() {
    var getBoundary, moveBoundary;
    getBoundary = function(doc, textRange, bStart) {
    var cursor, cursorNode, node, offset, parent;
    @@ -101,7 +102,7 @@ if (this.getSelection) {
    textRange.setEndPoint((bStart ? 'StartToStart' : 'EndToEnd'), cursor);
    return textRange[bStart ? 'moveStart' : 'moveEnd']('character', textOffset);
    };
    return this.selection = {
    return root.selection = {
    hasSelection: function(win) {
    var range;
    win.focus();
    @@ -114,7 +115,7 @@ if (this.getSelection) {
    getStart: function(win) {
    var range;
    win.focus();
    if (!util.selection.hasSelection(win)) {
    if (!root.selection.hasSelection(win)) {
    return null;
    }
    range = win.document.selection.createRange();
    @@ -123,17 +124,17 @@ if (this.getSelection) {
    getEnd: function(win) {
    var range;
    win.focus();
    if (!util.selection.hasSelection(win)) {
    if (!root.selection.hasSelection(win)) {
    return null;
    }
    range = win.document.selection.createRange();
    return getBoundary(win.document, range, false);
    },
    getOrigin: function(win) {
    return util.selection.getStart(win);
    return root.selection.getStart(win);
    },
    getFocus: function(win) {
    return util.selection.getEnd(win);
    return root.selection.getEnd(win);
    },
    setSelection: function(win, orgn, orgo, focn, foco) {
    var range;
    @@ -143,7 +144,7 @@ if (this.getSelection) {
    return range.select();
    }
    };
    }, this))();
    })();
    } else {
    throw new Exception('Browser not supported: no selection support.');
    }
  2. Tim Cameron Ryan revised this gist Jun 13, 2011. 2 changed files with 5 additions and 4 deletions.
    4 changes: 2 additions & 2 deletions selection.coffee
    Original file line number Diff line number Diff line change
    @@ -51,9 +51,9 @@ if this.getSelection
    win.getSelection()?.addRange(r)

    else if this.document.selection
    # <= IE8
    # TextRanges (IE5+)

    (->
    (=>
    getBoundary = (doc, textRange, bStart) ->
    # iterate backwards through parent element to find anchor location
    cursorNode = doc.createElement('a')
    5 changes: 3 additions & 2 deletions selection.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@
    var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
    if (this.getSelection) {
    this.selection = {
    hasSelection: function(win) {
    @@ -58,7 +59,7 @@ if (this.getSelection) {
    }
    };
    } else if (this.document.selection) {
    (function() {
    (__bind(function() {
    var getBoundary, moveBoundary;
    getBoundary = function(doc, textRange, bStart) {
    var cursor, cursorNode, node, offset, parent;
    @@ -142,7 +143,7 @@ if (this.getSelection) {
    return range.select();
    }
    };
    })();
    }, this))();
    } else {
    throw new Exception('Browser not supported: no selection support.');
    }
  3. Tim Cameron Ryan revised this gist Jun 13, 2011. 1 changed file with 3 additions and 2 deletions.
    5 changes: 3 additions & 2 deletions selection.coffee
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,8 @@
    ########################################################################
    # Cross-Browser Selection Utilities
    # Provides a 'selection' object whose API support all modern browsers
    # (using window.getSelection()) as well as <= IE8 (using TextRanges)
    # Provides a 'selection' object with an API supporting
    # all modern browsers (using window.getSelection())
    # and IE5+ (using TextRanges)
    ########################################################################

    if this.getSelection
  4. Tim Cameron Ryan revised this gist Jun 13, 2011. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion selection.coffee
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,7 @@
    ########################################################################
    # window selection utilities
    # Cross-Browser Selection Utilities
    # Provides a 'selection' object whose API support all modern browsers
    # (using window.getSelection()) as well as <= IE8 (using TextRanges)
    ########################################################################

    if this.getSelection
  5. Tim Cameron Ryan revised this gist Jun 13, 2011. 2 changed files with 152 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions selection.coffee
    Original file line number Diff line number Diff line change
    @@ -2,10 +2,10 @@
    # window selection utilities
    ########################################################################

    if root.getSelection
    if this.getSelection
    # DOMSelection

    util.selection =
    this.selection =
    hasSelection: (win) ->
    return (sel = win.getSelection()) and sel.focusNode? and sel.anchorNode?

    @@ -47,7 +47,7 @@ if root.getSelection
    # IE9 throws error sometimes
    win.getSelection()?.addRange(r)

    else if root.document.selection
    else if this.document.selection
    # <= IE8

    (->
    @@ -98,7 +98,7 @@ else if root.document.selection
    textRange.setEndPoint((if bStart then 'StartToStart' else 'EndToEnd'), cursor)
    textRange[if bStart then 'moveStart' else 'moveEnd']('character', textOffset)

    util.selection =
    this.selection =
    hasSelection: (win) ->
    win.focus()
    return false unless win.document.selection
    148 changes: 148 additions & 0 deletions selection.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,148 @@
    if (this.getSelection) {
    this.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 (!util.selection.hasSelection(win)) {
    return null;
    }
    _ref = util.selection.getOrigin(win), n1 = _ref[0], o1 = _ref[1];
    _ref2 = util.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 (!util.selection.hasSelection(win)) {
    return null;
    }
    _ref = util.selection.getOrigin(win), n1 = _ref[0], o1 = _ref[1];
    _ref2 = util.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 (this.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 this.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 (!util.selection.hasSelection(win)) {
    return null;
    }
    range = win.document.selection.createRange();
    return getBoundary(win.document, range, true);
    },
    getEnd: function(win) {
    var range;
    win.focus();
    if (!util.selection.hasSelection(win)) {
    return null;
    }
    range = win.document.selection.createRange();
    return getBoundary(win.document, range, false);
    },
    getOrigin: function(win) {
    return util.selection.getStart(win);
    },
    getFocus: function(win) {
    return util.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.');
    }
  6. Tim Cameron Ryan revised this gist Jun 13, 2011. 1 changed file with 0 additions and 135 deletions.
    135 changes: 0 additions & 135 deletions selection.coffee
    Original file line number Diff line number Diff line change
    @@ -1,271 +1,136 @@
    ########################################################################

    # window selection utilities

    ########################################################################



    if root.getSelection

    # DOMSelection



    util.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 util.selection.hasSelection(win)

    [n1, o1] = util.selection.getOrigin(win)

    [n2, o2] = util.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 util.selection.hasSelection(win)

    [n1, o1] = util.selection.getOrigin(win)

    [n2, o2] = util.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

    # <= IE8



    (->

    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)



    util.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 util.selection.hasSelection(win)

    range = win.document.selection.createRange()

    return getBoundary(win.document, range, yes)



    getEnd: (win) ->

    win.focus()

    return null unless util.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) -> util.selection.getStart(win)

    getFocus: (win) -> util.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.')
  7. Tim Cameron Ryan revised this gist Jun 13, 2011. No changes.
  8. Tim Cameron Ryan revised this gist Jun 13, 2011. 1 changed file with 3 additions and 5 deletions.
    8 changes: 3 additions & 5 deletions selection.coffee
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,7 @@
    ########################################################################

    # Cross-Browser selection utilities
    # window selection utilities

    # Provides a 'selection' object which normalizes differences across
    # browsers which support window.getSelection() and IE's TextRanges.
    ########################################################################


    @@ -14,7 +12,7 @@ if root.getSelection



    selection =
    util.selection =

    hasSelection: (win) ->

    @@ -200,7 +198,7 @@ else if root.document.selection



    selection =
    util.selection =

    hasSelection: (win) ->

  9. Tim Cameron Ryan created this gist Jun 13, 2011.
    273 changes: 273 additions & 0 deletions selection.coffee
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,273 @@
    ########################################################################

    # Cross-Browser selection utilities

    # Provides a 'selection' object which normalizes differences across
    # browsers which support window.getSelection() and IE's TextRanges.
    ########################################################################



    if root.getSelection

    # DOMSelection



    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 util.selection.hasSelection(win)

    [n1, o1] = util.selection.getOrigin(win)

    [n2, o2] = util.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 util.selection.hasSelection(win)

    [n1, o1] = util.selection.getOrigin(win)

    [n2, o2] = util.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

    # <= IE8



    (->

    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)



    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 util.selection.hasSelection(win)

    range = win.document.selection.createRange()

    return getBoundary(win.document, range, yes)



    getEnd: (win) ->

    win.focus()

    return null unless util.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) -> util.selection.getStart(win)

    getFocus: (win) -> util.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.')