Skip to content

Commit

Permalink
feat(data-table): indeterminate state in 'selectAll' checkbox (closes #…
Browse files Browse the repository at this point in the history
…571) (#573)

* feat(data-table): indeterminate state in 'selectAll' checkbox

when atleast one row is selected and not every row is selected, the checkbox for select/deselect all should be in indeterminate state.

* fix(data-table): set variable when selecting all instead of recalculating
  • Loading branch information
emoralesb05 authored and kyleledbetter committed May 8, 2017
1 parent 33f529b commit bd0f7bc
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 24 deletions.
3 changes: 2 additions & 1 deletion src/platform/core/data-table/data-table.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
#checkBoxAll
*ngIf="isMultiple"
[disabled]="!hasData"
[checked]="areAllSelected() && hasData"
[indeterminate]="indeterminate && !allSelected && hasData"
[checked]="allSelected && hasData"
(click)="selectAll(!checkBoxAll.checked)">
</md-checkbox>
</th>
Expand Down
149 changes: 135 additions & 14 deletions src/platform/core/data-table/data-table.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import 'hammerjs';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
import { TdDataTableColumnComponent } from './data-table-column/data-table-column.component';
import { TdDataTableRowComponent } from './data-table-row/data-table-row.component';
import { TdDataTableComponent, ITdDataTableColumn } from './data-table.component';
import { TdDataTableService } from './services/data-table.service';
import { CovalentDataTableModule } from './data-table.module';
import { NgModule, DebugElement } from '@angular/core';
import { MdCheckbox } from '@angular/material';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

describe('Component: DataTable', () => {
Expand Down Expand Up @@ -120,22 +122,141 @@ describe('Component: DataTable', () => {
})();
});

it('should not set the data input and not fail when selectable and multiple', (done: DoneFn) => {
inject([], () => {
let fixture: ComponentFixture<any> = TestBed.createComponent(TdDataTableSelectableTestComponent);
let element: DebugElement = fixture.debugElement;
let component: TdDataTableSelectableTestComponent = fixture.debugElement.componentInstance;

component.selectable = true;
component.multiple = true;
describe('selectable and multiple', () => {

it('should not set the data input and not fail', (done: DoneFn) => {
inject([], () => {
let fixture: ComponentFixture<any> = TestBed.createComponent(TdDataTableSelectableTestComponent);
let element: DebugElement = fixture.debugElement;
let component: TdDataTableSelectableTestComponent = fixture.debugElement.componentInstance;

component.selectable = true;
component.multiple = true;

fixture.detectChanges();
fixture.whenStable().then(() => {
// if it finishes in means it didnt fail
done();
});
})();
});

it('should select one and be in indeterminate state, select all and then unselect all',
(done: DoneFn) => { inject([], () => {
let fixture: ComponentFixture<any> = TestBed.createComponent(TdDataTableSelectableTestComponent);
let element: DebugElement = fixture.debugElement;
let component: TdDataTableSelectableTestComponent = fixture.debugElement.componentInstance;

component.selectable = true;
component.multiple = true;
component.columns = [
{ name: 'sku', label: 'SKU #' },
{ name: 'item', label: 'Item name' },
{ name: 'price', label: 'Price (US$)', numeric: true },
];

component.data = [{ sku: '1452-2', item: 'Pork Chops', price: 32.11 },
{ sku: '1421-0', item: 'Prime Rib', price: 41.15 },
{ sku: '1452-1', item: 'Sirlone', price: 22.11 },
{ sku: '1421-3', item: 'T-Bone', price: 51.15 }];

fixture.detectChanges();
fixture.whenStable().then(() => {
let dataTableComponent: TdDataTableComponent = fixture.debugElement.query(By.directive(TdDataTableComponent)).componentInstance;
// check how many rows (without counting the columns) were rendered
expect(fixture.debugElement.queryAll(By.directive(TdDataTableRowComponent)).length - 1).toBe(4);
// check to see checkboxes states
expect(dataTableComponent.indeterminate).toBeFalsy();
expect(dataTableComponent.allSelected).toBeFalsy();
// select a row with a click event
fixture.debugElement.queryAll(By.directive(TdDataTableRowComponent))[2].triggerEventHandler('click', new Event('click'));
fixture.detectChanges();
fixture.whenStable().then(() => {
// check to see if its in indeterminate state
expect(dataTableComponent.indeterminate).toBeTruthy();
expect(dataTableComponent.allSelected).toBeFalsy();
// select the rest of the rows by clicking in selectAll
fixture.debugElement.query(By.directive(MdCheckbox)).triggerEventHandler('click', new Event('click'));
fixture.detectChanges();
fixture.whenStable().then(() => {
// check to see if its in indeterminate state and allSelected
expect(dataTableComponent.indeterminate).toBeTruthy();
expect(dataTableComponent.allSelected).toBeTruthy();
// unselect all rows by clicking in unselect all
fixture.debugElement.query(By.directive(MdCheckbox)).triggerEventHandler('click', new Event('click'));
fixture.detectChanges();
fixture.whenStable().then(() => {
// check to see if its not in indeterminate state and not allSelected
expect(dataTableComponent.indeterminate).toBeFalsy();
expect(dataTableComponent.allSelected).toBeFalsy();
done();
});
});
});
});
})();
});

it('should be interminate when atleast one row is selected and allSelected when all rows are selected',
(done: DoneFn) => { inject([], () => {
let fixture: ComponentFixture<any> = TestBed.createComponent(TdDataTableSelectableTestComponent);
let element: DebugElement = fixture.debugElement;
let component: TdDataTableSelectableTestComponent = fixture.debugElement.componentInstance;

component.selectable = true;
component.multiple = true;
component.columns = [
{ name: 'sku', label: 'SKU #' },
{ name: 'item', label: 'Item name' },
{ name: 'price', label: 'Price (US$)', numeric: true },
];

component.data = [{ sku: '1452-2', item: 'Pork Chops', price: 32.11 },
{ sku: '1421-0', item: 'Prime Rib', price: 41.15 },
{ sku: '1452-1', item: 'Sirlone', price: 22.11 },
{ sku: '1421-3', item: 'T-Bone', price: 51.15 }];

fixture.detectChanges();
fixture.whenStable().then(() => {
let dataTableComponent: TdDataTableComponent = fixture.debugElement.query(By.directive(TdDataTableComponent)).componentInstance;
// check how many rows (without counting the columns) were rendered
expect(fixture.debugElement.queryAll(By.directive(TdDataTableRowComponent)).length - 1).toBe(4);
// check to see checkboxes states
expect(dataTableComponent.indeterminate).toBeFalsy();
expect(dataTableComponent.allSelected).toBeFalsy();
// select a row with a click event
fixture.debugElement.queryAll(By.directive(TdDataTableRowComponent))[2].triggerEventHandler('click', new Event('click'));
fixture.detectChanges();
fixture.whenStable().then(() => {
// check to see if its in indeterminate state
expect(dataTableComponent.indeterminate).toBeTruthy();
expect(dataTableComponent.allSelected).toBeFalsy();
// select the rest of the rows
fixture.debugElement.queryAll(By.directive(TdDataTableRowComponent))[1].triggerEventHandler('click', new Event('click'));
fixture.debugElement.queryAll(By.directive(TdDataTableRowComponent))[3].triggerEventHandler('click', new Event('click'));
fixture.debugElement.queryAll(By.directive(TdDataTableRowComponent))[4].triggerEventHandler('click', new Event('click'));
fixture.detectChanges();
fixture.whenStable().then(() => {
// check to see if its in indeterminate state and allSelected
expect(dataTableComponent.indeterminate).toBeTruthy();
expect(dataTableComponent.allSelected).toBeTruthy();
// unselect one of the rows
fixture.debugElement.queryAll(By.directive(TdDataTableRowComponent))[2].triggerEventHandler('click', new Event('click'));
fixture.detectChanges();
fixture.whenStable().then(() => {
// check to see if its in indeterminate state and not allSelected
expect(dataTableComponent.indeterminate).toBeTruthy();
expect(dataTableComponent.allSelected).toBeFalsy();
done();
});
});
});
});
})();
});

fixture.detectChanges();
fixture.whenStable().then(() => {
// if it finishes in means it didnt fail
done();
});
})();
});

});

@Component({
Expand Down
64 changes: 55 additions & 9 deletions src/platform/core/data-table/data-table.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export class TdDataTableComponent implements ControlValueAccessor, AfterContentI
private _columns: ITdDataTableColumn[];
private _selectable: boolean = false;
private _multiple: boolean = true;
private _allSelected: boolean = false;
private _indeterminate: boolean = false;

/** sorting */
private _sortable: boolean = false;
Expand All @@ -73,6 +75,21 @@ export class TdDataTableComponent implements ControlValueAccessor, AfterContentI
private _templateMap: Map<string, TemplateRef<any>> = new Map<string, TemplateRef<any>>();
@ContentChildren(TdDataTableTemplateDirective) _templates: QueryList<TdDataTableTemplateDirective>;

/**
* Returns true if all values are selected.
*/
get allSelected(): boolean {
return this._allSelected;
}

/**
* Returns true if all values are not deselected
* and atleast one is.
*/
get indeterminate(): boolean {
return this._indeterminate;
}

/**
* Implemented as part of ControlValueAccessor.
*/
Expand Down Expand Up @@ -277,18 +294,10 @@ export class TdDataTableComponent implements ControlValueAccessor, AfterContentI
* Refreshes data table and rerenders [data] and [columns]
*/
refresh(): void {
this._calculateCheckboxState();
this._changeDetectorRef.markForCheck();
}

/**
* Checks if all visible rows are selected.
*/
areAllSelected(): boolean {
const match: string =
this._data ? this._data.find((d: any) => !this.isRowSelected(d)) : true;
return typeof match === 'undefined';
}

/**
* Selects or clears all rows depending on 'checked' value.
*/
Expand All @@ -300,8 +309,12 @@ export class TdDataTableComponent implements ControlValueAccessor, AfterContentI
this._value.push(row);
}
});
this._allSelected = true;
this._indeterminate = true;
} else {
this.clearModel();
this._allSelected = false;
this._indeterminate = false;
}
this.onSelectAll.emit({rows: this._value, selected: checked});
}
Expand Down Expand Up @@ -343,6 +356,7 @@ export class TdDataTableComponent implements ControlValueAccessor, AfterContentI
this._value.splice(index, 1);
}
}
this._calculateCheckboxState();
this.onRowSelect.emit({row: row, selected: checked});
this.onChange(this._value);
}
Expand Down Expand Up @@ -391,4 +405,36 @@ export class TdDataTableComponent implements ControlValueAccessor, AfterContentI
}
}

/**
* Calculate all the state of all checkboxes
*/
private _calculateCheckboxState(): void {
this._calculateAllSelected();
this._calculateIndeterminate();
}

/**
* Checks if all visible rows are selected.
*/
private _calculateAllSelected(): void {
const match: string =
this._data ? this._data.find((d: any) => !this.isRowSelected(d)) : true;
this._allSelected = typeof match === 'undefined';
}

/**
* Checks if all visible rows are selected.
*/
private _calculateIndeterminate(): void {
this._indeterminate = false;
if (this._data) {
for (let row of this._data) {
if (!this.isRowSelected(row)) {
continue;
}
this._indeterminate = true;
}
}
}

}

0 comments on commit bd0f7bc

Please sign in to comment.