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.
You should slightly prepare your data by extending doctypes with
computed
property that depends on the state of respective members. Also members should containobservable
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:So using the approach from the fiddle mentioned by Roy J (and authored by user3297291) in comments you will get something like:
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/