Search code examples
javascriptjqueryrangy

Rangy, contenteditable caret positioning and focus in Firefox


I am trying to use Rangy to position the cursor within a contenteditable div, but I am finding inconsistent behaviour across browsers (which is odd, considering this is touted heavily as a "cross-browser solution").

When I open this fiddle in Chrome 36.0.1985.143 m, I observe that anywhere I click (within the "results" portion of the page), the selection is updated and the blue div gains a green border.

However; visiting in Firefox 31 will, after step 2, position the caret but NOT move the green border... and after step 3, will not position the caret at all.

My Question:

How can I reliably set the position of the caret inside a contenteditable div regardless of what the "current" selection/focus is?

Screenshot of example:

Example Screenshot

HTML:

<div id="msg">Every 2 seconds, a setInterval fires that tells rangy to set the selection to the firstChild (ie: text node) of the blue div at index 49 (between the second 2 arrows).</div>
  <ol>
    <li><span>Click the blue div, between the first 2 arrows, watch to see rangy reposition the caret.</span></li>
    <li><span>Click the red div, between the arrows, watch to see rangy reposition the caret. (Doesn't also move focus in Firefox 31 ?!?)</span></li>
    <li><span>Click this list right here.</span></li>
    <li><h4>DOES THE CARET REPOSITION?!?!</h4></li>
</ol>

<br />
<div contenteditable="true" id="one">CLICK HERE --><--  RANGY WILL REPOSITION HERE --><--</div>
<br />
<div contenteditable="true" id="two">CLICK HERE --><--</div>

CSS:

div#one {
  color: white;
  background-color: #00f;
}
div#two {
  color: white;
  background-color: #f00;
}
div#msg {
  background-color: #ccc;
  padding: 10px;
}

:focus {
  border: 2px solid lime;
  padding: -2px;
}

JS:

setInterval(function () {
  rangy.getSelection().collapse($('#one')[0].firstChild, 49);
}, 2000);

Solution

  • Rangy doesn't automatically deal with the focus in the same way that the native browser selection API doesn't deal with the focus. You need to do that yourself. In this case it's just a matter of calling the editable element's focus() method:

    setInterval(function () {
      var editableOne = $('#one')[0];
      editableOne.focus();
      rangy.getSelection().collapse(editableOne.firstChild, 49);
    }, 2000);
    

    Demo: http://jsfiddle.net/afvc7r6r/2/