Using KnockoutJS automatic binding

2019-09-11 15:40发布

问题:

I am trying to bind a List=>List=>List=>List object into a Knockout JS view model.

My data from $getJSON is as follows

I tried the mapping plugin to create a viewmodel like :- var viewModel = ko.mapping.fromJS(d.organisationsData);

and the problem is I dont know why (/whether) mapping is happening properly.

I am unable to use data-bind to iterate over this List and innerList properties

This is my ViewModel object

I tried iterating using data-bind: for-each ListOfOrg and it is showing Binding error ListOfOrg is not found.

<ul data-bind="foreach:ListOfOrg">

I am new to KnockoutJS and so I guess there is something pretty silly I am doing and if yes please point me in the right direction.

回答1:

Ok let's go through a simple example imagining we want to show some activities on a page.

  1. Let's say your data returned is an enumeration of activities which all have certain properties, then your activity might look like something like this:
var ActivityType = function (data) {
       var self = this;
       self.Id = ko.observable(data.Id);
       self.Name = ko.observable(data.Name);
       self.Description = ko.observable(data.Description);
       self.Selected = ko.observable(data.Selected); };

Then you have a viewmodel which has a property called Activities which needs to mapped to this activity Model, so when you get your activities enumeration it automatically produces the same of number New ActivityTypes for you in that array.

var activitiesViewModel = {};

so we define a mapping utility for that (simply saying new ActivityTypes are the ones with a different Id so then create a new one):

var ActivityTypeDataMappingOptions = {
        key: function (data) {
            return data.Id;
        }, create: function (options) {
            return new ActivityType(options.data, null);
        }
    }

So now we need the activities property on the view model:

activitiesViewModel.Activities = mapping.fromJS([]);

This means make an observable array based on the mapping data.

Let's say you have a datacontext defined that gives you your data then in the callback of that:

datacontext.getActivities(function (data) {
    mapping.fromJS(data.Activities, ActivityTypeDataMappingOptions, activitiesViewModel.Activities);
    ko.applyBindings(activitiesViewModel);
});

mapping.fromJS takes 3 params, first one the collection thats going to have the data, second one the mapping utility we defined so make new activities out of and last the property on the view model that holds the observable collection.

So now you can use this like:

<ul data-bind="foreach: Activities">
     <li><input type='text' data-bind="value: Name" /></li>
</ul>
<button data-bind="click: updateActivities">Update Activities</button>
  • note: In this example view model is a singleton but you might want to depending on your usage make it a instantiatable object using the revealing prototype pattern. Then when you are calling ko.applyBindings you need to pass new activitiesViewModel() as the param.

Now for updating and sending back the results we need to define updateActivities:

activitiesViewModel.updateActivities = function(){
     // Let's say our data context also has a setActivities method that requires a payload of activities object for Post
    datacontext.setActivities({
        // as activitiesViewModel.Activities is an observable array then this will always have the latest values which might have changed in your UI
        activities : ko.toJSON(activitiesViewModel.Activities)
    }, function(data){
     // Lets say the POST will return true if the call was successful
        if (data) alert("all done");
    });
};
  • You can also bind this function to change even of the input but that would be far too many API calls, so I don't recommend it.

  • If you are making lots of these models which is then used by a view model you will quickly realize you are doing a lot of modelling that is already done on the server side. If you write some server side code that automatically generates js files containing the model e.g. ActivityType in my example, you can simply use requireJS to import them in the view model file and then use them across the application. This will allow property name changes and updates to be automatically available on the front end by only updating the model in the backend.