how to modify selection from left to right white space

Viewed 189

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>

3 Answers

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
    }
}

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>

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>

Related