Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Formatting rules for numbers in KnockoutJS

Tags:

knockout.js

I have a viewModel with a bunch of numbers with lots of decimal places. If my bindings look like this:

    <tr>         <td data-bind="text: Date"></td>         <td data-bind="text: ActualWeight"></td>         <td data-bind="text: TrendWeight"></td>     </tr> 

Then, of course, the output has all the decimal places and is very unreadable. Changing the bindings to look like this solves the problem, but is very verbose and "noisy":

    <tr>         <td data-bind="text: Date"></td>         <td data-bind="text: ActualWeight().toFixed(1)"></td>         <td data-bind="text: TrendWeight().toFixed(1)"></td>     </tr> 

Note, this is one small snippet and having to add .toFixed(1) every place I bind a number leads to much more messy markup than what is shown here.

For everything except numbers, overriding the toString has been an effective way for me to control what the output looks like. Any suggestions on a way to tell knockout once, in some central way for my page what function to use to convert numbers into strings before they are added to the output?

For that matter, having a general purpose way to tell knockout how to format any type of value seems like it would be useful. Overriding Date.prototype.toString works but feels a little heavy handed since it may impact other uses of .toString besides just knockout's.

like image 420
Erv Walter Avatar asked Oct 09 '11 14:10

Erv Walter


People also ask

What is $data in knockout?

The $data variable is a built-in variable used to refer to the current object being bound. In the example this is the one of the elements in the viewModel.

What is KnockoutJS used for?

KnockoutJS is basically a library written in JavaScript, based on MVVM pattern that helps developers build rich and responsive websites.

How do you activate a KnockoutJS model?

To activate Knockout, add the following line to a <script> block: ko. applyBindings(myViewModel); You can either put the script block at the bottom of your HTML document, or you can put it at the top and wrap the contents in a DOM-ready handler such as jQuery's $ function.


1 Answers

There are a couple of ways that you can handle a situation like this one. You can either choose to address it through bindings or push it into your view model.

If your view model is created by the mapping plugin and you don't want to get into customizing the way that it is created, then you can consider using a custom binding that is a wrapper to the text binding to handle the formatting.

Something like (http://jsfiddle.net/rniemeyer/RVL6q/):

ko.bindingHandlers.numericText = {     update: function(element, valueAccessor, allBindingsAccessor) {        var value = ko.utils.unwrapObservable(valueAccessor()),            precision = ko.utils.unwrapObservable(allBindingsAccessor().precision) || ko.bindingHandlers.numericText.defaultPrecision,            formattedValue = value.toFixed(precision);          ko.bindingHandlers.text.update(element, function() { return formattedValue; });     },     defaultPrecision: 1   }; 

It certainly would be possible to create an even more generic binding (formattedText) that either inspected the value and formatted it using some overridable defaults or allowed you to pass in some formatting options ({ type: "numeric", precision: 2 }).

For your scenario, it sounds like the first option might be a good choice. However, if you want to push it into your view model, then you could create a special observable that can return both a formatted and a raw version of the value.

It could be something like (http://jsfiddle.net/rniemeyer/fetBG/):

function formattedNumericObservable(initialValue, precision) {     var _raw = ko.observable(initialValue),         precision = precision || formattedNumericObservable.defaultPrecision,                 //the dependentObservable that we will return         result = ko.dependentObservable({             read: function() {                return _raw().toFixed(precision);              },             write: _raw         });          //expose raw value for binding         result.raw = _raw;          return result;    } 

Now you could potentially bind against myValue and myValue.raw depending on your needs. Otherwise, you could flip it and return the raw value by default and expose a formatted dependentObservable. When an object like this is converted to JSON, it will lose any of the "sub-observables", so if you are sending this data back to a server that might be a consideration.

You could again make it more generic and create a formattedObservable that takes in some information about how to format the object.

Finally, 1.3 beta offers an extenders API. You could do something similar to above like: (http://jsfiddle.net/rniemeyer/AsdES/)

ko.extenders.numeric = function(target, precision) {     var result = ko.dependentObservable({         read: function() {            return target().toFixed(precision);          },         write: target      });      result.raw = target;     return result; }; 

Then, apply it to an observable like: var myValue = ko.observable(1.223123).extend({numeric: 1});

You could have the extender also just add a formatted dependentObservable to target instead of returning the dependentObservable itself.

like image 56
RP Niemeyer Avatar answered Sep 21 '22 10:09

RP Niemeyer