Can I conditionally change the character entered into an input on keypress?

Is it possible to change the character which has been entered on keypress, without doing it manually?

For example, if I want to force uppercase letters based on some condition, it'd be nice to do the following:

function onKeypressHandler(e)
{
    if ( condition )
    {
        e.which -= 32;
    }
}

But of course that doesn't work.

NOTE: This is not an across the board uppercasing, but only specific characters.

Maybe I want to say if ( e.which >= 97 && e.which <= 102 ) or if ( Wind.Direction == 'South' ) or whatever - the condition itself is not important, but the uppercasing must only apply to the current character not the entire input.


I can do it by manually appending the changed character, but this is an ugly and messy way of doing it, and probably slower than it could be.

function onKeypressHandler(e)
{
    if ( condition )
    {
        $j(this).val( $j(this).val() + String.fromCharCode( e.which - 32 ) );
        return false;
    }
}

A specific flaw with this method - if selecting all input text and entering a key, if it drops into this then it doesn't remove existing content, but simply appends to the content the user wanted removed. (Would need to investigating detecting any selected text to solve that, which makes this one even uglier.)

Can anyone provide a better solution?


Solution 1:

The following will do the job. It's based on an answer I wrote to another question. Customize the transformTypedChar function to suit your needs; my example capitalizes only the letters a-g.

If you need this on a textarea rather than an <input type="text"> then be aware that there are issues in IE <= 8 with line breaks that the following code doesn't handle for the sake of brevity. You can find the cross browser function for obtaining the selection within a textarea here: Is there an Internet Explorer approved substitute for selectionStart and selectionEnd?

function transformTypedChar(charStr) {
    return /[a-g]/.test(charStr) ? charStr.toUpperCase() : charStr;
}

document.getElementById("your_input_id").onkeypress = function(evt) {
    var val = this.value;
    evt = evt || window.event;

    // Ensure we only handle printable keys, excluding enter and space
    var charCode = typeof evt.which == "number" ? evt.which : evt.keyCode;
    if (charCode && charCode > 32) {
        var keyChar = String.fromCharCode(charCode);

        // Transform typed character
        var mappedChar = transformTypedChar(keyChar);

        var start, end;
        if (typeof this.selectionStart == "number" && typeof this.selectionEnd == "number") {
            // Non-IE browsers and IE 9
            start = this.selectionStart;
            end = this.selectionEnd;
            this.value = val.slice(0, start) + mappedChar + val.slice(end);

            // Move the caret
            this.selectionStart = this.selectionEnd = start + 1;
        } else if (document.selection && document.selection.createRange) {
            // For IE up to version 8
            var selectionRange = document.selection.createRange();
            var textInputRange = this.createTextRange();
            var precedingRange = this.createTextRange();
            var bookmark = selectionRange.getBookmark();
            textInputRange.moveToBookmark(bookmark);
            precedingRange.setEndPoint("EndToStart", textInputRange);
            start = precedingRange.text.length;
            end = start + selectionRange.text.length;

            this.value = val.slice(0, start) + mappedChar + val.slice(end);
            start++;

            // Move the caret
            textInputRange = this.createTextRange();
            textInputRange.collapse(true);
            textInputRange.move("character", start - (this.value.slice(0, start).split("\r\n").length - 1));
            textInputRange.select();
        }

        return false;
    }
};

Solution 2:

How about preventing default action and then triggering the keypress? Something like,

function onKeypressHandler(e)
{
    if ( condition )
    {
        e.preventDefault();
        // create new event object (you may clone current e)
        var ne = new jQuery.Event("keypress");
        ne.which = e.which - 32;
        $(e.target).trigger(ne);   // you may have to invoke with setTimeout
    }
}

Solution 3:

You've got to see this.. I was pretty happy with myself after getting it to work..

You obviously would want to include sufficient criteria to avoid going into a loop here.

The code below returns false when condition evaluates to true, but it fires the same event with a different charCode which will not return false.

document.getElementById("input1").onkeypress = Handler;
function Handler(e)
{
    e = e || window.event;
    if ( e.charCode == 97 )
    {
        var evt = document.createEvent("KeyboardEvent");
        evt.initKeyEvent("keypress",true, true, window, false, false,false, false, 0, e.charCode -32);
        this.dispatchEvent(evt);
        return false;
    }
    return true;
}

you could use fireEvent in IE... I used http://help.dottoro.com/ljrinokx.php and https://developer.mozilla.org/en/DOM/event.initKeyEvent for reference