1

I have a simple text area with text like this:

Lorem Ipsum [text] dolar, rock n'[more] roller.

I am trying to detect when my cursor is between the brackets and if so, allow for a Ctrl+ Right or left arrow to move the text and brackets right or left by one position with each key press without going past start or end of the line and without moving past an adjacent bracketed block. I guess this is the same as the Ctrl+Right arrow copying the first character (or space) on the right side, to the left side and vice versa.

I have some basic jquery skills but all of my attempts at this have failed miserably. Otherwise, I would have pasted in a partial block of code showing what I have. Thanks,

Shawn Cooke
  • 907
  • 1
  • 11
  • 28

3 Answers3

3

The solution will require a few different pieces:

Finding the cursor location:

function getCaretPosition(element)
{
    var CaretPos = 0;

    //Old IE way
    if ( document.selection )
    {
        element.focus();
        var textSelection = document.selection.createRange();
        textSelection.moveStart( 'character', -element.value.length );
        CaretPos = textSelection.text.length;
    }

    //DOM way
    else if ( element.selectionStart || element.selectionStart == '0' )
        CaretPos = element.selectionStart;

    return (CaretPos);
}

Find the location of the string (to see if caret is inside it). You can use a Regular expression like:

var search = /(\[[^\[\]]+\])/g;

//Then loop and find all the matches and check if
//the caret is between them
var matches = myString.match( search );

