I had to write some KnockoutJS code recently. The requirement was simple:
Extend an observable so that the observable:
can suppress alpha characters in a numeric input textbox;
formats percentages as “12.13%” when displayed, but stores the actual value as a number (not a string);
formats currency as “R123 456.00″, with spaces for thousand separators.
Credit must be given to Bryan Posas and Zeal for parts of this code that was sourced and subsequently mangled from here and here.
Disclaimer: there might be a simpler way to do this. (If there is, please let me know!)
First, I will explain how to use the percentage extender. It will be clear then how to use the others too. First, extend your observable property with the new extender as follows:
The percentage observable will now store its default value (50) as a plain-old number, and the extender will add a ‘.formatted’ property on top of the normal observable.
Bind to the formatted property on the front-end. This is to always show the formatted value, and for invalid characters to be stripped out automatically:
It’s as simple as that. Here is the percentage extender:
You would use the currency extender in a similar way:
Here is the currency extender:
Finally, the ensureNumeric extender just ensures that the non-numeric input is stripped from the user input. This extender does not add a ‘formatted’ property; it simply cleans the value in-place:
The ensureNumeric extender looks as follows:
These extenders make our viewModels a lot smaller and easier to understand. Normal knockoutjs validation using the validation plugin will continue to work, and will only kick in after the ‘cleansed’ value has been written by the extender. So you can still do additional required-field or min/max amount validation if required.
An alternative approach – if you prefer not to use an extender – is to extend knockout itself so that you can use a function as follows:
The addFormattedCurrency function is defined by adding to knockout’s “ko.subscribable.fn” object. When you refer to “this” within the function, you are actually referring to the observable property: