32

When I bind numeric data in my view model using knockout, it displays correctly but changes the data type to string if the user changes the input tag value. The problem with submitting string is that the server expects a numeric value with no implicit conversion available.

Any way to tell knockout to maintain the data type of the original property value?

My example code that matches the view model names to the input tag names. I use unobtrusive knockout to do the bindings, which works fine.

// Bind the first object returned to the first view model object
// FNS is the namespace, VM is the view model
FNS.VM.Items[0] = ko.mapping.fromJS(data.Items[0]);

// For each property found, find the matching input and bind it
$.each(FNS.VM.Items[0], function (indexInArray, valueOfElement) {
    var attrName = indexInArray;
    var attrValue;
    if (typeof valueOfElement == "function")
        attrValue = valueOfElement();
    else
        attrValue = valueOfElement;

    var a = $('input[name="' + attrName + '"][type="checkbox"]');
    if (a.length)
        a.dataBind({ checked: 'VM.Items[0].' + attrName });

    var b = $('input[name="' + attrName + '"][type="radio"]');
    if (b.length)
        b.dataBind({ checked: 'VM.Items[0].' + attrName });

    var c = $('input[name="' + attrName + '"][type="text"]');
    if (c.length)
        c.dataBind({ value: 'VM.Items[0].' + attrName });
});
ko.applyBindings(FNS);
Community
  • 1
  • 1
Zachary Scott
  • 20,968
  • 35
  • 123
  • 205

1 Answers1

49

Here is a thread with a few different techniques to keep the value numeric: https://groups.google.com/d/topic/knockoutjs/SPrzcgddoY4/discussion

One option is to push this concern into your view model and create a numericObservable to use instead of a normal observable. It might look like:

ko.numericObservable = function(initialValue) {
    var _actual = ko.observable(initialValue);

    var result = ko.dependentObservable({
        read: function() {
            return _actual();
        },
        write: function(newValue) {
            var parsedValue = parseFloat(newValue);
            _actual(isNaN(parsedValue) ? newValue : parsedValue);
        }
    });

    return result;
};

Sample: http://jsfiddle.net/rniemeyer/RJbdS/

Another option is to handle this with a custom binding. Instead of using the value binding, you can define a numericValue binding and use it instead. It could look like:

ko.bindingHandlers.numericValue = {
    init : function(element, valueAccessor, allBindings, data, context) {
        var interceptor = ko.computed({
            read: function() {
                return ko.unwrap(valueAccessor());
            },
            write: function(value) {
                if (!isNaN(value)) {
                    valueAccessor()(parseFloat(value));
                }                
            },
            disposeWhenNodeIsRemoved: element 
        });

        ko.applyBindingsToNode(element, { value: interceptor }, context);
    }
};

Sample: http://jsfiddle.net/rniemeyer/wtZ9X/

RP Niemeyer
  • 114,592
  • 18
  • 291
  • 211
  • 1
    Your answer lead me to [this answer](http://www.knockmeout.net/2011/03/reacting-to-changes-in-knockoutjs.html) under **Writable dependentObservables**, which collectively answers my question. Thanks – Zachary Scott Dec 06 '11 at 06:48
  • Quick note: Reading through the Knockout commits I see RP Niemeyer referenced his own answer here with a quick note: https://github.com/knockout/knockout/pull/1334 There's a more supported way of invoking the line ko.bindingHandlers.value.init... – Ian Yates Aug 16 '14 at 01:34
  • 5
    Anyone reading this, please note dependentObservable() is now called computed() – Quango Sep 04 '14 at 16:34
  • 2
    Beware: Further to @IanYates binding comment. ko 3.2.0 kbh value breaks any use of update. ko.bindingHandlers.value.update calls are ignored. If you need to change the value read from the model, use the interceptor.read function. e.g. to coerce to a string return underlyingObservable().toString(); – RockResolve Nov 06 '14 at 23:13
  • 3
    @RockResolve - updated with an approach that is more compatible with current KO. – RP Niemeyer Nov 07 '14 at 15:54
  • Is the `disposeWhenNodeIsRemoved` necessary in the `ko.numericObservable`? There is no `element` in scope so it will be `undefined`. I can see the case for the binding handler however which could probably use it. – Jeff Mercado Nov 07 '14 at 16:45
  • @JeffMercado - Thanks- I inadvertently added it to the wrong block of code. – RP Niemeyer Nov 07 '14 at 19:14