{"id":3128,"date":"2014-01-06T11:54:43","date_gmt":"2014-01-06T17:54:43","guid":{"rendered":"http:\/\/bililite.com\/blog\/?p=3128"},"modified":"2014-02-26T17:15:52","modified_gmt":"2014-02-26T23:15:52","slug":"preserving-the-insertion-point-on-blur","status":"publish","type":"post","link":"https:\/\/bililite.com\/blog\/2014\/01\/06\/preserving-the-insertion-point-on-blur\/","title":{"rendered":"Preserving the Insertion Point on Blur"},"content":{"rendered":"<p>One of those ongoing unsolved problems with web apps is how to keep track of the selection or insertion point in an editing element, when the focus has moved onto some other element. For instance, in a rich-text editor, you want to be able to select a word, then click the \"bold\" button and make that word bold. But once you click the button, the original word is no longer selected!<\/p>\r\n<p>One solution that is used by Yahoo mail (and I believe Gmail as well) is to put the editing element in its own <code class=\"language-html\" >&lt;iframe&gt;<\/code>. That acts as a separate window, and the selection is kept even when that \"window\" is no longer active. You can then get the selection with <code class=\"language-javascript\" >window.getSelection()<\/code> and the like (look at the <a href=\"https:\/\/github.com\/dwachss\/bililiteRange\/blob\/master\/bililiteRange.js\">source for <code class=\"language-javascript\" >bililiteRange._nativeGetSelection()<\/code><\/a> for the variations on that in different browsers and elements). That's a perfectly good solution, but I would like to be able to use any element on the current page.<\/p>\r\n<p>There are two related issues we need to deal with:<\/p>\r\n<ol>\r\n<li>We want to know what the selection was when the element was active, after the element loses focus.<\/li>\r\n<li>We want to be able to restore the selection when focus returns to the element.<\/li>\r\n<\/ol>\r\n<p>In each case, we have to deal with standards-based browsers in input and textarea elements (which behave identically) and other elements, and with Internet Explorer 8 (\"we\" here means just me, unfortunately).<\/p>\r\n<h3>Finding the selection<\/h3>\r\n<p>For input elements, that's easy. The browsers maintains <code class=\"language-javascript\" >element.selectionStart<\/code> and  <code class=\"language-javascript\" >element.selectionEnd<\/code>. For the others, we have to somehow catch the element before it loses focus and grab the selection then. Internet Explorer actually does this right; it fires a <code class=\"language-javascript\" >beforedeactivate<\/code> event, so we can use <code class=\"language-javascript\" >element.attachEvent('onbeforedeactivate', function() { saveSelection(document.selection) }<\/code>. I don't like IE's nonstandard nomenclature, but at least the event exists.<\/p>\r\n<p>In standard browsers, there's nothing. The <code class=\"language-javascript\" >blur<\/code> event fires <em>after<\/em> the focus is lost, so the selection is lost. I can't find anything else to do but listen to <em>every<\/em> user interaction and record the selection each time, and remember to programmatically save the selection whenever it is changed in code: <code class=\"language-javascript\" >element.addEventListener('mouseup', function() { saveSelection(window.getSelection() })<\/code> and similarly for <code class=\"language-javascript\" >keyup<\/code>. I'm pretty sure that those are the only two user interactions that browsers fire; all the others, like <code class=\"language-javascript\" >cut<\/code> or <code class=\"language-javascript\" >drop<\/code>, also fire one of those. It's inefficient to have to check the selection so often, but I don't see another option.<\/p>\r\n<h3>Restoring the focus<\/h3>\r\n<p>With the selection in hand, we can manipulate the text as desired, but when we return the focus to the text, we would like the selection to be in the right place. It's easy enough to do <code class=\"language-javascript\" >element.addEventListener('focus', function() {window.getSelection().addRange (savedSelection)})<\/code> or the equivalent in the other use cases, but that sets the selection even when we don't want it to.<\/p>\r\n<p>There are three ways to put the focus in an element: click in it, tab into it, and in script with <code class=\"language-javascript\" >element.focus()<\/code>. With a mouse click, we want the selection to be where the user clicked, not where it used to be. The effect of tabbing into an element varies (see below), but I would like the selection to be restored. So the only question is whether the <code class=\"language-javascript\" >focus<\/code> event happened as the result of a click or not.<\/p>\r\n<p>Unfortunately, the <code class=\"language-javascript\" >focus<\/code> event happens <em>before<\/em> the <code class=\"language-javascript\" >mouseup<\/code> or <code class=\"language-javascript\" >click<\/code> events, and the change of the selection happens before that (even for input events that record their own selections), so I can't listen for <code class=\"language-javascript\" >mouseup<\/code>. The only solution I found was to check whether the mouse button is down at the time of the <code class=\"language-javascript\" >focus<\/code> event:<\/p>\r\n<pre><code class=\"language-javascript\" >element.addEventListener('focus', function() {\r\n  if (mouseButtonIsDown) window.getSelection().addRange (savedSelection)\r\n})<\/code><\/pre>\r\n<p>But how to get <code class=\"language-javascript\" >mouseButtonIsDown<\/code>? There is no way in Javascript in modern browsers. It's a hack, but I had to track <code class=\"language-javascript\" >mousedown<\/code> and <code class=\"language-javascript\" >mouseup<\/code>:<\/p>\r\n<pre><code class=\"language-javascript\" >mouseButtonIsDown= false;\r\ndocument.addEventListener ('mousedown', function() {\r\n  mouseButtonIsDown = true;\r\n});\r\ndocument.addEventListener ('mouseup', function() {\r\n  mouseButtonIsDown = false;\r\n});<\/code><\/pre>\r\n<p>Ugly, and fails in all sorts of edge cases when the user hits more than one mouse button or drags from one window to the next. But it's not critical; the user gets immediate feedback where the selection\/insertion point is.<\/p>\r\n<p>The other subtlety is tabbing in; I'd like to restore the selection rather than use the browser's default of selecting the whole thing or placing the insertion point at the beginning. With the <code class=\"language-javascript\" >focus<\/code> event listener above, there's a flash as the insertion point goes to the default position then moves to where I want it. I don't know how to avoid that.<\/p>\r\n<h3>Conclusion<\/h3>\r\n<p>Putting the pieces together, I can keep the selection in the right place even with other elements programmatically manipulating it. See the <a href=\"https:\/\/github.com\/dwachss\/bililiteRange\/blob\/master\/bililiteRange.js\">source code<\/a> for  <code class=\"language-javascript\" >bililiteRange.select()<\/code> and <code class=\"language-javascript\" >bililiteRange.bounds('selection')<\/code>, along with all the event listeners in the <code class=\"language-javascript\" >bililiteRange<\/code> constructor.<\/p>\r\n<p>To watch it in action, see the <a href=\"http:\/\/bililite.com\/blog\/2011\/01\/23\/improved-sendkeys\/#demo\" title=\"Improved sendkeys\"><code class=\"language-javascript\" >sendkeys<\/code> demo<\/a>. Enter text in each box, then click or tab between them or click the radio buttons above them to change the focus with Javascript.<\/p>\r\n<h3>Postscript<\/h3>\r\n<p>Browsers vary in how they handle focussing in response to a tab key or an <code class=\"language-javascript\" >element.focus()<\/code>. I tested it on my Windows XP machine:<\/p>\r\n<table>\r\n   <tr>\r\n      <td><\/td>\r\n      <td><code class=\"language-html\" >&lt;input&gt;<\/code><\/td>\r\n      <td><code class=\"language-html\" >&lt;textarea&gt;<\/code><\/td>\r\n      <td><code class=\"language-html\" >&lt;div contenteditable&gt;<\/code><\/td>\r\n   <\/tr>\r\n   <tr>\r\n      <td>Firefox tab<\/td>\r\n      <td>all<\/td>\r\n      <td>saved<\/td>\r\n      <td>start<\/td>\r\n   <\/tr>\r\n   <tr>\r\n      <td>Firefox <code class=\"language-javascript\" >focus()<\/code><\/td>\r\n      <td>saved<\/td>\r\n      <td>saved<\/td>\r\n      <td>start<\/td>\r\n   <\/tr>\r\n   <tr>\r\n      <td>Chrome tab<\/td>\r\n      <td>all<\/td>\r\n      <td>start<\/td>\r\n      <td>start<\/td>\r\n   <\/tr>\r\n   <tr>\r\n      <td>Chrome <code class=\"language-javascript\" >focus()<\/code><\/td>\r\n      <td>saved<\/td>\r\n      <td>saved<\/td>\r\n      <td>start<\/td>\r\n   <\/tr>\r\n   <tr>\r\n      <td>IE 8 tab<\/td>\r\n      <td>all<\/td>\r\n      <td>end<\/td>\r\n      <td>start<\/td>\r\n   <\/tr>\r\n   <tr>\r\n      <td>IE 8 <code class=\"language-javascript\" >focus()<\/code><\/td>\r\n      <td>start<\/td>\r\n      <td>start<\/td>\r\n      <td>start<\/td>\r\n   <\/tr>\r\n<\/table>\r\n<p>\"All\" means the entire text is selected, \"start\" means the insertion point is placed before the first character, and \"saved\" means that the selection is restored to where it was when the element lost focus.<\/p>","protected":false},"excerpt":{"rendered":"One of those ongoing unsolved problems with web apps is how to keep track of the selection or insertion point in an editing element, when the focus has moved onto some other element. For instance, in a rich-text editor, you want to be able to select a word, then click the \"bold\" button and make [&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\/3128"}],"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=3128"}],"version-history":[{"count":10,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/3128\/revisions"}],"predecessor-version":[{"id":3138,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/3128\/revisions\/3138"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=3128"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/categories?post=3128"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/tags?post=3128"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}