views:

104

answers:

6

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?

+3  A: 

Can you use css?

<input type="text" style="text-transform: uppercase;" />
Paul Whelan
That doesn't change the actual input value, and more importantly doesn't apply conditionally.
Peter Boughton
Could you do a to uppercase when you are processing the submitted value? What do you mean by conditionally?
Paul Whelan
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
thanks Peter :)
Paul Whelan
A: 

Not really sure what you want but will this work?

$('#my_element').keyup(function(){ $(this).val().toUpperCase(); });

or use a sub string to get the last character pressed and do toUpperCase() on that?

(psst... you can use keydown or keypress too).

Thomas Clayson
Again, that's not conditional, and the toUpperCase isn't really an improvement over the String.fromCharCode() method.
Peter Boughton
what do you mean by conditional?
Thomas Clayson
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
A: 

Peter,

You might find some inspiration here:

http://www.maconstateit.net/tutorials/JSDHTML/JSDHTML15/jsdhtml15-05.htm

basically walks around the various ways to look at the keypress events and functions around that 'area'.

jim
+1  A: 

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
    }
}
VinayC
Won't work. You can't simulate the effects of a keypress in the browser.
Tim Down
Doh! This seemed promising until I saw Tim's comment. Nice idea anyway. :(
Peter Boughton
@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?
VinayC
@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 - here's the link: http://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.
VinayC
@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."*
Tim Down
Apparently you can simulate a keypress.. check my answer below. (cant guarantee cross browser, but works in FF :) )
Ravindra Sane
@Ravindra: Yes, it works in Firefox, but sadly only in Firefox.
Tim Down
+2  A: 

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. I can provide code that does work in textareas if necessary.

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;
    }
};
Tim Down
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.
Peter Boughton
OK. I just added a note about using this with textareas.
Tim Down
+1 nice answer very clever
Paul Whelan
+1  A: 

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

Ravindra Sane
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.
Tim Down
not exactly.. IE also supports firing events.. instead of dispatchEvent, you use fireEvent and to create the event, you use object.createEventObject. See http://help.dottoro.com/ljhlvomw.php. I am on Linux so cant help you with the code/testing. But this seems doable
Ravindra Sane
Seems the code for crossbrowser mouse event handling is already provided in that link.
Ravindra Sane
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
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
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
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.
Ravindra Sane
@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.
Tim Down