1

I try to modify the selection a user has done to always be complete words. For example, given the Text "Report: President Obama seeks to keep 9,800 US troops in Afghanistan...after this year, source says", if as user just selects "fghanist" I would like that the selection modifies to "Afghanistan". After some searching I stumbled, here on Stackoverflow, upon the following solution:

snapSelectionToWord : function() {
  var sel;
  if (window.getSelection && (sel = window.getSelection()).modify) {
    sel = window.getSelection();
    if (!sel.isCollapsed) {
        // Detect if selection is backwards
        var range = document.createRange();
        range.setStart(sel.anchorNode, sel.anchorOffset);
        range.setEnd(sel.focusNode, sel.focusOffset);

        var backwards = range.collapsed;
        range.detach();

        // modify() works on the focus of the selection
        var endNode = sel.focusNode, endOffset = sel.focusOffset;
        sel.collapse(sel.anchorNode, sel.anchorOffset);

        var direction = [];
        if (backwards) {
            direction = ['backward', 'forward'];
        } else {
            direction = ['forward', 'backward'];
        }

        sel.modify("move", direction[0], "character");
        sel.modify("move", direction[1], "word");
        sel.extend(endNode, endOffset);
        sel.modify("extend", direction[1], "character");
        sel.modify("extend", direction[0], "word");
    }
  } else if ( (sel = document.selection) && sel.type != "Control") {
      var textRange = sel.createRange();
      if (textRange.text) {
        textRange.expand("word");
        // Move the end back to not include the word's trailing space(s), if necessary
        while (/\s$/.test(textRange.text)) {
            textRange.moveEnd("character", -1);
        }
        textRange.select();
    }
  }
},

In principle, it works pretty well. However, there are some pathelogical cases. For examle, the selection "fghanistan.." it snaps to "Afghanistan...after" (I would like to have "Afghanistan"). The same result I get for selection ".afte" (desired result here: "after"). The main problem seems to be that the granularity word only considers whitespace but not other punctuation marks.

My idea was now to change the granularity from word to character and put this into a loop until a punctuation signs is reached. This works pretty well for the sel.modify("extend", ...); lines, but not (as intended) for the sel.modify("move", ...); lines. Both after the collapse and the first move modification, sel.toString() is empty. Hence, I cannot loop of the first/last character to move character by character. After sel.extend(endNode, endOffset);, sel.toString() is set again, and the approach using a loop works.

I simply cannot get my head around this.

Community
  • 1
  • 1
Christian
  • 3,239
  • 5
  • 38
  • 79
  • 1
    Applications that auto–select entire words drive me crazy. Be aware that some users will seriously hate this. :-) – RobG May 28 '14 at 07:13
  • RobG, agreed! I don't like to be patronized either :). However, I'm aiming for a system where users annotate text snippets. That is, they first select a snippet and then can annotate it. I think snapping to word boundaries is reasonable in this case. – Christian May 28 '14 at 07:42

1 Answers1

2

I stumbled upon rangy, which solves my issues pretty well. Using this library reduces my function to:

snapSelectionToWord : function() {
  rangy.getSelection().expand("word");
},

Thanks and kudos to the developer for making my life so much easier!

Christian
  • 3,239
  • 5
  • 38
  • 79