diff --git a/addon/-private/collapse-tree.js b/addon/-private/collapse-tree.js
index 286702ded..584105972 100644
--- a/addon/-private/collapse-tree.js
+++ b/addon/-private/collapse-tree.js
@@ -46,12 +46,13 @@ export const TableRowMeta = EmberObject.extend({
// eslint-disable-next-line ember/use-brace-expansion
isSelected: computed(
- '_tree.{selection.[],selectionMatchFunction}',
+ '_tree.{selection.[],selectionMatchFunction,selectingParentSelectsChildren}',
'_parentMeta.isSelected',
function() {
let rowValue = get(this, '_rowValue');
let selection = get(this, '_tree.selection');
let selectionMatchFunction = get(this, '_tree.selectionMatchFunction');
+ let selectingParentSelectsChildren = get(this, '_tree.selectingParentSelectsChildren');
if (isArray(selection)) {
return this.get('isGroupSelected');
@@ -60,17 +61,22 @@ export const TableRowMeta = EmberObject.extend({
let isRowSelection = selectionMatchFunction
? selectionMatchFunction(selection, rowValue)
: selection === rowValue;
- return isRowSelection || get(this, '_parentMeta.isSelected');
+
+ // Only consider parent selection if selectingParentSelectsChildren is true
+ let parentIsSelected = selectingParentSelectsChildren && get(this, '_parentMeta.isSelected');
+
+ return isRowSelection || parentIsSelected;
}
),
isGroupSelected: computed(
- '_tree.{selection.[],selectionMatchFunction}',
+ '_tree.{selection.[],selectionMatchFunction,selectingParentSelectsChildren}',
'_parentMeta.isSelected',
function() {
let rowValue = get(this, '_rowValue');
let selection = get(this, '_tree.selection');
let selectionMatchFunction = get(this, '_tree.selectionMatchFunction');
+ let selectingParentSelectsChildren = get(this, '_tree.selectingParentSelectsChildren');
if (!selection || !isArray(selection)) {
return false;
@@ -79,7 +85,12 @@ export const TableRowMeta = EmberObject.extend({
let isSelectionMatch = selectionMatchFunction
? selection.filter(item => selectionMatchFunction(item, rowValue)).length > 0
: selection.includes(rowValue);
- return isSelectionMatch || get(this, '_parentMeta.isGroupSelected');
+
+ // Only consider parent selection if selectingParentSelectsChildren is true
+ let parentIsSelected =
+ selectingParentSelectsChildren && get(this, '_parentMeta.isGroupSelected');
+
+ return isSelectionMatch || parentIsSelected;
}
),
@@ -198,6 +209,7 @@ export const TableRowMeta = EmberObject.extend({
let rowIndex = get(this, 'index');
let isGroupSelected = get(this, 'isGroupSelected');
let selectingChildrenSelectsParent = get(tree, 'selectingChildrenSelectsParent');
+ let selectingParentSelectsChildren = get(tree, 'selectingParentSelectsChildren');
let rowMetaCache = get(tree, 'rowMetaCache');
@@ -233,57 +245,68 @@ export const TableRowMeta = EmberObject.extend({
selection.add(tree.objectAt(i));
}
} else if (toggle) {
- if (isGroupSelected) {
- let meta = this;
- let currentValue = rowValue;
-
- // If the parent is selected all of its children are selected. Since
- // the current row is going to be removed from the selection, add all
- // the sibling rows at each level of its grouping to be explicitly
- // selected so their state remains stable.
- while (get(meta, '_parentMeta.isSelected')) {
- meta = get(meta, '_parentMeta');
-
- // Iterate from the parent meta to the "next" tree node. Since this
- // is a group it will have at least one child, so there should be at
- // least one next row to iterate over.
- let expectedChildDepth = get(meta, 'depth') + 1;
- let childIndex = get(meta, 'index'); // will be incremented by 1 before use
- let child;
- while ((child = tree.objectAt(++childIndex))) {
- // The currentValue is being toggled, don't add it to the selection
- if (child === currentValue) {
- continue;
- }
-
- // If the depth of the row is lower than the expectedChildDepth a
- // non-child meta has been found (a sibling or something higher.
- // That means iterating children is complete, so break.
- //
- // If the depth is higher than expected then children of a child
- // group are being iterated. Skip over them, but don't break since
- // there may be a leaf child after a group child.
- let childMeta = rowMetaCache.get(child);
- let childDepth = get(childMeta, 'depth');
- if (childDepth < expectedChildDepth) {
- break;
- }
- if (childDepth > expectedChildDepth) {
- continue;
+ // If selectingParentSelectsChildren is false, then we can just toggle
+ // the row. If it is true, then we need to do a bit more work to ensure
+ // that the selection is stable.
+ if (!selectingParentSelectsChildren) {
+ if (isGroupSelected) {
+ selection.delete(rowValue);
+ } else {
+ selection.add(rowValue);
+ }
+ } else {
+ if (isGroupSelected) {
+ let meta = this;
+ let currentValue = rowValue;
+
+ // If the parent is selected all of its children are selected. Since
+ // the current row is going to be removed from the selection, add all
+ // the sibling rows at each level of its grouping to be explicitly
+ // selected so their state remains stable.
+ while (get(meta, '_parentMeta.isSelected')) {
+ meta = get(meta, '_parentMeta');
+
+ // Iterate from the parent meta to the "next" tree node. Since this
+ // is a group it will have at least one child, so there should be at
+ // least one next row to iterate over.
+ let expectedChildDepth = get(meta, 'depth') + 1;
+ let childIndex = get(meta, 'index'); // will be incremented by 1 before use
+ let child;
+ while ((child = tree.objectAt(++childIndex))) {
+ // The currentValue is being toggled, don't add it to the selection
+ if (child === currentValue) {
+ continue;
+ }
+
+ // If the depth of the row is lower than the expectedChildDepth a
+ // non-child meta has been found (a sibling or something higher.
+ // That means iterating children is complete, so break.
+ //
+ // If the depth is higher than expected then children of a child
+ // group are being iterated. Skip over them, but don't break since
+ // there may be a leaf child after a group child.
+ let childMeta = rowMetaCache.get(child);
+ let childDepth = get(childMeta, 'depth');
+ if (childDepth < expectedChildDepth) {
+ break;
+ }
+ if (childDepth > expectedChildDepth) {
+ continue;
+ }
+
+ // Else, this is a child node which must be explictly selected.
+ // Add it to the list.
+ selection.add(child);
}
- // Else, this is a child node which must be explictly selected.
- // Add it to the list.
- selection.add(child);
+ selection.delete(currentValue);
+ currentValue = get(meta, '_rowValue');
}
selection.delete(currentValue);
- currentValue = get(meta, '_rowValue');
+ } else {
+ selection.add(rowValue);
}
-
- selection.delete(currentValue);
- } else {
- selection.add(rowValue);
}
} else {
selection.clear();
@@ -307,17 +330,19 @@ export const TableRowMeta = EmberObject.extend({
reduceSelectedRows(selection, groupingCounts, rowMetaCache);
}
- for (let rowMeta of rowMetas) {
- let rowValue = get(rowMeta, '_rowValue');
- let parentMeta = get(rowMeta, '_parentMeta');
+ if (selectingParentSelectsChildren) {
+ for (let rowMeta of rowMetas) {
+ let rowValue = get(rowMeta, '_rowValue');
+ let parentMeta = get(rowMeta, '_parentMeta');
+
+ while (parentMeta) {
+ if (selection.has(get(parentMeta, '_rowValue'))) {
+ selection.delete(rowValue);
+ break;
+ }
- while (parentMeta) {
- if (selection.has(get(parentMeta, '_rowValue'))) {
- selection.delete(rowValue);
- break;
+ parentMeta = get(parentMeta, '_parentMeta');
}
-
- parentMeta = get(parentMeta, '_parentMeta');
}
}
diff --git a/addon/components/ember-tbody/component.js b/addon/components/ember-tbody/component.js
index 889e6d802..acb86aa8d 100644
--- a/addon/components/ember-tbody/component.js
+++ b/addon/components/ember-tbody/component.js
@@ -96,6 +96,14 @@ export default Component.extend({
*/
selectingChildrenSelectsParent: defaultTo(true),
+ /**
+ When true, this option causes selecting a node to also select all of the node's children.
+
+ @argument selectingParentSelectsChildren
+ @type boolean
+ */
+ selectingParentSelectsChildren: defaultTo(true),
+
/**
The currently selected rows. Can either be an array or an individual row.
@@ -297,6 +305,14 @@ export default Component.extend({
'You must create an