11

My application owners want select text fields to be uppercase as if the caps lock is on. I am using a KnockoutJS viewmodel with observables for these fields. Is there a way I can in effect convert any user entered text to upper case?

I put a input event on the controls I wanted to change but found that although it works, the observables are not updated.

<input type="text" maxlength="80" data-bind="value: colorName, disable: $parent.isReadOnly, event: { 'input': toUpper }" />

toUpper: function (d, e) {
    if (e.target) {
        if (e.target.value) {
            e.target.value = e.target.value.toUpperCase();
        }
    }
}

I have also been thinking of putting a ucase CSS class on the controls I want to be upper case and then either on the client or server, saving those fields in upper case.

.ucase {
    text-transform: uppercase;
}
DaveB
  • 9,470
  • 4
  • 39
  • 66

2 Answers2

11

You could extend your observables -

<input data-bind="value: colorName, valueUpdate:'afterkeydown'" />

ko.extenders.uppercase = function(target, option) {
    target.subscribe(function(newValue) {
       target(newValue.toUpperCase());
    });
    return target;
};

var colorName = ko.observable().extend({ uppercase: true });

fiddle example - http://jsfiddle.net/kbFwK/

Basically whenever the value changes it will convert the value of the observable to upper case.

The disadvantage here is that it actually changes the value and would store it that way as well. You could always tack on a computed property onto the observable just for display purposes as well. You could do that using a ko.computed, a custom binding handler (since it is just for presentation), or something similar. If that is more of what you are looking for let me know with a comment.

Edit

Updated with afterkeydown - http://jsfiddle.net/kbFwK/2/

PW Kad
  • 14,953
  • 7
  • 49
  • 82
  • I need to have the input upper cased as the user types it in as if the caps lock key is on. I neglected to mention that in my initial post. I have updated my question. This is a good idea though. – DaveB Jul 21 '14 at 17:42
  • All you need to do is have it notify on key down. I added that to my fiddle. – PW Kad Jul 21 '14 at 17:48
  • I like using the extender. Thanks for the help! – DaveB Jul 21 '14 at 18:48
  • 1
    Is there a way to retain the cursor position when inserting text into and existing string? – DaveB Oct 08 '14 at 18:02
  • Good question, I am not sure. I would open up a new question with the JavaScript / Knockout tags to find out and get more exposure. – PW Kad Oct 08 '14 at 18:32
  • Thanks. I asked about retaining the cursor position in this question: http://stackoverflow.com/q/26263169/139917 – DaveB Oct 08 '14 at 18:57
  • Your code gets triggered twice. If I use your fiddle and type in `a`, the console shows `a A A A`. – comecme May 14 '15 at 10:53
  • @comecme It logs two values, and subscribes to value changes which means it logs once when it changes to `a` and again when it changes to `A` – PW Kad May 14 '15 at 12:21
1

This is a custom binding I wrote that makes sure both the input field and the observable is upper case. It should work just like a textInput binding.

ko.bindingHandlers.textInputUpperCase = {
    init: (element, valueAccessor) => {
        const observable = valueAccessor();
        let preventDoubleUpdate = false;

        function update(newValue) {
            if (preventDoubleUpdate) {
                preventDoubleUpdate = false;
            } else {
                switch(typeof newValue) {
                    //Undefined value will be displayed as empty in the input field.
                    case 'undefined':
                        element.value = '';
                        break;
                    //String value will be converted to upper case.
                    case 'string':
                        const upperCase = newValue.toLocaleUpperCase();
                        //Check if input field matches the observable. If not the change was made directly to the observable.
                        const match = element.value.toLocaleUpperCase() === upperCase;
                        //Remember the cursor position.
                        const selectionStart = element.selectionStart;
                        const selectionEnd = element.selectionEnd;
                        //Update the input text (will move the cursor to the end of the text).
                        element.value = upperCase;
                        //Move the cursor to it's original position if the change came from the input field.
                        if (match) {
                            element.selectionStart = selectionStart;
                            element.selectionEnd = selectionEnd;
                        }
                        //Update the observable if necessary and make sure it won't do a double update.
                        if (newValue !== upperCase) {
                            preventDoubleUpdate = true;
                            observable(upperCase);
                        }
                        break;
                    default:
                        element.value = newValue;
                }
            }
        }

        //Run the update function each time the observable has been changed
        observable.subscribe(update);

        //Initiate the observable and input
        update(observable());

        //Update the observable on changes of the text in the input field
        element.addEventListener('input', event => {
            observable(event.target.value);
        });
    }
};

A small caveat. If you write to the observable it will notify other subscribers twice. First when you write to it and then when it gets upper cased.

Conny Olsson
  • 1,567
  • 2
  • 11
  • 19