41
votes

I have a regular text-box:

<input type="text"> 

I use jQuery to handle key-related events:

$("input:text").keydown(function() {
    // keydown code
}).keypress(function() {
    // keypress code
}).keyup(function() {
    // keyup code
});

The user focuses on a text-box and presses various keys on his keyboard (the usual ones: letters, numbers, SHIFT, BACKSPACE, SPACE, ...). I need to detect when the user presses a key that is going to increase the length of the text-box value. For example, the "A" key will increase it, the "SHIFT" key wont.

I remember watching a lecture by PPK where he mentioned the difference between those two. It has something to do with the event - keydown vs. keypress - and possibly with the event properties - key, char, keyCode.

Update!

I need to know this information within the keydown or keypress handlers. I cannot wait for the keyup event to occur.

Why I need this:

I have a text-box which size dynamically changes based on the user input. You can have a look at this demo: http://vidasp.net/tinydemos/variable-size-text-box.html

In the demo, I have a keydown and keyup handler. The keyup handler adjusts the text-box size based on the input value. However, the keydown handler sets the size to be 1 character larger then the input value. The reason I do this is that if I didn't, then the character would overflow outside the text-box and only when the user would let go of the key, the text-box would expand. This looks weird. That's why I have to anticipate the new character - I enlarge the text-box on each keydown, ergo, before the character appears in the text-box. As you can see in the demo, this method looks great.

However, the problem are the BACKSPACE and ARROW keys - they will also expand the text-box on keydown, and only on keyup the text-box size will be corrected.

A work-around:

A work-around would be to detect the BACKSPACE, SHIFT, and ARROW keys manually and act based on that:

// keydown handler
function(e) {
    var len = $(this).val().length;
    if (e.keyCode === 37 || e.keyCode === 39 ||
        e.keyCode === 16) { // ARROW LEFT or ARROW RIGHT or SHIFT key
        return;
    } else if (e.keyCode === 8) { // BACKSPACE key
        $(this).attr("size", len <= 1 ? 1 : len - 1);
    } else {
        $(this).attr("size", len === 0 ? 1 : len + 1);
    }
}

This works (and looks great) for BACKSPACE, SHIFT, ARROW LEFT and ARROW RIGHT. However, I would like to have a more robust solution.

9
What the masses would like to know is "Why do you need to know about the value's length changing before the keyUp handler is called"??Larry K
@Larry K I updated my question...Šime Vidas
do u want to detect CTRL+V (paste) as well?Lukman
@Lukman If it's possible to cross-browser detect pasting, that would be cool. However, my primary concern are key events.Šime Vidas

9 Answers

32
votes

This I think will do the job, or if not is very close and will need only minor tweaking. The thing you have to remember is that you can't reliably tell anything at all about any character that may be typed in a keydown or keyup event: that all has to be done in a keypress handler. The definitive resource for key events is http://unixpapa.com/js/key.html

You also need to consider pastes, which this code won't handle. You will need to have separate paste event handler (although this event isn't supported in Firefox < 3.0, Opera, and very old WebKit browsers). You'll need a timer in your paste handler since it's impossible in JavaScript to access the content that's about to be pasted.

function isCharacterKeyPress(evt) {
    if (typeof evt.which == "undefined") {
        // This is IE, which only fires keypress events for printable keys
        return true;
    } else if (typeof evt.which == "number" && evt.which > 0) {
        // In other browsers except old versions of WebKit, evt.which is
        // only greater than zero if the keypress is a printable key.
        // We need to filter out backspace and ctrl/alt/meta key combinations
        return !evt.ctrlKey && !evt.metaKey && !evt.altKey && evt.which != 8;
    }
    return false;
}

<input type="text" onkeypress="alert(isCharacterKeyPress(event))">
18
votes

Here's a much simpler solution which worked well for me:

document.addEventListener('keyup', event => {
  if (String.fromCharCode(event.keyCode).match(/(\w|\s)/g)) {
    //pressed key is a char
  } else {
    //pressed key is a non-char
    //e.g. 'esc', 'backspace', 'up arrow'
  }
});

This doesn't require probing a DOM element (which would add latency and ugliness).

Updated example use:

10
votes

The possible solution I can find is checking the length of key in event.

Eg:-

<input type="text" id="testId" onkeyup="keyChecking(event)" />

<script type="text/javascript">
function keyChecking(event) {

    if (event.key.length == 1) {
        alert("key produced character " + event.key);
    } else {
        alert("Key DOES NOT produce character");

        const alphabets = "AZaz09";
        const key = event.key;
        var notEvenASymbol = false;

        for (let i = 0; i < key.length; i++) {
            var charCode = key.charCodeAt(i);
            if ((charCode >= alphabets.charCodeAt(0) && charCode <= alphabets.charCodeAt(1)) ||
                (charCode >= alphabets.charCodeAt(2) && charCode <= alphabets.charCodeAt(3)) ||
                (charCode >= alphabets.charCodeAt(4) && charCode <= alphabets.charCodeAt(5))
            ) {
                notEvenASymbol = true;
                console.log(charCode);
                break;
            }
        }

        if (notEvenASymbol) {
            alert("Key DOES NOT produce even a symbol");
        }
        console.log(event.key);

    }
}    
</script>

So, if you press any characters/symbols, the event.key will contain that character and its length will be 1. If you press character V then the event.key will have value V but if you press enter key then it will contain value Enter, if you press shift then Shift and so on. Therefore, if a key doesn't produce a character then its length will be greater than 1.

Updated

Some special keys in the keyboard produce symbol and its length may be greater than 1 so I modified the code so that it can alert even if it's not a symbol. Eg:- 😄 its length is 2. Some mobile keyboards have shortcut keys for such symbols.

A non character/symbol key in the keyboard will always be a combination of alphabets, number characters or of both, Eg:- F2, Shift.

Thanks @Vicky Chijwani for bringing attention to this scenario.

4
votes

OK, I think I've got it. The solution is a bit hackish, but actually works really well.

On keydown, do a setTimeout for 1 millisecond, that calls a function to check/change the length of your input box.

http://jsfiddle.net/rygar/e2wQM/

It seems to work really well, especially in a few places where your version doesn't (e.g. backspace, CTRL+V, or selecting a whole bunch of text and hitting delete)

Edit: Even setTimeout with a 0ms delay seems to work!

3
votes

You should use the property keyEventArgs.Key in the keydown function, this will return the numeric value that will depend on the system.

here is a link that has the different key codes for the different browsers and OS:

http://www.quirksmode.org/js/keys.html

3
votes

To detect in a keydown handler if a pressed key produces a single unicode character, you can use the ES6 unicode u flag for regular expressions.

We use the KeyboardEvent.key property, which returns the value of the pressed key. According to the docs:

If the pressed key has a printed representation, the returned value is a non-empty Unicode character string containing the printable representation of the key.

inputElement.addEventListener("keydown", ({ key }) => {
  if (/^.$/u.test(key)) {
    // `key` matches a single unicode character
  }
});

The solution does not handle pasting...

1
votes

This may not be the method that you're looking for, but you can just check the value of this.value.length in your keydown function. The return value is the length of the text in the input field BEFORE the new character is added. So if you check the length again in the keyup function, it will be greater if the user pressed a character, but the same if the user hit the shift key.

1
votes

I presume you are setting up a counter on the length of an input field, in which case you don't need to be so fancy, you can just keep assigning the length of the field to a variable, and when the user gets to your max length only allow them to press delete or backspace like so:

$("input:text").keypress(function() {
var current = $(this).val().length;
if (current >= 130) {
if (e.which != 0 && e.which != 8) {
e.preventDefault();
}
}
}

You can use the current variable to display the counter as well, or do maxlength - current to do a countdown of how many charachters are left

1
votes

Your goal of keeping the textbox larger than the text that has been entered into it.

I'd accomplish this by planning on having room for two additional characters (not one) in the text box. Then:

// pseudo-code.... 
old_len = textbox.value.length
keyUp function() {
  var new_len = textbox.value.length
  if (new_len != old_len) {
    old_len = new_len
    textbox.style.size = new_len + 2 // pseudo code.
  }
}

The advantage of the above is that you don't need to descend into the nether world of keycodes.