Search code examples
javascriptknockout.jsknockout-mapping-plugin

How do you use knockout mapping to map from a null value to an empty observable array?


I have a problem when I'm trying to use knockout mapping to map from a null json property to a knockout observable array. After the mapping has been performed, the knockout array is null.

So, for example, I have the following data:

var data = { 'Name': 'David', 'Modules': null };

And the following models:

var ModuleViewModel = function(data) {
    var self = this;
    ko.mapping.fromJS(data, {}, self);
};

var Model = function(data) {

    var self = this;

    var mapping = {
      'Modules': {
        key: function(module) {
            return ko.utils.unwrapObservable(module.Id);
        },
        create: function (options) {
            return new ModuleViewModel(options.data);
        }
      }
    };

    ko.mapping.fromJS(data, mapping, self);
};

Which is then applied to the view using:

var model = new Model(data);
ko.applyBindings(model);

And in the view, simply:

<span data-bind="text: Name"></span>
<span data-bind="text: Modules().length"></span>

What I would like to see (after my name) is the number of modules that have been mapped. If modules were present in the data:

var data = { 'Name': 'David', 'Modules': [ {'Id': '1', 'Name': 'Module1'} ] }

then the correct number is displayed on the view, but if the modules are set to null in the data then the number is not displayed at all as it does not know to map to an observable array.

Additionally, if I define an extra property on my view model to map to:

self.Modules = ko.observableArray;

this still does not get mapped as I expect as if you query self.Modules() after the mapping you get a null value returned when if you query a normal empty observable array you get [] returned.

What am I doing wrong in the mapping? I would just like a 0 value to be displayed when the modules data is null.

Click here to edit using jsbin


Solution

  • Here is a little update to your mappings that you might find useful.

    http://jsbin.com/zeguxiveyi/1

    It's important to note the following changes.

    For one, you weren't applying your mapping.

    // need to invoke the mapping object
    ko.mapping.fromJS(data, mapping, self);
    

    Secondly, it doesn't make a lot of sense to invoke an observable with null...it's like eating the hole of a donut, right?

    //Initialize your data with null modules :: ERROR
    //For the sake of having an empty observable array, we need to convert this null to an empty array
    var data = { 'Name': 'David', 'Modules': null };
    if(data.Modules == null) data.Modules = [];
    

    Thirdly, in the case where you may be getting nulls, you should go ahead and add a little short-circuit logic to prevent it...

    var mapping = {
     'Modules': {
        create: function (options) {
          if(options.data !== null)
            return new ModuleViewModel(options.data);
        }
      }
    };
    

    All in all, the mapping plugin is useful, but there's nothing magical about it...still need to be sure your data is reasonable.