How to deal with incrementing and decrementing ko.observables floating point

Viewed 254

I have prepared a small jsfiddle here: jsfiddle.net/v8s176p2

Basically I want to increment and decrement observable in steps of 0.1 But because of number representation and precision issues, numbers are sometimes displayed as:1.3200000000000003...etc, tu I want them to only be displayed and have values with 1 decimal point

How would I code this exact same functionality in knockout.js, but by not using floating point. So my starting value is 10 and in HTML it's displayed as 1, if I decrmenet it (10-1 = 9) in html it's displayed as 0.9? This is probably better approach for my application, since I get this data from a server on embedded device, so it would be better to recieve the number as 200 and then divide it by 10, but how to set this up in ko.js in my example fiddle?

3 Answers

You could create a custom binding, something like this

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

There you can format the value however you like, and then use it like this:

<p data-bind="numericText: num1"></p>

Work with integers and do division in the text binding to display the floating point. This keeps the backing data as ints so errors won't accumulate.

data-bind="text: num1() / 10"

http://jsfiddle.net/xagup8vt/

Another way is to to add a computed observable to the observable property like

var vm = function(){
    var self = this;
    self.num1 = ko.observable();
    self.num1.formatted = ko.computed(function(){ return  Math.round((self.num1()*10),0)/10; });
}

This way the formatting is contained with the view model and converting the vm object to JSON will ignore the formatted computed function in the output.

To use it, <p data-bind="text: num1.formatted"></p>

Another option is to use a writable computed observable. I have included both techniques in the following snippet.

var increment = function(observable) {
  observable(observable() + 0.1);
};
var decrement = function(observable) {
  observable(observable() - 0.1);
};

var MyViewModel = function() {
  var self = this;
  self.num1 = ko.observable(0);
  self.num1.formatted = ko.computed(function() {
    return Math.round((self.num1() * 10), 0) / 10;
  });
  var num2BackingProperty = ko.observable(0);
  self.num2 = ko.computed({

    read: function() {
      return num2BackingProperty();
    },
    write: function(value) {
      num2BackingProperty(Math.round((value * 10), 0) / 10);
    },
    owner: self
  })

  this.incrementNum = function(observable) {
    // passing the observable by reference to the helper function
    increment(observable);
  };

  this.decrementNum = function(observable) {
    decrement(observable);
  };
}

ko.applyBindings(new MyViewModel());
h3 {
  font-weight: bold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/2.2.1/knockout-min.js"></script>
<button data-bind="click: incrementNum.bind($root, num1)">Increment Num1</button>
<button data-bind="click: decrementNum.bind($root, num1)">Decrement Num1</button>
<h3>
  Formatted
</h3>
<p data-bind="text: num1.formatted"></p>
<h3>
  Unformatted
</h3>
<p data-bind="text: num1"></p>
<br/>
<br/>
<button data-bind="click: incrementNum.bind($root, num2)">Increment Num2</button>
<button data-bind="click: decrementNum.bind($root, num2)">Decrement Num2</button>

<h3>
  Writable Computed
</h3>
<p data-bind="text: num2"></p>
<br/>
<h3>
  View Model Json
</h3>

<pre data-bind="text: ko.toJSON($root)"></pre>

http://jsfiddle.net/gonefishern/yo2nurf8/30/

Related