Knockout.js: time input format and value restriction

knockout.js

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);

Best Solution

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/

Related Question