Needing to format observable values (decimal)

1,343 views
Skip to first unread message

Dean Friedland

unread,
Apr 5, 2018, 3:38:07 PM4/5/18
to KnockoutJS
I have not used Knockout in a while and am running into an issue when trying to trim the input value of a form field to two decimal places. I created the below computed observable and am subscribing to it by the observable that I want to update with the trimmed value. Can anyone tell me what I am doing wrong?


function createViewModel() {
 
var self = {};

 
self.SalesRepId = ko.observable().extend({ required: true });
 
self.PriceAdvanced = ko.observable("").extend({ required: true, min: 1, max: 200 });

 
self.decimalValue = ko.computed(function () {
   
var num = self.PriceAdvanced().slice(0, (self.PriceAdvanced().indexOf(".")) + 3);
   
return num;
 
}).extend({ notify: "always" });

 
self.PriceAdvanced.subscribe(self.decimalValue);

 
return self;
}


<div class="form-group col-xs-12">
         
<label class="label-col col-xs-4 control-label labelFormat" for="PriceAdvanced"><span class="warn">* </span>Advanced Criminal History Search</label>
         
<div class="col-xs-8">
           
<input class="form-control max225" type="text" id="PriceAdvanced" name="PriceAdvanced" data-bind="textInput: PriceAdvanced" size="23" placeholder="$0.00" />
         
</div>
</div>

Andrew Vickers

unread,
Apr 5, 2018, 4:14:40 PM4/5/18
to KnockoutJS
If you use a computed observable, you should not manually subscribe.  Computed observables automatically track changes in their underlying observable values.

If the computed observable is not working in this instance, for whatever reason, then you can handle the subscription manually like this

self.decimalValue = ko.observable(0).extend({ notify: "always" });

  
self.PriceAdvanced.subscribe(function(val) {
    self.decimalValue(val.
slice(0, (self.PriceAdvanced().indexOf(".")) + 3))
  });


Although either way this seems pretty problematic to me without some sanity checking.  What if they input an integer instead of a floating point value?  What if they only input 1 decimal place?

I think a better option is just to use HTLM5 input rules if possible.  Something like:
<input type="number" min="0.00" max="200.00" step="0.01" data-bind="textInput: PriceAdvanced" />


Dean Friedland

unread,
Apr 5, 2018, 11:05:27 PM4/5/18
to KnockoutJS
Thanks but I ended up going with the below code. It works very well but unfortunately it negates my min/max KO validation that I set on the observable. 

Any way to fix that issue?

  function formatCurrency(symbol, value, precision) {
   
return (value < 0 ? "-" : "") + symbol + Math.abs(value).toFixed(precision).replace(/(\d)(?=(\d{3})+\.)/g, "$1,");
 
}


 
function rawNumber(val) {
   
return Number(val.replace(/[^\d\.\-]/g, ""));
 
}


  ko
.bindingHandlers.currency = {
    symbol
: ko.observable("$"),
    init
: function (element, valueAccessor, allBindingsAccessor) {
     
//only inputs need this, text values don't write back
     
if ($(element).is("input") === true) {
       
var underlyingObservable = valueAccessor(),
          interceptor
= ko.computed({
            read
: underlyingObservable,
            write
: function (value) {
             
if (value === "") {
                underlyingObservable
(null);
             
} else {
                underlyingObservable
(rawNumber(value));
             
}
           
}
         
});
        ko
.bindingHandlers.value.init(element, function () {
         
return interceptor;
       
}, allBindingsAccessor);
     
}
   
},
    update
: function (element, valueAccessor, allBindingsAccessor) {
     
var symbol = ko.unwrap(allBindingsAccessor().symbol !== undefined ? allBindingsAccessor().symbol : ko.bindingHandlers.currency.symbol),
        value
= ko.unwrap(valueAccessor());
     
if ($(element).is("input") === true) {
       
//leave the boxes empty by default
        value
= value !== null && value !== undefined && value !== "" ? formatCurrency(symbol, parseFloat(value), 2) : "";
        $
(element).val(value);
     
} else {
       
//text based bindings its nice to see a 0 in place of nothing
        value
= value || 0;
        $
(element).text(formatCurrency(symbol, parseFloat(value), 2));
     
}
   
}
 
};

Andrew Vickers

unread,
Apr 6, 2018, 1:47:43 PM4/6/18
to KnockoutJS
The easiest and most performant approach would be to use the html5 input syntax that I recommended above.  

If you truly need to support pre-HTML5 browsers, then I would imagine that the next easiest approach would be to leave that custom binding alone and validate the value in an onSubmit handler rather than coerce it at the time of entry.
Reply all
Reply to author
Forward
0 new messages