Knockout checked binding and select all children c

2019-09-09 13:23发布

问题:

I have a Knockout component which template prints two nested lists of items (checkboxes in my interface): a first foreach loop iterates through some "parents" items, a second (and nested) list iterates through each of the parents items and, if children are found, prints the children. The requirement is that if the user clicks on a parent item (that is, on a parent checkbox), all the children checkboxes become checked.

In my code, both parents and children checkboxes listen to an observable that is first set to false and then, when a parent is selected, is set to true - result: that parent's children get selected. My issue is that, this way, since every checkbox listen to the same observable, I end up with ALL the checkboxes selected, not only my selected parents + children, which is what I want.

Not sure how I should approach this issue some other way, here is my code:

ko.components.register("types",{ 
viewModel: function(){
var self = this;

// Data
self.types = ko.observableArray();
self.isPChecked = ko.observable(false);
nut.structuredDocumentTypes()
  .done(function (types) {
    self.types(types);
  });

// Behaviours
self.selectChildren = function(parent) {
  self.isPChecked(true);
  console.log(self.isPChecked(),parent);
}
},
template: 
'<div id="doctypes" class="wfp-form wfpTreeList">\
  <h2 class="hidden">Document Types</h2>\
  <ul class="wfpList" data-bind="foreach: types">\
    <!-- ko if: $index() > 0 -->\
    <li class="root">\
      <input\
        type="checkbox"\
        name="types"\
        class="wfp-checkbox"\
        data-select="multi"\
        data-bind="\
          checked: $component.isPChecked,\
          event: { change: $component.selectChildren },\
          value: $data.id">\
      <input\
        type="checkbox"\
        name="acc"\
        data-bind="\
          attr: { class: \'lvl\' + $data.id, id: \'acc\' + $data.id }">\
      <label\
        data-bind="attr: { for: \'acc\' + $data.id }">\
          <i class="ico-angle-down"></i>\
      </label>\
      <span data-bind="text: $data.label" class="root-title"></span>\
      <ul data-bind="attr: { \'data-lvl\': \'lvl\' + $index(), id: \'lvl\' + $data.id }">\
        <!-- ko foreach: $data.members -->\
          <li>\
            <label data-bind="attr: { for: \'dt-child-\' + $parent.id + \'-\' + $index() }">\
              <input\
                name="types"\
                type="checkbox"\
                data-bind="\
                  checked: $component.isPChecked,\
                  value: $data.id,\
                  attr: { id: \'dt-child-\' + $parent.id + \'-\' + $index() }">\
              <span data-bind="text: $data.label"></span>\
            </label>\
          </li>\
        <!-- /ko -->\
      </ul>\
    </li>\
  <!-- /ko -->\
  </ul>\
</div>'
});

UPDATE: JSON looks like this:

[{
"id": 4,
"members": [
    {
        "last_modified_date": "2016-08-04T14:59:25.958Z",
        "id": 31,
        "label": "Backgrounders & Information Notes"
    },
    {
        "last_modified_date": "2016-08-04T14:59:25.961Z",
        "id": 32,
        "label": "Biographies"
    },
],
"label": "Event-/Stakeholder related documentation",
},
{
"id": 2,
"members": [
    {
        "last_modified_date": "2016-08-04T14:59:25.875Z",
        "id": 1,
        "label": "Books"
    },
    {
        "last_modified_date": "2016-08-04T14:59:25.878Z",
        "id": 2,
        "label": "Briefs, Summaries, Synthesis, Highlights"
    },
],
"label": "Publications"
}]

Thanks all.

回答1:

You should slightly prepare your data by extending doctypes with computed property that depends on the state of respective members. Also members should contain observable property to track their checked state.

The best place to implement such preparations will be (I guess) the callback where you initialize types property with decoded JSON response:

nut.structuredDocumentTypes()
    .done(function (types) {
        // we'll prepare types here
        self.types(types);
    });

So using the approach from the fiddle mentioned by Roy J (and authored by user3297291) in comments you will get something like:

nut.structuredDocumentTypes()
    .done(function (types) {

        types.forEach(function(type){
            type.members.forEach(function(member){
                member.isSelected = ko.observable(false);
            });
            type.isSelected = ko.computed({
                read: function(){
                    return type.members.every(function(member){
                        return member.isSelected();
                    });
                },
                write: function(selected){
                    type.members.forEach(function(member){
                        member.isSelected(selected);
                    });
                }
            });
        });

        self.types(types); // insert prepared data
    });

The code is copied from my fiddle where I'd decomposed your component to the usual view model, and property names are different. Pay your attention to changes in data-bind attributes too!

https://jsfiddle.net/ostgals/z8vshLow/



标签: knockout.js