You will also need to listen to keypress events on the textarea and in that event listener:

  1. If it's a left arrow or right arrow (with ctrl button held down) then:
  2. find caret position, see if it's inside a bracket piece of text and if so:
  3. Move the text left or right (you'd grab substrings of the text before/after the bracketed text and create a new concatenated string)

Those pieces should make this work. (I'm not going to write it for you as that won't really help you)

Don Rhummy
  • 24,730
  • 42
  • 175
  • 330
  • 1
    The pattern `/(\[[^[\]]+\])/g` is also ok. (You don't need to escape the `[` character inside set). – hjpotter92 Nov 17 '15 at 20:55
  • @hjpotter92 Thanks, did not know that. I think I'll still do it for clarity though. It's easier to see which is a control "command" and which is a match character with the escaping. – Don Rhummy Nov 17 '15 at 21:04
0

Here is a fiddle that does this, and here is the code:

$('textarea').on('keydown', function(e) {
    // Is Ctrl-left and Ctrl+right pressed?
    if (e.ctrlKey && (e.which === 37 || e.which === 39)) {
        var pos = this.selectionStart;
        var val = this.value;
        var openBracketOnLeft = val.substr(0, pos).lastIndexOf('[');
        var closeBracketOnLeft = val.substr(0, pos).lastIndexOf(']');
        var closeBracketOnRight = val.substr(pos).indexOf(']');
        // Is start of selection within two brackets?
        if (openBracketOnLeft > closeBracketOnLeft && closeBracketOnRight !== -1) {
            closeBracketOnRight += pos + 1;
            var tagText = val.substr(openBracketOnLeft, closeBracketOnRight - openBracketOnLeft);
            var level = 0;
            // Repeat moving the tag until we do not break another tag in two.
            do {
                // Is Ctrl-left pressed, and is tag not yet on far left?
                if (e.which === 37 && openBracketOnLeft) {
                    ch = val.substr(openBracketOnLeft - 1, 1);
                    val = val.substr(0, openBracketOnLeft - 1)
                        + tagText
                        + ch 
                        + val.substr(closeBracketOnRight);
                    openBracketOnLeft--;
                    closeBracketOnRight--;
                // Is Ctrl-right pressed, and is tag not yet on far right?
                } else if (e.which === 39 && closeBracketOnRight < val.length) {
                    ch = val.substr(closeBracketOnRight, 1);
                    val = val.substr(0, openBracketOnLeft) 
                        + ch 
                        + tagText 
                        + val.substr(closeBracketOnRight + 1);
                    openBracketOnLeft++;
                    closeBracketOnRight++;
                } else {
                    break;
                }
                level += ch == '[' ? 1 : ch == ']' ? -1 : 0;
            } while (level);
            // Select the tag, without the brackets
            this.value = val;
            this.selectionStart = openBracketOnLeft + 1;
            this.selectionEnd = closeBracketOnRight - 1;
            e.preventDefault();
        }
    };
});
trincot
  • 317,000
  • 35
  • 244
  • 286
  • There's a small bug in your code. If you move the `[text]` far enough to the right so it's in the middle of `[more]` and then try to move that, it will end up with a left over `"]"` that is dropped off of that bracketed text. Probably better to make a bracketed text piece act as one character that cannot be moved "into". – Don Rhummy Nov 17 '15 at 21:46
  • Thanks so much for doing this. Actually, I was trying to disallow overlapping at all so that if one bracketed group is up against another, then attempting to overlap just does nothing. Is this something that could be added? – Shawn Cooke Nov 17 '15 at 22:09
  • Also, @DonRhummy if you look at some of my past posts, you will see that I **always** provide code and usually put in overtime trying to learn as much as possible prior to submitting. This one, however, was **way** over my head. Anyway, I am very appreciative of help. – Shawn Cooke Nov 17 '15 at 22:11
  • I have updated the code to do as Don suggested: jump over other tags as you move. I hope this is OK. – trincot Nov 17 '15 at 22:12
  • This is way more concise than my answer, nice! Is there any advantage to using 'e.preventDefault()' vs 'return false' at the to prevent the cursor jump? – BBQ Singular Nov 18 '15 at 03:03
  • 1
    Within a `jQuery` event handler there is a slight difference as `return false` also stops propagation ("bubbling up"). See [this question and answer](http://stackoverflow.com/questions/1357118/event-preventdefault-vs-return-false). So if you don't have any other event handlers for the same event, higher up in the `dom` hierarchy, there is no difference. – trincot Nov 18 '15 at 08:21
0

Here is a way to do it without evil regex strings. Instead I wanted to try and do it with jQuery 'keydown' event which was inline with what the questioner mentioned (see: newb at jQuery). Also note that 'keydown' is better for this methodology as 'keyup' will fire multiple times, though I guess this will too... Anyways, here is what I came up with:

$('#inputFieldInQuestion').on('keydown', function (event) {
    // if both the control key and left key are pushed
    if (event.keyCode == 37 && event.ctrlKey) {
        // grab the text from the input and caret position in the input box
        var inputBoxText = $(this).val(),
            currentCaretPosition = this.selectionStart
        // loop through all the characters in the input box text
        for (var i = 0; i < inputBoxText.length; i++) {
            // if the current character is an open bracket start testing for the end
            if (inputBoxText[i] === "[") {
                for (var j = i + 1; j < inputBoxText.length; j++) { 
                    // this means that there is another bracketed string in between the 
                    // beginning and the current bracketed string
                    if (inputBoxText[j] === "[") { break }
                    // if instead we come to the end of the bracketed string will determine
                    // if the bounds make sense
                    else if (inputBoxText[j] === "]") {
                        // if the caret position is in the bounds that you have just created
                        // we continue the shift
                        if (currentCaretPosition > i && currentCaretPosition < j) {
                            // test as per the question if the bracketed string is adjascent
                            // to another bracketed string
                            if (inputBoxText[i - 1] !== "]") {
                                // if the bracketed text is all the way to the left of the 
                                // input box
                                if (i > 0) {
                                    // slice and dice the string and move things left by one 
                                    // character
                                    var frontString = inputBoxText.substring(0, i),
                                        stringToMove = inputBoxText.substring(i, j + 1),
                                        endString = inputBoxText.substring(j + 1)
                                    $(this).val(frontString.slice(0, i - 1) + stringToMove + frontString.slice(i - 1) + endString)
                                    this.setSelectionRange(currentCaretPosition - 1, currentCaretPosition - 1); break
                                }
                            }
                            else { break }
                        }
                    }
                }
            }
        }
        // important so that the ctrl-left doesn't shift the cursor to the end of the word
        return false;
    }
    // if both the control key and right key are pushed
    else if (event.keyCode == 39 && event.ctrlKey) {
        var inputBoxText = $(this).val(),
            currentCaretPosition = this.selectionStart
        for (var i = 0; i < inputBoxText.length; i++) {
            if (inputBoxText[i] === "[") {
                for (var j = i; j < inputBoxText.length; j++) {
                    if (inputBoxText[j] === "]") {
                        if (currentCaretPosition > i && currentCaretPosition < j) {
                            // test as per the question if the bracketed string is adjascent
                            // to another bracketed string
                            if (inputBoxText[j + 1] !== "[") {
                                // bracketed text is all the way to the right of the input box
                                if (inputBoxText.length - j > 1) {
                                    var frontString = inputBoxText.substring(0, i),
                                        stringToMove = inputBoxText.substring(i, j + 1),
                                        endString = inputBoxText.substring(j + 1)
                                    $(this).val(frontString + endString.slice(0, 1) + stringToMove + endString.slice(1))
                                    this.setSelectionRange(currentCaretPosition + 1, currentCaretPosition + 1); break
                                }
                            }
                            else { break }
                        }
                    }
                }
            }
        }
        return false;
    }
})

This might be the most complicated way to do this ever but it does seem to work and satisfy all the constraints posed. Since I just noticed that this was flagged regex, this might be a terrible solution. Let the evisceration begin!

Super Bonus: This will work if you have any number of "[]" pairs in the string.

BBQ Singular
  • 457
  • 4
  • 15