Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to exclude certain properties from Knockoutjs toJS()

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?

like image 660
Mike Christensen Avatar asked Feb 20 '13 19:02

Mike Christensen


3 Answers

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.

like image 162
Paul Manzotti Avatar answered Nov 10 '22 20:11

Paul Manzotti


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
    }
    //[...]
}
like image 21
Asereware Avatar answered Nov 10 '22 19:11

Asereware


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));
like image 43
Mike Christensen Avatar answered Nov 10 '22 21:11

Mike Christensen