{"id":1404,"date":"2011-01-17T13:40:06","date_gmt":"2011-01-17T19:40:06","guid":{"rendered":"http:\/\/bililite.nfshost.com\/blog\/?p=1404"},"modified":"2015-01-14T14:05:19","modified_gmt":"2015-01-14T20:05:19","slug":"cross-browser-text-ranges-and-selections","status":"publish","type":"post","link":"https:\/\/bililite.com\/blog\/2011\/01\/17\/cross-browser-text-ranges-and-selections\/","title":{"rendered":"Cross-Browser Text Ranges and Selections"},"content":{"rendered":"<p><strong>Updated to version 2.3 on 2014-02-26 for data routines.<\/strong><\/p>\r\n<p>I was trying to update my <a href=\"\/blog\/2008\/08\/20\/the-fnsendkeys-plugin\/\"><code>sendkeys<\/code> plugin<\/a> to use <a href=\"\/blog\/2010\/11\/19\/contenteditable\/\"><code>contentEditable<\/code><\/a>, and realized that it was getting harder and harder to do without some selection\/text replacement library. Each browser is different and even different elements are treated differently. There are a <a href=\"http:\/\/perplexed.co.uk\/1020_text_selector_jquery_plugin.htm\">few<\/a> <a href=\"http:\/\/code.google.com\/p\/rangy\/\">libraries<\/a> <a href=\"http:\/\/www.jquery-plugin.buss.hk\/my-plugins\/jquery-caret-plugin\">out<\/a> <a href=\"http:\/\/code.google.com\/p\/ierange\/\">there<\/a>, but none handled both <code>input<\/code> and <code>textarea<\/code> elements <strong>and<\/strong> regular elements, and would constrain the selections to a given element (critical if I want to edit only part of a document) and none made it easy to simulate arrow keys, moving the selection left or right. So I figured I would make my own.<p>\r\n<p>See the <a href=\"https:\/\/github.com\/dwachss\/bililiteRange\/blob\/master\/bililiteRange.js\">code on Github<\/a>.<\/p>\r\n<p><a href=\"\/blog\/blogfiles\/bililiteRange.html\">See the demo (try it in different browsers!)<\/a>.<\/p>\r\n<!--more-->\r\n<p>I was entering a world of hurt.<\/p>\r\n<p>But I think I survived, with a great deal of knowledge about browsers and a useful package of routines.<\/p>\r\n<h4>Usage<\/h4>\r\n<p><code class=\"language-javascript\">bililiteRange(element)<\/code> returns an abstraction of a range of characters within <code>element<\/code>, initially all of the element. The range never extends outside the element. <code>element<\/code> is a DOM element. jQuery is not required or used for this; it involved too much under-the-hood hacking that it was easier to write everything myself.<\/p>\r\n<p>Note that this fails for the entire <code class=\"language-html\" >&lt;body&gt;<\/code> element. I know why, but I don't have the time to fix it, and I haven't used it. If there's any demand, please let me know.<\/p>\r\n<p>The object exposes the following methods:<\/p>\r\n<dl>\r\n  <dt><code>all()<\/code><\/dt>\r\n  <dd>Returns the entire text of the element without changing the bounds of the range.<\/dd>\r\n  <dt><code>all(text {String})<\/code><\/dt>\r\n  <dd>Sets the entire text of the element to <code class=\"language-javascript\">text<\/code> without changing the bounds of the range.<\/dd>\r\n\r\n  <dt><code>bounds()<\/code><\/dt>\r\n  <dd>Returns an <code>Array [start, end]<\/code> of the bounds of the current range. <code>start<\/code> is always >= 0 and <code>end<\/code> is always <= length of the text of the entire element.<\/dd>\r\n  <dt><code>bounds(Array | 'all' | 'selection')<\/code><\/dt>\r\n  <dd>Sets the bounds of the current range to the <code class=\"language-javascript\">Array [start, end]<\/code>, limited to <code class=\"language-javascript\">[0, length of whole element]<\/code>. Does not throw an error for limits out of bounds, just silently limits it. <code class=\"language-javascript\">bounds('all')<\/code> sets the range to cover the entire element. <code class=\"language-javascript\">bounds('selection')<\/code> sets the range to the part of the current selection that is in the element. This only uses the actual selection if the element is the same as <a href=\"https:\/\/developer.mozilla.org\/en\/DOM\/document.activeElement\"><code class=\"language-javascript\">document.activeElement<\/code><\/a>; if the element is not active, then <code class=\"language-javascript\" >bililiteRange<\/code> sets up event listeners to remember the selection from when the element <em>was<\/em> the active element, and uses that. If the element was never active, it does its best: if the selection is <em>before<\/em> the element, returns <code class=\"language-javascript\">[0,0]<\/code> and if the selection is <em>after<\/em> the element, returns <code class=\"language-javascript\">[length of whole element, length of whole element]<\/code>. See <a href=\"http:\/\/bililite.com\/blog\/2014\/01\/06\/preserving-the-insertion-point-on-blur\/\" title=\"Preserving the Insertion Point on Blur\">my discussion of preserving the selection on blur<\/a>.<\/dd>\r\n\r\n  <dt><code>clone()<\/code><\/dt>\r\n  <dd>Return a new bililiteRange with the same bounds as this one.<\/dd>\r\n\r\n  <dt><code>data()<\/code><\/dt>\r\n  <dd>Returns an object tied to the underlying element. See my <a href=\"\/blog\/2014\/02\/26\/bililiterange-data\/\">later post on bililiteRange data<\/a>.<\/dd>\r\n\r\n  <dt><code>element()<\/code><\/dt>\r\n  <dd>Returns the DOM element that the range was defined on.<\/dd>\r\n \r\n  <dt><code>insertEOL()<\/code><\/dt>\r\n  <dd>Replaces the text at the current range with a line break. For <code>input<\/code> and <code>textarea<\/code> elements, this is the same as <code class=\"language-javascript\">text('\\n')<\/code>. For other elements, this inserts a <code>br<\/code> element.<\/dd>\r\n  <dd>This part remains flaky for <code>contenteditable<\/code> elements. I can't get the standards-based browsers to select <code class=\"language-html\" >&lt;br&gt;<\/code> elements consistently. Use at your own risk (or fix it for me and let me know!)<\/dd>\r\n\r\n  <dt><code>scrollIntoView([Function])<\/code><\/dt>\r\n  <dd>Does its best to scroll the beginning of the range into the visible part of the element, by analogy to <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ie\/ms536730(v=vs.85).aspx\">Element.scrollIntoView<\/a>. See <a href=\"http:\/\/bililite.com\/blog\/2013\/02\/13\/scrolling-to-cross-browser-ranges\/\" title=\"Scrolling to Cross-browser Ranges\">my post<\/a> for a longer discussion. Note that it does <em>not<\/em> move the element itself, just sets <code class=\"language-javascript\" >element.scrollTop<\/code> so that the start of the range is within the visible part of the element. If it already visible, does nothing. This only scrolls vertically, not horizontally.<\/dd>\r\n  <dd>The function passed in used to do the scrolling, with one parameter that is the target <code class=\"language-javascript\" >scrollTop<\/code>, and <code class=\"language-javascript\" >this<\/code> set to the element itself. So, to animate the scrolling, use <code class=\"language-javascript\" >range.scrollIntoView(function (top) { $(this).animate({scrollTop: top}) })<\/code>. The default function is <code class=\"language-javascript\" >function (top) { this.scrollTop = top }<\/code>.<\/dd>\r\n\r\n  <dt><code>select()<\/code><\/dt>\r\n  <dd>If the element is the same as <a href=\"https:\/\/developer.mozilla.org\/en\/DOM\/document.activeElement\"><code class=\"language-javascript\">document.activeElement<\/code><\/a>, then set the window selection to the current range. If the window is not active, change the saved selection to the current range, and use that for <code class=\"language-javascript\" >bounds('selection')<\/code>. Sets up event listeners so that when the element is activated, the saved selection is restored. See <a href=\"http:\/\/bililite.com\/blog\/2014\/01\/06\/preserving-the-insertion-point-on-blur\/\" title=\"Preserving the Insertion Point on Blur\">my discussion of preserving the selection on blur<\/a>.<\/dd>\r\n<dd>Note that this does <em>not<\/em> set the focus on the element; use <code class=\"language-javascript\" >range.element().focus()<\/code> to do that. Note also that elements that are not editable and do not have a <code>tabindex<\/code> cannot be focussed.<\/dd>\r\n\r\n  <dt><code>selection()<\/code><\/dt>\r\n  <dd>Short for <code class=\"language-javascript\" >rng.bounds('selection').text()<\/code>, to get the text currently selected.<\/dd>\r\n  <dt><code>selection(String)<\/code><\/dt>\r\n  <dd>Short for <code class=\"language-javascript\" >rng.bounds('selection').text(text, 'end').select()<\/code>; useful for inserting text at the insertion point. This just inserts the String argument straight in the text; for a more sophisticated function, see <code>sendkeys<\/code> below.<\/dd>\r\n\r\n  <dt><code>sendkeys(String)<\/code><\/dt>\r\n  <dd>Basically does <code>text(String, 'end')<\/code> but interprets brace-surrounded words (like <code class=\"language-javascript\" >'{Backspace}'<\/code> as special commands that execute the corresponding functions in <code class=\"language-javascript\" >bililiteRange.sendkeys<\/code>, in this case <code class=\"language-javascript\" >bililiteRange.sendkeys['{Backspace}']<\/code>. See the <a href=\"\/blog\/2015\/01\/14\/bililiterange-sendkeys\/\">description<\/a> of the functions available and how to add more.<\/dd>\r\n\r\n  <dt><code>text()<\/code><\/dt>\r\n  <dd>Returns a <code>String<\/code> containing the text of the current range (with <code class=\"language-javascript\">'\\r\\n'<\/code> converted to <code class=\"language-javascript\">'\\n'<\/code>).<\/dd>\r\n  <dt><code>text(String [, 'start'|'end'|'all'])<\/code><\/dt>\r\n  <dd>Sets the text of the current range to <code>String<\/code>. If the second argument is present, also sets bounds, to the start of the inserted text, the end of the inserted text (what happens with the usual \"paste\" command) or the entire inserted text. Follow this with <code class=\"language-javascript\">select()<\/code> to actually set the selection.<\/dd>\r\n\r\n  <dt><code>top()<\/code><\/dt>\r\n  <dd>Returns the \"offsetTop\" for the range--the pixels from the top of the padding box of the element to the beginning of the range. See MSDN's <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/hh781509(v=vs.85).aspx\">description<\/a> of element dimensions. Will be negative if the element is scrolled so that the range is above the visible part of the element. To scroll the element so that the range is at the top of the element, set <code class=\"language-javascript\" >element.scrollTop = range.top()<\/code>. See <code class=\"language-javascript\" >range.scrollIntoView()<\/code> above, and my <a href=\"http:\/\/bililite.com\/blog\/2013\/02\/13\/scrolling-to-cross-browser-ranges\/\" title=\"Scrolling to Cross-browser Ranges\">extended discussion<\/a>.<\/dd>\r\n\r\n  <dt><code>wrap(Node)<\/code><\/dt>\r\n  <dd>Wraps the range with the <a href=\"http:\/\/www.w3.org\/TR\/DOM-Level-2-Core\/core.html#ID-1950641247\">DOM Node<\/a> passed in (generally will be\r\n  an HTML element). Only works with ranges defined on the DOM itself; throws an error for ranges in\r\n  <code class=\"language-html\" >&lt;input&gt;<\/code> or\r\n  <code class=\"language-html\" >&lt;textarea&gt;<\/code> elements. Depending on the browser, will throw an error for invalid HTML\r\n  (like wrapping a <code class=\"language-html\" >&lt;p&gt;<\/code>\r\n  with a <code class=\"language-html\" >&lt;span&gt;<\/code>). For example, to highlight the range, use\r\n  <code class=\"language-javascript\" >rng.wrap (document.createElement('strong'));<\/code><\/dd>\r\n\r\n<\/dl>\r\n\r\n\r\n<h4>Examples<\/h4>\r\n<p>Replace the first character of an element: <code class=\"language-javascript\">bililiteRange(element).bounds([0,1]).text('X')<\/code><\/p>\r\n<p>Select all of an element: <code class=\"language-javascript\">bililiteRange(element).bounds('all').select()<\/code><\/p>\r\n<p>Implement a \"backspace\" key on an editable element (assuming the element is focused and the selection has been made by the user): <pre><code class=\"language-javascript\">var rng = bililiteRange(element).bounds('selection');\r\nvar bounds = rng.bounds();\r\nif (bounds[0] == bounds[1]){\r\n  \/\/ no characters selected; it's just an insertion point. Remove the previous character\r\n  rng.bounds([bounds[0]-1, bounds[1]]);\r\n}\r\nrng.text('', 'end'); \/\/ delete the characters and replace the selection<\/code><\/pre><\/p>\r\n<p>Implement a \"left arrow\" key on an editable element: <pre><code class=\"language-javascript\">var rng = bililiteRange(element).bounds('selection');\r\nvar bounds = rng.bounds();\r\nif (bounds[0] == bounds[1]){\r\n  \/\/ no characters selected; it's just an insertion point. Move to the left\r\n  rng.bounds([bounds[0]-1, bounds[0]-1]);\r\n}else{\r\n  \/\/ move the insertion point to the left of the selection\r\n  rng.bounds([bounds[0], bounds[0]]);\r\n}\r\nrng.select();<\/code><\/pre><\/p>\r\n\r\n<h4>Plugins<\/h4>\r\n<p>Inspired by jQuery, I exposed the prototype for the underlying object so it is possible to extend <code>bililiteRange<\/code>. <code class=\"language-javascript\" >bililiteRange.extend({myplugin:  function(){ do things where this is the bililiteRange object; notably this._el is the original element }; })<\/code>. For instance, you could create the plugin:<\/p>\r\n<script src=\"\/inc\/bililiteRange.js\"><\/script>\r\n<pre><code class=\"language-javascript demo\" >bililiteRange.extend({\r\n\tline: function(n){\r\n\t\tvar text = this.bounds('all').text();\r\n\t\tvar lineRE = \/^.*$\/mg; \/\/ a whole line\r\n\t\tvar match;\r\n\t\tvar bounds = [0,0];\r\n\t\tfor (var i = 0; i &lt; n; ++i){\r\n\t\t\t\/\/ note that we are using 1-indexed lines! Line 0 selects the start of the text; lines past the end select the last line.\r\n\t\t\tmatch = lineRE.exec(text);\r\n\t\t\tif (match) bounds = [match.index, match.index+match[0].length]; \/\/ the whole line\r\n\t\t\tif (!match) break;\r\n\t\t}\r\n\t\tthis.bounds(bounds);\r\n\t\treturn this;\r\n\t}\r\n});<\/code><\/pre>\r\n<p>And use this to select lines:<\/p>\r\n<pre><code class=\"language-html demo\" >&lt;pre id=text tabindex=0 &gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. In bibendum tincidunt diam,\r\n ac consequat turpis gravida id. Sed eget faucibus leo. Ut aliquet, sem nec porttitor\r\n rhoncus, felis nisi aliquet eros, a ornare nisl velit eget massa. Fusce dui leo, tempus\r\n quis auctor et, lobortis tincidunt ante. Quisque pretium rutrum suscipit. Nam id sapien\r\n vitae sapien scelerisque dignissim venenatis nec risus. Integer vitae enim ut orci dapibus\r\n lobortis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur lectus libero,\r\n scelerisque eu sodales vitae, convallis scelerisque justo. Maecenas vitae dictum enim. Sed\r\n hendrerit magna vitae eros aliquet vestibulum.&lt;\/pre&gt;\r\n&lt;label&gt;Line Number: &lt;input id=linenumber \/&gt;&lt;\/label&gt;&lt;input type=submit value=\"Select Line\" id=selectline \/&gt;<\/code><\/pre>\r\n<pre><code class=\"language-javascript demo\" >$('#selectline').click(function(){\r\n\tvar n = parseInt($('#linenumber').val());\r\n\tif (!isNaN(n)) bililiteRange($('#text')[0]).line(n).select().element().focus();\r\n});<\/code><\/pre>\r\n<p><code class=\"language-javascript\" >bililiteRange.fn.myplugin = function(){}<\/code> also works (as in jQuery) and can be used for <a href=\"http:\/\/en.wikipedia.org\/wiki\/Monkey_patch\">monkey patching<\/a>:<\/p>\r\n<pre><code class=\"language-javascript\" >var oldbounds = bililiteRange.fn.bounds;\r\nbililiteRange.fn.bounds = function () {console.log(this._bounds); oldbounds.apply(this, arguments);}<\/code><\/pre>\r\n\r\n<h4>Events<\/h4>\r\n<p>This tries to implement <a href=\"http:\/\/www.w3.org\/TR\/DOM-Level-3-Events\/\">DOM level 3 events<\/a>. <code class=\"language-javascript\" >rng.text('some text')<\/code> fires a <code class=\"language-javascript\" >beforeinput<\/code> event before, and an <code class=\"language-javascript\" >input<\/code> event after, setting the text. The event object passed to event handlers has two extra fields: <code class=\"language-javascript\" >event.data<\/code> is <code class=\"language-javascript\" >'some text'<\/code> that is to be or was inserted. <code class=\"language-javascript\" >event.bounds<\/code> is set to the bounds of the range before the text was inserted (this is not part of the standard).<\/p>\r\n<p><code class=\"language-javascript\" >rng.select()<\/code> fires a <code class=\"language-javascript\" >select<\/code> event on the element.<\/p>\r\n<p>The events are <em>asynchronous<\/em> in the sense that they are dispatched on the event loop, not executed immediately. <\/p>\r\n\r\n<p>Note that jQuery uses the <code class=\"language-javascript\" >event.data<\/code> field for its own purposes (to send the data that was added in the <code class=\"language-javascript\" >$element.on()<\/code> handler). If that data is present, then the input text is not passed in at all. You can use <code class=\"language-javascript\" >event.originalEvent<\/code> to get my input event<\/p>\r\n\r\n<p>My workplace is still standardized on Internet Explorer 8, so I have tried to make these routines compatible. The best way to do that is with a <a href=\"https:\/\/github.com\/jonathantneal\/EventListener\">polyfill<\/a>. In the absence of that, the code tries to use my own quick and dirty polyfill. It also tries to polyfill <code class=\"language-javascript\" >input<\/code> events by listening for <code class=\"language-javascript\" >keyup<\/code>, <code class=\"language-javascript\" >cut<\/code>, <code class=\"language-javascript\" >paste<\/code> and <code class=\"language-javascript\" >drop<\/code> events and triggering <code class=\"language-javascript\" >input<\/code> events.<\/p>\r\n<p>The IE8 code event handling code works very poorly, and I will not be spending a lot of time fixing it. I hope to be rid of it soon.<\/p>\r\n\r\n<p>It exposes three utility routines for event handling (each called as <code class=\"language-javascript\" >bililiteRange(element).listen('input', function)<\/code> etc.):<\/p>\r\n<dl>\r\n  <dt><code>dispatch(opts {Object})<\/code><\/dt>\r\n  <dd>Asynchronously creates a custom event of type <code class=\"language-javascript\" >opts.type<\/code>, then extends it with the rest of  <code class=\"language-javascript\" >opts<\/code>, and dispatches it on <code class=\"language-javascript\" >rng.element<\/code>. Basically does:\r\n<pre><code class=\"language-javascript\" >var event =  new CustomEvent(opts.type);\r\nfor (var key in opts) event[key] = opts[key];\r\nthis.element().dispatchEvent(event);<\/code><\/pre>\r\nbut with <code class=\"language-javascript\" >setTimeout(..., 0);<\/code>.<\/dd>\r\n  \r\n  <dt><code>listen(String, Function)<\/code><\/dt>\r\n  <dd>Shorthand for <code class=\"language-javascript\" >this.element().addEventListener<\/code>.<\/dd>\r\n\r\n  <dt><code>dontlisten(String, Function)<\/code><\/dt>\r\n  <dd>Shorthand for <code class=\"language-javascript\" >this.element().removeEventListener<\/code>.<\/dd>\r\n<\/dl>\r\n\r\n<h4>Implementation Notes<\/h4>\r\n<p><a href=\"http:\/\/youngisrael-stl.org\/images\/webdesign.png\">I never thought I'd say this<\/a>, but I think Internet Explorer's API is better than the standards-based one (remember, IE did it all <a href=\"http:\/\/www.brucelawson.co.uk\/2010\/in-praise-of-ie6\/\">earlier than anyone else<\/a>; they just didn't bother keeping up with the competition). <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms536623%28v=vs.85%29.aspx\"><code>moveStart<\/code><\/a> and <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms536620%28v=VS.85%29.aspx\"><code>moveEnd<\/code><\/a> make life much easier when I'm trying to manipulate ranges character-by-character.<\/p>\r\n<p>Neither model lets you play with characters directly; I had to iterate through the page. Again, IE lets you do it character by character, while the standards way required <a href=\"http:\/\/www.w3.org\/TR\/DOM-Level-2-Traversal-Range\/Overview.html\">traversing the DOM tree<\/a> and counting characters in text nodes. In case you are interested, here's an in-order depth-first traversal without recursion (I hate getting \"too much recursion\" errors):<\/p>\r\n<pre><code class=\"language-javascript\">function nextnode (node, root){\r\n\tif (node.firstChild) return node.firstChild;\r\n\tif (node.nextSibling) return node.nextSibling;\r\n\tif (node===root) return null;\r\n\twhile (node.parentNode){\r\n\t\tnode = node.parentNode;\r\n\t\tif (node == root) return null;\r\n\t\tif (node.nextSibling) return node.nextSibling;\r\n\t}\r\n\treturn null;\r\n}<\/code><\/pre>\r\n<p>As I write this, I note the existence of <a href=\"http:\/\/www.w3.org\/TR\/DOM-Level-2-Traversal-Range\/traversal.html#Traversal-NodeIterator\"><code>nodeIterator<\/code><\/a>, which does the same thing. I may try it out in the future. It's only available in Firefox 3.5 and above.<\/p>\r\n<p>Internet Explorer is still weird. <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms536401%28v=vs.85%29.aspx\"><code>createTextRange<\/code><\/a> <em>exists<\/em> for <code>body<\/code>, <code>textarea<\/code>, and <code>input<\/code> elements, but it doesn't work correctly on <code>textarea<\/code> elements, so I have to switch on the <code>tagName<\/code> and use <code class=\"language-javascript\">rng = document.body.createTextRange ();\r\n\t\trng.moveToElementText(el);<\/code> for most elements. Then, for <code class=\"language-css\">display: block<\/code> elements, there's an invisible paragraph marker that gets selected but isn't in the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms534676%28v=vs.85%29.aspx\"><code>textrange.text<\/code><\/a> string, so moving to the end involves an extra <code class=\"language-javascript\">moveEnd('character', -1)<\/code>, then recalculating the size of the text.<\/p>\r\n<p>And calculating the size of the text is no easy feat, since Internet Explorer uses <a href=\"http:\/\/en.wikipedia.org\/wiki\/Newline\">carriage return-line feeds<\/a> to end lines in <code>textarea<\/code> elements. I use <code class=\"language-javascript\">len = range.text.replace(\/\\r\/g, '').length<\/code> to calculate the length.<\/p>\r\n<p>The standards-based browsers handle <code>input<\/code> elements with an entirely different API, with <a href=\"https:\/\/developer.mozilla.org\/en\/XUL\/Method\/setSelectionRange\">setSelectionRange<\/a> to do exactly what I'm trying to do: manage ranges as character positions. If only everything was this easy; now it's just another special case I have to test for.<\/p>\r\n<p><code>textarea<\/code> elements are consistently inconsistent: they have textnode children like real elements, but their text in in the value attribute like <code>input<\/code> elements. So there's a lot of switching on <code>tagName<\/code>s.<\/p>\r\n<p>Internet Explorer's <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms536373%28VS.85%29.aspx\"><code>a.compareEndPoints(how, b)<\/code><\/a> and the standard's <a href=\"http:\/\/www.w3.org\/TR\/2000\/REC-DOM-Level-2-Traversal-Range-20001113\/ranges.html#Level2-Range-method-compareBoundaryPoints\"><code>a.compareBoundaryPoints(how, b)<\/code><\/a> sound identical, but they interpret their arguments <em>in exactly the opposite way<\/em>. This is insane! IE's start-to-end compares <code>a<\/code>'s start to <code>b<\/code>'s end, but standard's start-to-end compares <code>b<\/code>'s start to <code>a<\/code>'s end. Both interpret the result the same way, -1 if <code>a<\/code> is before <code>b<\/code>, +1 if <code>a<\/code> is after <code>b<\/code>. Again, IE seems more intuitive.<\/p>\r\n<p>The standards API throws errors for any out-of-bounds violations (offsets too large or < 0, start > end). IE just accepts them and limits them appropriately, which is the tack I take here.<\/p>\r\n<p>Hope this turns out useful to someone.<\/p>\r\n<\/a>","protected":false},"excerpt":{"rendered":"Updated to version 2.3 on 2014-02-26 for data routines. I was trying to update my sendkeys plugin to use contentEditable, and realized that it was getting harder and harder to do without some selection\/text replacement library. Each browser is different and even different elements are treated differently. There are a few libraries out there, but [&hellip;]","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[18,10],"tags":[],"_links":{"self":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/1404"}],"collection":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/comments?post=1404"}],"version-history":[{"count":60,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/1404\/revisions"}],"predecessor-version":[{"id":3445,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/1404\/revisions\/3445"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=1404"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/categories?post=1404"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/tags?post=1404"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}