javascripthtmladdeventlistenerkeypresskeyup

Get the final text of a textbox on `keypress` if the click event is allowed to go through


I want to be able to prevent a user entering characters into a textbox based on what the text of the textbox would be if the character gets entered.

The initial thought is to do something like this:

<input type="text" id="test" />

document.getElementById("test").addEventListener("keypress", (e) => {
    // Get the current text
    const currentText = e.target.value;

    // Get the new char entered by the user
    const newChar = String.fromCharCode(e.keyCode);

    // The problem is that we don't know where the cursor is, 
    // i.e. we CANNOT guarantee that the new text will be:
    const newText = `${currentText}${newChar}`;
    
    // ...because that assumes the cursor is at the end of the current text

    // I want to check that newText is valid before allowing the event to go through
    if(!someCustomCheck(newText)) {
        e.preventDefault();
    }
});

The comments explain my issue, but here's a further example: imagine we have an input that allows numbers with 2 decimal places to be added:

I cannot see a way to get the cursor position in the event in the click event, nor can I see a property in e.target (like you can in the keyup event) that gives the final text of the input.

Is there a way to do this?


Solution

  • The answer, for future readers, is to use e.target.selectionStart. That allows you to find where the user made the key press and allows you to build up what the final string would be if the input is allowed to be entered.

    const newChar = String.fromCharCode(e.keyCode);
    const cursorPos = e.target.selectionStart;
    const inputText = e.target.value;
    
    const proposedNewText = `${inputText.slice(0, cursorPos)}${newChar}${inputText.slice(cursorPos, inputText.length)}`;
    

    It's worth noting that this can get more complicated if the user has selected a range of text (i.e. a textbox with 5 characters in it and the users clicks and drags to select the 2nd -> 5th character) and then types. In that case, you'll have to get both the start and end cursor position (e.target.selectionStart and e.target.selectionEnd) and .splice() the string, adding in the user's new character.

    You've then got the issue that by changing the value of the input (using e.target.value = newText) that the cursor will automatically jump to the end of the text. There's a lot to consider depending on your use case.

    Hopefully this helps point someone in the right direction.