0

I have a contentditable element. When user selects some text, I know about it using a keyup event. I'm interested in knowing when and if the selection has changed from last selection.

The problem is (see Fiddle):

html:

<div id="div1" contenteditable="true">This is a sample text</div>

js:

 $(function(){
  var selectedRange, selectedNode;
  $("#div1").on("keyup mouseup", function(e) {
    selectedRange = window.getSelection().getRangeAt(0);
    selectedNode = selectedRange.commonAncestorContainer;
    console.log("selected node: ", selectedNode);
    console.log("selected node value: ", selectedNode.nodeValue);
    console.log("selection chars range: START POS: ", selectedRange.startOffset);
    console.log("selection chars range: END POS: ", selectedRange.endOffset);
  });
});

When a user selects some text, and then selects another text within the first selection's range (or just puts the caret inside the selected range) - the range object returns the wrong value, claiming that the selection hasn't been changed.

Only after second selection/ mouse click will the range object return the correct value.

It is very important for me to know the selection has changed on the first mouse click.

I came across this answer, 3 years ago, hoping things have changed by now.

Please note I'm supporting all major browsers (ie >=10) (e.g. no "selectstart" event in firefox).

Here's how to reproduce this issue:

  1. Go to the fiddle attached, run the code and open web console.
  2. In the output window, select the word "sample" using the mouse, starting from letter "s" , releasing the mouse after letter "e".
  3. Notice the console saying the start offset ("POS") is 10 & end offset is 16.
  4. click with the mouse inside the word "sample", between letters "m" to letter "p" (selecting nothing).
  5. Notice the web console, the range start position & end position are told to stay the same.
  6. Repeat step 4.
  7. Notice correct values return by Range API now - position is 13.

Thank you.

Community
  • 1
  • 1
Or A.
  • 1,220
  • 1
  • 15
  • 28
  • Can you add a step-by-step on how to reproduce the "error"? (i.e. paste this, copy that, notice this, error only visible if using keyboard etc.) – Mackan Apr 19 '15 at 08:31
  • @Mackan I've added reproducing steps. – Or A. Apr 19 '15 at 09:02

2 Answers2

3

One way to make sure that the selection is reset, is to actually reset it on a click. You could use mousedown for example:

$("#div1").on("keyup mouseup mousedown", function(e) {
  if (e.type == 'mousedown') {
      clearTheSelection();
  } else {
      selectedRange = window.getSelection().getRangeAt(0);
      selectedNode = selectedRange.commonAncestorContainer;
      console.log("selection chars range: START POS: ", selectedRange.startOffset);
      console.log("selection chars range: END POS: ", selectedRange.endOffset);
  }
});

function clearTheSelection() {
  if (window.getSelection) {
      if (window.getSelection().empty) {  // Chrome
         window.getSelection().empty();
      } else if (window.getSelection().removeAllRanges) {  // Firefox
         window.getSelection().removeAllRanges();
      }
  } else if (document.selection) {  // IE
      document.selection.empty();
  }
}

I have updated your fiddle with an example

Also, the clearTheSelection() I got from another SO answer: After clicking on selected text, window selection is not giving updated range

Edit:

Actually, it seems Chrome and FF can use the same method for clearing selection. That leaves us with:

function clearTheSelection() {
    if (window.getSelection) {
        window.getSelection().removeAllRanges();
    } else if (document.selection) {
        document.selection.empty();
    }
}
Community
  • 1
  • 1
Mackan
  • 6,200
  • 2
  • 25
  • 45
0

I've also managed to solve this issue by putting the logic within a timer with time of 0, meaning it will be fired immediately after last browser operation in the browser operation stack order, thus returning the correct values.

I've updated the Fiddle.

$(function(){
  var selectedRange, selectedNode, timer;
  $("#div1").on("keyup mouseup", function(e) {
    timer = setTimeout(function(){
      selectedRange = window.getSelection().getRangeAt(0);
      selectedNode = selectedRange.commonAncestorContainer;
      console.log("selected node: ", selectedNode);
      console.log("selected node value: ", selectedNode.nodeValue);
      console.log("selection chars range: START POS: ", selectedRange.startOffset);
      console.log("selection chars range: END POS: ", selectedRange.endOffset);
      clearTimeout(timer);
    },0);
  });
});
Or A.
  • 1,220
  • 1
  • 15
  • 28