Skip to content
Draft
3 changes: 3 additions & 0 deletions addon-test-support/pages/-private/ember-table-body.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export default PageObject.extend({
checkbox: {
scope: '[data-test-select-row]',
isChecked: property('checked'),
isIndeterminate: property('indeterminate'),

async clickWith(options) {
await click(findElement(this), options);
Expand All @@ -98,6 +99,8 @@ export default PageObject.extend({

isSelected: hasClass('is-selected'),

isGroupIndeterminate: hasClass('is-group-indeterminate'),

/**
Helper function to click with options like the meta key and ctrl key set

Expand Down
30 changes: 30 additions & 0 deletions addon/-private/collapse-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,36 @@ export const TableRowMeta = EmberObject.extend({
}
),

isGroupIndeterminate: computed(
'_tree.{selection.[],selectionMatchFunction,selectingChildrenSelectsParent}',
function() {
let rowValue = get(this, '_rowValue');
let selection = get(this, '_tree.selection');
let selectionMatchFunction = get(this, '_tree.selectionMatchFunction');
let selectingChildrenSelectsParent = get(this, '_tree.selectingChildrenSelectsParent');

if (!rowValue.children || !isArray(rowValue.children)) {
return false;
}

if (!selection || !isArray(selection)) {
return false;
}

if (!selectingChildrenSelectsParent) {
return false;
}

if (selectionMatchFunction) {
return rowValue.children.some(child =>
selection.some(item => selectionMatchFunction(item, child))
);
}

return rowValue.children.some(child => selection.includes(child));
}
),

canCollapse: computed(
'_tree.{enableTree,enableCollapse}',
'_rowValue.{children.[],disableCollapse}',
Expand Down
1 change: 1 addition & 0 deletions addon/components/ember-td/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
>
<EmberTableSimpleCheckbox
@checked={{this.rowMeta.isGroupSelected}}
@indeterminate={{this.rowMeta.isGroupIndeterminate}}
@onClick={{action "onSelectionToggled"}}
@ariaLabel="Select row"
@dataTestSelectRow={{this.isTesting}}
Expand Down
4 changes: 3 additions & 1 deletion addon/components/ember-tr/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default Component.extend({
layout,
tagName: 'tr',
classNames: ['et-tr'],
classNameBindings: ['isSelected', 'isGroupSelected', 'isSelectable'],
classNameBindings: ['isSelected', 'isGroupSelected', 'isGroupIndeterminate', 'isSelectable'],

/**
The API object passed in by the table body, header, or footer
Expand Down Expand Up @@ -88,6 +88,8 @@ export default Component.extend({

isGroupSelected: readOnly('rowMeta.isGroupSelected'),

isGroupIndeterminate: readOnly('rowMeta.isGroupIndeterminate'),

isSelectable: computed('rowSelectionMode', function() {
let rowSelectionMode = this.get('rowSelectionMode');

Expand Down
51 changes: 50 additions & 1 deletion tests/integration/components/selection-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { module, test } from 'qunit';
import { componentModule } from '../../helpers/module';

import TablePage from 'ember-table/test-support/pages/ember-table';

import { generateTable } from '../../helpers/generate-table';
import { generateRows } from 'dummy/utils/generators';
import { A as emberA } from '@ember/array';
Expand Down Expand Up @@ -483,6 +482,31 @@ module('Integration | selection', () => {
assert.ok(row.isSelected, 'the row is selected');
assert.ok(!row.checkbox.isChecked, 'the row checkbox is checked');
});

test('Selecting a child row causes parent checkbox to be indeterminate', async function(assert) {
await generateTable(this, { rowCount: 3, rowDepth: 2 });

let parentRow = table.rows.objectAt(0);
let childRow = table.rows.objectAt(1);

assert.ok(!parentRow.isSelected, 'parent row is not selected');
assert.ok(!parentRow.isGroupIndeterminate, 'parent row is not indeterminate');

assert.ok(!parentRow.checkbox.isChecked, 'parent row checkbox is not checked');
assert.ok(!parentRow.checkbox.isIndeterminate, 'parent row checkbox is not indeterminate');

assert.ok(!childRow.isSelected, 'child row is not selected');

await childRow.checkbox.click();

assert.ok(childRow.isSelected, 'child row is selected');

assert.ok(!parentRow.isSelected, 'parent row is not selected');
assert.ok(parentRow.isGroupIndeterminate, 'parent row is indeterminate');

assert.ok(!parentRow.checkbox.isChecked, 'parent row checkbox is not checked');
assert.ok(parentRow.checkbox.isIndeterminate, 'parent row checkbox is indeterminate');
});
});

componentModule('single', function() {
Expand Down Expand Up @@ -532,6 +556,31 @@ module('Integration | selection', () => {

await table.selectRow(0);
});

test('Selecting a child row causes parent checkbox to be indeterminate', async function(assert) {
await generateTable(this, { rowCount: 3, rowDepth: 2, checkboxSelectionMode: 'single' });

let parentRow = table.rows.objectAt(0);
let childRow = table.rows.objectAt(1);

assert.ok(!parentRow.isSelected, 'parent row is not selected');
assert.ok(!parentRow.isGroupIndeterminate, 'parent row is not indeterminate');

assert.ok(!parentRow.checkbox.isChecked, 'parent row checkbox is not checked');
assert.ok(!parentRow.checkbox.isIndeterminate, 'parent row checkbox is not indeterminate');

assert.ok(!childRow.isSelected, 'child row is not selected');

await childRow.checkbox.click();

assert.ok(childRow.isSelected, 'child row is selected');

assert.ok(!parentRow.isSelected, 'parent row is not selected');
assert.ok(parentRow.isGroupIndeterminate, 'parent row is indeterminate');

assert.ok(!parentRow.checkbox.isChecked, 'parent row checkbox is not checked');
assert.ok(parentRow.checkbox.isIndeterminate, 'parent row checkbox is indeterminate');
});
});

componentModule('none', function() {
Expand Down
1 change: 1 addition & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface TableRowMeta {
isCollapsed: boolean;
isSelected: boolean;
isGroupSelected: boolean;
isGroupIndeterminate: boolean;
canCollapse: boolean;
depth: number;
first: unknown | null;
Expand Down