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

15,532

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

Share:
15,532

Related videos on Youtube

Peter Boughton
Author by

Peter Boughton

About Me: peterboughton.net Development: sorcerersisle.com

Updated on January 06, 2020

Comments

  • Peter Boughton
    Peter Boughton over 4 years

    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?

    • Andy E
      Andy E over 13 years
      How would you handle pasting, undo/redo, dragging and dropping or other instances of multiple characters being entered at the same time, or isn't that important?
    • Peter Boughton
      Peter Boughton over 13 years
      Andy, that's not hugely important here, but would still be useful to handle - I guess it'd have to involve walking the string manually, within the onchange event?
    • Andy E
      Andy E over 13 years
      Or the oninput and onpropertychange events. I outlined these on my blog a short while ago - whattheheadsaid.com/2010/09/… and I have an almost complete jQuery plugin for it in the works.
  • Peter Boughton
    Peter Boughton over 13 years
    That doesn't change the actual input value, and more importantly doesn't apply conditionally.
  • Peter Boughton
    Peter Boughton over 13 years
    Again, that's not conditional, and the toUpperCase isn't really an improvement over the String.fromCharCode() method.
  • Paul Whelan
    Paul Whelan over 13 years
    Could you do a to uppercase when you are processing the submitted value? What do you mean by conditionally?
  • Thomas Clayson
    Thomas Clayson over 13 years
    what do you mean by conditional?
  • Peter Boughton
    Peter Boughton over 13 years
    I've updated the question to clarify - I only want to (maybe) uppercase the current character, not the whole string - and if I uppercase it depends on an if condition (the precise check is unimportant to the question).
  • Tim Down
    Tim Down over 13 years
    Won't work. You can't simulate the effects of a keypress in the browser.
  • Peter Boughton
    Peter Boughton over 13 years
    I've updated the question to clarify - I only want to (maybe) uppercase the current character, not the whole string - and if I uppercase it depends on an if condition (the precise check is unimportant to the question).
  • Peter Boughton
    Peter Boughton over 13 years
    Doh! This seemed promising until I saw Tim's comment. Nice idea anyway. :(
  • VinayC
    VinayC over 13 years
    @Tim, thanks for info. BTW, example from jquery docs uses .keypress() to trigger the event. So what happens in such case - does only event handler get invoked w/o control receiving the character?
  • Peter Boughton
    Peter Boughton over 13 years
    Thanks Tim, that looks like it's also handling the new text selection problem I've just found. Will try this out and see how it does.
  • Tim Down
    Tim Down over 13 years
    @VinayC: Could you post the jQuery docs link? It may well be possible to simulate a keypress event so that it fires on a particular element (I haven't tried to do this) but it won't perform the usual browser action (such as making a character appear in an input).
  • Tim Down
    Tim Down over 13 years
    OK. I just added a note about using this with textareas.
  • VinayC
    VinayC over 13 years
    @Tim - here's the link: api.jquery.com/keypress. It uses keypress() to trigger the event handler. The example does not put any character in input but then it is not specifying any event object.
  • Tim Down
    Tim Down over 13 years
    @VinayC: Thanks. Their documentation could do with being clear about the fact that triggering a keypress event won't actually trigger the associated browser behaviour. All you get is the following vague disclaimer in the docs for trigger(): "Although .trigger() simulates an event activation, complete with a synthesized event object, it does not perfectly replicate a naturally-occurring event."
  • Ravindra Sane
    Ravindra Sane over 13 years
    Apparently you can simulate a keypress.. check my answer below. (cant guarantee cross browser, but works in FF :) )
  • Tim Down
    Tim Down over 13 years
    Interesting. This only works in Mozilla, however. Fortunately its scope seems limited: trying to trigger keyboard shortcuts such as Ctrl-B (for bookmarks), Ctrl-A (select all) doesn't work. I wasn't aware you could do this much though.
  • Ravindra Sane
    Ravindra Sane over 13 years
    not exactly.. IE also supports firing events.. instead of dispatchEvent, you use fireEvent and to create the event, you use object.createEventObject. See help.dottoro.com/ljhlvomw.php. I am on Linux so cant help you with the code/testing. But this seems doable
  • Ravindra Sane
    Ravindra Sane over 13 years
    Seems the code for crossbrowser mouse event handling is already provided in that link.
  • Tim Down
    Tim Down over 13 years
    I've tested the IE version and while it fires a new event correctly, it doesn't perform the associated browser behaviour such as inserting a character in an input.
  • Tim Down
    Tim Down over 13 years
    I also tested the original code in recent versions of Chrome, Safari and Opera, with the same result. This means that of the major browsers, your approach is only viable in Firefox.
  • Tim Down
    Tim Down over 13 years
    Finally, in Firefox your code gets into an infinite loop: the keypress event you fire then causes the same event handler to be called again for ever, or at least until Firefox throws a "too much recursion" error.
  • Tim Down
    Tim Down over 13 years
    @Ravindra: Yes, it works in Firefox, but sadly only in Firefox.
  • Ravindra Sane
    Ravindra Sane over 13 years
    For Chrome etc use initKeyboardEvent .. I did mention that there needs to be enough checking to avoid infinite loop in the second line of the answer itself. I wasn't writing production-quality code.. just giving the general idea. Accept it.. it is possible to fire another event with different parameters, if the current handler finds that the parameters dont meet your criteria.
  • Tim Down
    Tim Down over 13 years
    @Ravindra: the point is that the only browser where it is possible to make a character actually appear in the text box by firing a new keypress event is Firefox.
  • Ram
    Ram almost 7 years
    Maybe you can use this gist gist.github.com/termi/4654819 to make a keypress event. (Refer to stackoverflow.com/a/14561303/1191999)