How to exclude certain properties from Knockoutjs toJS()
Asked Answered
C

3

16

I have the following model:

var model = {
   A: 'One',
   B: 'Two',
   C: 'Three'
};

I bind various UI elements to these fields, which works great. However, I convert the model back to a JavaScript object so I can save any changes to the server:

var goingToServer = ko.toJS(model);

goingToServer will include properties A, B and C. However, let's say property C is a huge chunk of data that will never change. I'd like to avoid sending this back to the server.

Is there a way to make toJS() only include a predefined set of fields when converting a model back to a JavaScript object?

One thing I've been investigating is the Knockout Mapping plugin. It has a setting called include which is documented as such:

When converting your view model back to a JS object, by default the mapping plugin will only include properties that were part of your original view model, except it will also include the Knockout-generated _destroy property even if it was not part of your original object. However, you can choose to customize this array:

However, it appears this plugin doesn't work as documented, as ko.mapping.toJS() will still include A, B and C, even if I pass in an include array of ['A', 'B']. I'm guessing this feature is intended to include additional fields that were not in the original model.

Is there a way to exclude certain properties when converting a model back to a JavaScript object, short of doing something hacky such as generating the object and manually removing the properties I don't want before sending to the server?

Chintz answered 20/2, 2013 at 19:23 Comment(0)
N
29

Have you tried the ignore keyword? It has a similar usage to the include:

var mapping = {
    'ignore': ["propertyToIgnore", "alsoIgnoreThis"]
}
var viewModel = ko.mapping.toJS(data, mapping);

You could just use ignore when you do the original mapping of the server data, then it won't be on your object at all when you convert it back to JS.

Noam answered 20/2, 2013 at 19:30 Comment(8)
Have you tried? As the documentation doesn't mention whether ignore can be used when going back to JS objects.Noam
Yes, I just tried it. When I ignore the field Notes and when I call applyBindings, I immediately get an exception that the Notes field isn't found in the model.Chintz
I meant when you are converting your view model to send back up to your server, not when you're mapping to your view model. Does that make sense?Noam
The conversion is done with the toJS method, which doesn't have any parameters for fields to ignore. I'm looking for a way to exclude certain fields when Knockout internally converts a view model back to a JavaScript object.Chintz
Looking at the debug version of the mapping plugin, the toJS method does take a second parameter of options, and it does test to see if the ignore option is an array, so you should be able to pass it in.Noam
Hmm yea, it looks like you're on to something there.. I didn't see any documentation indicating that! Just tried it out, and it indeed works.Chintz
Yeah, the documentation concentrates pretty much on the fromJS method, that's why I resorted to looking at the source code!Noam
// Merge in the options used in fromJS options = fillOptions(options, rootObject[mappingProperty]);//pretty sure it worksBirthday
T
3

If we have a complex object model instance under vm.Payment observable reference as:

{
    "Summary": { 
        "Count": 12,
        "PaymentAmount": 1500,
        "PaymentMethod": "Cheque",
        "PaymentDate": "2015-04-08T22:38:48.552Z",
        "AgentOid": 1208795,
        "AgentName": "Asere Ware"
    }
    //[...]
}

and need to ignore only some inner properties, then:

var mapping = {
    'ignore': ['Summary.PaymentMethod', 'Summary.PaymentDate'] //avoid lost some initialized values.
};
// map updating existing observable (Payment) under my ViewMode (vm) with source "data" JSON object.
ko.mapping.fromJS(data, mapping, vm.Payment);

and this is the result for vm.Payment content, where only PaymentMethod and PaymentDate are keept:

{
    "Summary": { 
        "Count": 0,
        "PaymentAmount": 0,
        "PaymentMethod": "Cheque",
        "PaymentDate": "2015-04-08T22:38:48.552Z",
        "AgentOid": undefined,
        "AgentName": undefined
    }
    //[...]
}
Tithable answered 8/4, 2015 at 23:2 Comment(0)
C
2

Ok, I've figured out one solution that works though I'm hoping there's a better approach. The trick is to ignore these fields in the mapping plugin, then add them in manually as computed fields. Computed fields will never end up in the generated JavaScript object when toJS is called.

// Utility function to quickly generate a computed field
ko.readonly = function (value)
{
   return ko.computed(function ()
   {
      return value;
   });
};

// View Model
var ViewModel = function (project)
{
   var readonly = ['B', 'C', 'D']; // Fields I want to ignore
   var mapping = {
      'ignore': readonly //Read-only properties, we'll add these manually as computed fields
   };

   // Use Mapping plugin to make everything observable, except the fields above
   ko.mapping.fromJS(project, mapping, this);

   // Now loop through the read only array and add these fields in as computed
   for(var i = 0; i < readonly.length; i++)
   {
      var field = readonly[i];
      this[field] = ko.readonly(project[field]);
   }
}

I can now bind to these view model as normal:

ko.applyBindings(new ViewModel(data));
Chintz answered 20/2, 2013 at 21:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.