2

in the example below pls place the cursor inside story, for example after the letter b.

When clicking on the button I need to expand the selection left AND right untill a white space - or start/end of line.
so https://tumblr.com should be selected and written in console.

problem with my trying - only tumblr is selected

$(btngo).on('click', function(){
    var sel = window.getSelection();
    sel.modify("move", "backward", "word");
    sel.modify("extend", "right", "word");
    console.log(sel.toString()); // tumblr - I need - https://tumblr.com
});
.story{outline:none;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='story' id='story' contenteditable>
lorem - https://tumblr.com
</div>
<br>
<button id='btngo'>GO</button>
bloodyKnuckles
  • 11,551
  • 3
  • 29
  • 37
provance
  • 877
  • 6
  • 10
  • [MDN getSelection()](https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection) *Returns: A Selection object. When cast to string, either by appending an empty string ("") or using Selection.toString(), this object returns the text selected.* – freedomn-m May 31 '22 at 14:16
  • @freedomn-m- thanks I see, but how to get the whole desired selection – provance May 31 '22 at 14:19
  • I realise that's not an answer, it's addressing your `console.log(sel)` – freedomn-m May 31 '22 at 14:20
  • https://developer.mozilla.org/en-US/docs/Web/API/Selection/extend may shed some light: *The anchor of the selection does not move. The selection will be from the anchor to the new focus, regardless of direction.* - which is exactly what's happening when you use .modify("extend") – freedomn-m May 31 '22 at 14:21
  • Use .modify("move".. to move the caret then extend it: https://stackoverflow.com/a/10964743/2181514 – freedomn-m May 31 '22 at 14:25
  • If you want something other than the [provided selections](https://developer.mozilla.org/en-US/docs/Web/API/Selection/modify) (granularity) - then you'll need to get the text yourself, locate the selection and expand it with your own code, eg using a regex or [lastIndexOf(" ", start)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf) – freedomn-m May 31 '22 at 14:28
  • @freedomn-m - strange there is no `space` as granularity option – provance May 31 '22 at 14:31

3 Answers3

1

Using Javascript Selection, Range and split()

This solution splits the entire block of text into lines, then splits the line into words. It first searches line by line for the cursor position, then, once the line if found, searches the line for the word containing the cursor. The larger the text block the more processing is required to find and highlight the target word. For a solution independent of text block size see my other answer.

Place cursor within any word, and click "GO". That word is returned for further processing, and highlighted. If the cursor is not placed in a word, or text is already selected, the block of text is not searched and the message "no cursor position" is returned.

To find the word, (a continuous string of characters without spaces), containing the cursor, the entire text block is split on spaces or line returns. This array is looped over searching for the cursor position. Once it is found the corresponding word in the array is returned, and then used to locate the word in the block of text, which is then highlighted.

// document.getElementById("btngo").addEventListener('click', function () { // vanilla

$(btngo).on('click', function(){
    var sel = window.getSelection();

    // get beginning and end of selection
    let ao = sel.anchorOffset;
    let fo = sel.focusOffset;

    // make sure there is a cursor position but not a selected range
    if ( 1 < fo && ao === fo ) {

        // get node containing cursor
        let fn = sel.focusNode;
        // trim whitespace at beginning and end of block of text, 
        // then split on spaces into an array
        let fn_arr = fn.textContent.trim().split(/\r?\n|\s/);

        // loop array and search for cursor position
        // when found use position to determine word in array containing cursor
        let [strt,end,wrd] = fn_arr.reduce(function (acc, txt) {

            return (fo > acc[1]) // have we reached the cursor position?
              ? [acc[1] + 1, acc[1] + txt.length + 1, txt] // if not keep track of location
              : acc; // if so, keep returning location information
        }, [0,0,""]);

        console.log(wrd); // display word containing cursor

        // select word containing cursor
        let range = new Range();
        range.setStart(fn, strt);
        range.setEnd(fn, end);
        sel.addRange(range);
    }
    else { console.log('no cursor position') }
});
.story{outline:none;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='story' id='story' contenteditable>
lorem - https://tumblr.com
more text to@select from
</div>
<br>
<button id='btngo'>GO</button>

For a LONG block of text, it can be searched line by line, then search only the line for cursor:

document.getElementById("btngo").addEventListener('click', function () {
    var sel = window.getSelection();
    // get beginning and end of selection
    let [ao, fo] = [sel.anchorOffset, sel.focusOffset];
    // make sure there is a cursor position but not a selected range
    if ( 1 < fo && ao === fo ) {
        // get node containing cursor
        let fn = sel.focusNode;

// FIND LINE
        // split on *line returns* into an array
        let fn_arr = fn.textContent.trim().split(/\r?\n/);
        // when found use position to determine *line* in array containing cursor
        let [strt,end,wrd] = fn_arr.reduce(findCursor(fo), [0,0,""]);

// FIND WORD IN LINE
        // split on *spaces* into an array
        fn_arr = wrd.trim().split(' ');    
        // when found use position to determine *word* in array containing cursor
        [strt,end,wrd] = fn_arr.reduce(findCursor(fo), [strt,strt-1,""]);

        console.log(wrd); // display word containing cursor
        // select word containing cursor
        let range = new Range();
        range.setStart(fn, strt);
        range.setEnd(fn, end);
        sel.addRange(range);
    }
    else { console.log('no cursor position')}
});

const findCursor = function (fo) {
    return function (acc, txt) {
        return (fo > acc[1]) // have we reached the cursor position?
            ? [acc[1] + 1, acc[1] + txt.length + 1, txt] // if not keep track of location
            : acc; // if so, keep returning location information
    }
}
bloodyKnuckles
  • 11,551
  • 3
  • 29
  • 37
1

You can use a range to position the cursor where you need it and select from there, in this case the selection will be from the 9th position up to the end of line.

$(btngo).on('click', function(){
    var range = document.createRange()
    var sel = window.getSelection()
    
    //place the cursor to 9th position
    range.setStart($("#story")[0].childNodes[0], 9)
    range.collapse(true)
    
    sel.removeAllRanges()
    sel.addRange(range)  
 
    sel.modify("extend", "right", "lineboundary");
    console.log(sel.toString());
});
.story{outline:none;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class='story' id='story' contenteditable>
lorem - https://tumblr.com
</div>
<br>
<button id='btngo'>GO</div>
user2495207
  • 861
  • 2
  • 10
  • 15
1

Using Javascript Selection and Selection.setBaseAndExtent():

This solution starts with the anchorNode and focusNode to locate the cursor, then Selection.setBaseAndExtent() to gradually expand the selection until whitespace is found. The benefit to this approach is speed and performance are not affected by the size of the text block the cursor is placed in.

Place cursor within any word, and click "GO".

The forward side of the selection, typically the anchorNode (starting) position, is moved backward incrementally until a space or line return is found. The backward side of the selection, typically the focusNode (ending) position, is moved forward incrementally until a space or line return is found. That selection text is now available for further processing as sel.toString().

Actually, the anchorNode and anchorOffset are where the users start their cursor selection, and the focusNode and focusOffset is where they end their selection. So the anchor... is typically before, and the focus... after, but not necessarily. So these are sorted in order to move them in the corresponding direction they were selected.

document.getElementById("btngo").addEventListener('click', function () { // vanilla...why not?
    const sel = window.getSelection();

    let [bws,aws] = [false,false]; // before whitespace, after whitespace

    // sort because direction user makes selection determines node/offset order
    let [[bn,bo],[an,ao]] // before node/offset (backward), and after node/offset (forward)
        = [[sel.anchorNode, sel.anchorOffset],[sel.focusNode, sel.focusOffset]]
            .sort(function (aa,bb) { return aa[1] - bb[1]; }); // sort by offset

    // move BEFORE position backward until whitespace or beginning of node
    while ( !bws && 0 < bo ) {
        sel.setBaseAndExtent(bn,--bo,an,ao); // move backward 1 character

        // don't include whitespace, and assign result to bws
        if ( (bws = (-1 !== sel.toString().search(/\r?\n| /))) ) {
            ++bo; // prepare to move forward 1 to remove space
        }
    }

    // move AFTER position forward until whitespace or end of node
    while ( !aws && an.length >= ao + 1 ) {
        sel.setBaseAndExtent(bn,bo,an,++ao); // move forward 1 character
        if ( (aws = (-1 !== sel.toString().search(/\r?\n| /))) ) { // don't include whitespace, and assign result to aws
            --ao; // prepare to move backward to remove space
        }
    }
    sel.setBaseAndExtent(bn,bo,an,ao); // remove whitespace
    console.log(sel.toString());
});
.story{outline:none;}
<div class="story" id="story" contenteditable>
lorem - https://tumblr.com
more text to@select from
</div>
<br>
<button id="btngo">GO</button>
bloodyKnuckles
  • 11,551
  • 3
  • 29
  • 37
  • 1
    finally that's it. Thanks again. Solved – provance Jun 01 '22 at 08:20
  • I see just now - it doesn't work if cursor is in last word (from) – provance Jun 01 '22 at 17:03
  • In the "Run code snippet" I put the cursor in "from" and it gets highlighted and logged. It doesn't for you? – bloodyKnuckles Jun 01 '22 at 18:15
  • 1
    no, it doesn't. Placing cursor inside `from` and clicking on `go` my page becomes unresponsive (chrome, don't know for other browsers). Seems the code fals into some kind of endless circular execution. – provance Jun 01 '22 at 20:56
  • Good catch. I found the problem and fixed it. Apparently chrome wasn't letting the `try`/`catch` block suppress the focusOffset error. Now using this check: `sel.focusNode.length >= sel.focusOffset + 1` to make sure the error doesn't occur. – bloodyKnuckles Jun 01 '22 at 21:28
  • it works. You're a javascript hero ! Any idea - how to suggest to js developers something like `selection.modify("extend", "left and right", "url");` - meaning - give me the url under the current cursor position. The entire story here would be - a single line of code – provance Jun 01 '22 at 23:34
  • Cool. `selection.modify("extend", /regex/)` might be more practical. – bloodyKnuckles Jun 02 '22 at 00:59