Updating Sub Properties in Knockout JS mapping plu

2019-07-20 14:03发布

问题:

I'm having an issue with knockout js and mapping plugin with a hierarchical view model

My viewmodel is structured somewhat like this:

VM = {
    members:[
        {
            name:"name 1",
            volunteering:[{...},{...},{...}]
        },
        {
            name:"name 1",
            volunteering:[{...},{...},{...}]
        }
    ]
}

Each member is in a tab, and each tab has a grid of volunteering activities. Clicking on an item in the grid pops up a dialog box to edit the volunteering activity. At this point I clone the object to facilitate 'cancel edit' functionality

var Volunteer = {};
var koContext=ko.contextFor(this);
Volunteer = ko.mapping.toJS(koContext.$data);  //plain js volunteer
Volunteer.index=koContext.$parent.EventVolunteers().indexOf(koContext.$data);  //index of volunteer in member volunteer array
ko.applyBindings(ko.mapping.fromJS(Volunteer),$("#dialog-EditVolunteer")[0]); //bind new volunteer obj to dialog

Up to this point seems ok, clicking save on the dialog causes the issue.

var volunteer = ko.mapping.toJS(ko.contextFor(this).$data);
ko.mapping.fromJS(volunteer,{},ko.contextFor(currentTab).$data.EventVolunteers()[volunteer.index]);

At this point the properties get updated in the viewmodel, but not in the grid on the main screen.

It appears ko.mapping.fromJS is replacing the observable rather than updating it.

回答1:

Personally I'm a fan of creating models like so. The abstraction here just makes more sense.

function VolunteerInfo(data){
  var self = this;
  self.activitiesName = ko.observable(data.name);
  // any other info not all of it has to be mapped 
  // unless you plan on sending it back.
}
function MembersInfo(data){
  var self = this;
  self.name = ko.observable(data.name)// w.e it is labeled as under json
  self.volunteering = ko.observableArray([]);
  var mappedVolunteers = $.map(data.volunterring, function(item){
    var volunteer = new VolunteerInfo(item);
    return volunteer;
  });
  self.volunterring(mappedVolunteers);
}
function VM(){
  var self = this;
  self.members = ko.o
  var request = $.getJSON("webservice address");
  request.done(function () {
    var mappedMembers = $.map(data.volunterring, function(item){
      var member = new MemberInfo(item);
      return member;
    });
    self.members(mappedMembers);
  }
}
// apply bindings to VM ect ect.


回答2:

I'm not certain I wholly understand exactly what you're doing.

But I was attempting to do undo functionality in a list of items, and I used this on the array:

observableArray.replace(newData, ko.mapping.fromJS(original))

I get the new data as an argument of the click handler.

When I store the original, I basically have this:

        //Dupe check
        self.undoCache.Emails.pop(jQuery.grep(self.undoCache.Emails, function (element, index) { return element.Id == data.Id(); })[0]);
        //Store original
        self.undoCache.Emails.push(ko.mapping.toJS(data));

The "Emails" are the objects I'm editing. I only store the original data, not the observable. That allowed me to use replace. I'm not certain how correct that is, per se, but canceling works for me.



回答3:

My eventual solution in this case was to set the properties on the original VM, with the edited values from the 'cloned' viewmodel.

For new projects however, I now use a knockout plugin