简体   繁体   中英

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

You could put a computed observable for null checking and to keep it as clean as possible, like this:

var ModuleViewModel = function(data) {
    var self = this;
    ko.mapping.fromJS(data, {}, self);
    //After mapping the object, add the computed observable here
    self.numberOfModules = ko.computed(function() {
        return this.Modules ? this.Modules().length : 0;
    }, self);
};

Then in your binding:

<span data-bind="text: numberOfModules">

This is specially handy if the computed is used in many different places in the HTML in order to avoid duplicating null checks everywhere.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM