Knockout checked binding and select all children c

2019-09-09 12:54发布

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.

标签: knockout.js
1条回答
孤傲高冷的网名
2楼-- · 2019-09-09 13:58

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/

查看更多
登录 后发表回答