Search code examples
knockout.jsknockout-mapping-plugin

KnockoutJS Mapping not


I have viewmodel with an array of diagnosis codes. In my html I have a button data-bound to a click that adds a blank diagnosis code to the array. This all works.

I use the button to add diagnosis codes. This works.

I am receiving JSON from an external source, and then trying to wrap it in an observable. Because it is from an external source, it does not have functions that I can bind to, so I am binding to functions that are not part of viewModel.

When I try to convert the object back to a JSON string, the new diagnosis codes are empty strings (the default value I add when the new codes).

Here's the code:

 <h3>Diagnosis Codes<input type="button" value="Add" data-bind="click:AddDiagnosisCode"/></h3>
    <div data-bind="foreach:DiagnosisCodes">
        <div><input type="text"  data-bind="value:$data"/><input type="button" value="Remove" data-bind="click: function(data, event) { RemoveDiagnosisCode($parent, data, event) }"/>
        </div>
    </div>

    <script type="text/javascript">
        function AddDiagnosisCode(item)
        {
            item.DiagnosisCodes.push("");
        }

        function RemoveDiagnosisCode(item, code) {
            item.DiagnosisCodes.remove(code);
        }

        function submitJSON() {
            var test= ko.mapping.toJSON(viewModel); // have also tried ko.toJSON(viewModel)
            alert(test);
        }

        var vm = {
           "DiagnosisCodes": ["2345","6789"]
        };

        var viewModel = ko.mapping.fromJS(vm);     
         ko.applyBindings(viewModel);
    </script>

So for example, if I click Add and type the code ABCD and then call submitJSON, I see :

{
    DiagnosisCodes:["2345","6789",""]
}

I am expecting

{
    DiagnosisCodes:["2345","6789","ABCD"]
}

UPDATE: it looks like the mapping plugin converts arrays of simple types (strings, ints, etc) into observables instead of observableArrays. So I modified (with pax's help) the javascript to convert the string arrays to arrays of objects holding the strings, before calling the mapping. Then when converting to JSON, converting them back:

<h3>Diagnosis Codes<input type="button" value="Add" data-bind="click:AddDiagnosisCode"/></h3>
    <div data-bind="foreach:DiagnosisCodes">
        <div><input type="text"  data-bind="value:code"/><input type="button" value="Remove" data-bind="click: $root.RemoveDiagnosisCode"/>
        </div>
    </div>
<button onclick="submitJSON()">Show</button>

function submitJSON() {
            //convert to JS object first
            var test= ko.mapping.toJS(viewModel);
            UnMapCodes(test);

            alert(ko.toJSON(test));
        }

           function Code(code)
        {
            var self=this;
            self.code = code;
        }

        function MapToCodes(obj)
        {
            var codes=[];

            for(var c=0; c<obj.DiagnosisCodes.length; c++)
            {
                codes.push(new Code(obj.DiagnosisCodes[c]));
            }

            obj.DiagnosisCodes=codes;
        }

        function UnMapCodes(obj)
        {
            var codes=[];

            for(var c=0; c<obj.DiagnosisCodes.length; c++)
            {
                codes.push(obj.DiagnosisCodes[c].code);
            }

            obj.DiagnosisCodes=codes;
        }


        var vm = {            
           "DiagnosisCodes": ["2345","6789"]
        };

        vm.AddDiagnosisCode= function (item)
            {
                self=this;
                self.DiagnosisCodes.push(new Code(""));
            };
        vm.RemoveDiagnosisCode= function (code) {
                self=this;
                self.DiagnosisCodes.remove(code);
            };

        MapToCodes(vm);

        var viewModel = ko.mapping.fromJS(vm);     
         ko.applyBindings(viewModel);

Solution

  • There were several problems with the code, restructured it a bit (fiddle: http://jsfiddle.net/VX9f2/2/):

    html:

    <button data-bind="click:submitJSON">submit json</button>
    <h3>Diagnosis Codes<input type="button" value="Add" data-bind="click:AddDiagnosisCode"/></h3>
        <div data-bind="foreach:DiagnosisCodes">
            <div><input type="text"  data-bind="value:code"/><input type="button" value="Remove" data-bind="click: $root.RemoveDiagnosisCode"/>
            </div>
        </div>
    

    js:

    var Code = function(code){
        var self = this;
        self.code = ko.observable(code);
    }
    
    var VM = function(){
        var self = this;
        self.DiagnosisCodes = ko.observableArray([
            new Code("2345"),
            new Code("6789")]);
        self.AddDiagnosisCode = function() {
            self.DiagnosisCodes.push(new Code(""));
        }
    
        self.RemoveDiagnosisCode = function(item) {
             self.DiagnosisCodes.remove(item);
        }
    
        self.submitJSON = function() {
            var test= ko.mapping.toJSON(self.DiagnosisCodes); // have also tried ko.toJSON(viewModel)
            alert(test);
            }
    
    }
           //var viewModel = ko.mapping.fromJS(new VM());     
             ko.applyBindings(new VM());
    

    For clarity, I replaced the initial ko.mapping usage with constructors. It you still wish to use it, let me know.