diff --git a/examples/components/data_table/selectable_usage.html b/examples/components/data_table/selectable_usage.html index 36db0f69..68fb7dd1 100644 --- a/examples/components/data_table/selectable_usage.html +++ b/examples/components/data_table/selectable_usage.html @@ -10,14 +10,14 @@ - + Material Quantity Unit price - + {{ material.name }} {{ material.quantity }} {{ material.price }} diff --git a/ng2-material/all.ts b/ng2-material/all.ts index 224ea467..fae667ee 100644 --- a/ng2-material/all.ts +++ b/ng2-material/all.ts @@ -2,7 +2,7 @@ import {CONST_EXPR, Type} from "angular2/src/facade/lang"; import {MdAnchor, MdButton} from "./components/button/button"; import {MdCheckbox} from "./components/checkbox/checkbox"; import {MdContent} from "./components/content/content"; -import {MdDataTable, MdDataTableHeaderRow, MdDataTableRow} from './components/data_table/data_table'; +import {MdDataTable, MdDataTableHeaderSelectableRow, MdDataTableSelectableRow} from './components/data_table/data_table'; import {MdDialog} from "./components/dialog/dialog"; import {MdDivider} from "./components/divider/divider"; import {MdIcon} from "./components/icon/icon"; @@ -83,7 +83,7 @@ export const MATERIAL_DIRECTIVES: Type[] = CONST_EXPR([ MdAnchor, MdButton, MdCheckbox, MdContent, - MdDataTable, MdDataTableHeaderRow, MdDataTableRow, + MdDataTable, MdDataTableHeaderSelectableRow, MdDataTableSelectableRow, MdDivider, MdIcon, MdInk, diff --git a/ng2-material/components/data_table/README.md b/ng2-material/components/data_table/README.md index 683d4d94..81858edf 100644 --- a/ng2-material/components/data_table/README.md +++ b/ng2-material/components/data_table/README.md @@ -11,28 +11,48 @@ MdDataTable is an enhancment of classic data tables. ### Properties | Name | Target | Type | Description | | --- | --- | --- | --- | -| selectable | md-data-table | boolean | Enable one checkbox per line and a master checkbox to rule them all. | -| md-data-table-header-row | thead tr | boolean | Enable the master checkbox on header. | -| md-data-table-row | tbody tr | boolean | Enable the checkbox on a row. | +| selectable | md-data-table | boolean | Enable listeners to children's checkbox. + +### Events +| Name | Description | +| --- | --- | +| onSelectableChange | Emitted when the user select or unselect a row | + +## Selectable header row +### Properties +| Name | Target | Type | Description | +| --- | --- | --- | --- | +| md-data-table-header-selectable-row | thead tr | boolean | Enable the master checkbox on header. | + +### Events +| Name | Description | +| --- | --- | +| onChange | Emitted when the user check or uncheck the master checkbox | + +## Selectable header row +### Properties +| Name | Target | Type | Description | +| --- | --- | --- | --- | +| md-data-table-selectable-row | tbody tr | boolean | Enable a checkbox for this row. | | selectable-value | tbody tr | string | value of the checkbox. If it's not set the checkbox's value will be the index of the row. | ### Events | Name | Description | | --- | --- | -| selectable_change | Emmited when the user select or unselect a row | +| onChange | Emitted when the user check or uncheck the checkbox | ## Examples ``` - + Material Quantity Unit price - + {{ material.name }} {{ material.quantity }} {{ material.price }} diff --git a/ng2-material/components/data_table/data_table.ts b/ng2-material/components/data_table/data_table.ts index 16d5aa00..1b1f747f 100644 --- a/ng2-material/components/data_table/data_table.ts +++ b/ng2-material/components/data_table/data_table.ts @@ -1,8 +1,9 @@ -import {Component, Input, Output, EventEmitter, ContentChildren, QueryList} from "angular2/core"; -import {MdDataTableHeaderRow, MdDataTableRow, ITableRow} from "./data_table_tr"; -import "rxjs/add/operator/share"; +import {Component, Input, Output, EventEmitter, ContentChild, ContentChildren, QueryList, AfterContentInit} from 'angular2/core'; +import {isPresent} from "angular2/src/facade/lang"; +import 'rxjs/add/operator/share'; +import {MdDataTableHeaderSelectableRow, MdDataTableSelectableRow, ITableSelectableRowSelectionChange} from './data_table_selectable_tr'; -export * from './data_table_tr'; +export * from './data_table_selectable_tr'; /** * Selectable change event data @@ -13,17 +14,16 @@ export interface ITableSelectionChange { values: any[]; } - @Component({ selector: 'md-data-table', template: ``, - directives: [MdDataTableHeaderRow, MdDataTableRow], + directives: [MdDataTableHeaderSelectableRow, MdDataTableSelectableRow], host: { '[class.md-data-table]': 'true', '[class.md-data-table-selectable]': 'selectable', } }) -export class MdDataTable { +export class MdDataTable implements AfterContentInit { @Input() selectable: boolean; @Output() @@ -31,46 +31,45 @@ export class MdDataTable { @Output() onSelectableChange: EventEmitter = new EventEmitter(false); - @ContentChildren(MdDataTableRow, true) - _rows: QueryList; + @ContentChild(MdDataTableHeaderSelectableRow) + _masterRow: MdDataTableHeaderSelectableRow; + + @ContentChildren(MdDataTableSelectableRow, true) + _rows: QueryList; + selected: Array = []; constructor() { this.onSelectableChange.share(); } - /** - * Fill/Empty the array of selected values - * - * @param {MdDataTableRow} tr - */ - toggleActivity(tr: ITableRow) { - let event: ITableSelectionChange = { + change(event: ITableSelectableRowSelectionChange) { + let outputEvent: ITableSelectionChange = { name: 'selectable_change', allSelected: false, values: [] }; - if (tr instanceof MdDataTableHeaderRow === true) { - if (tr.isActive === true) { - event.allSelected = true; - event.values = this._getRowsValues(); + if (event.target instanceof MdDataTableHeaderSelectableRow === true) { + if (event.isActive === true) { + outputEvent.allSelected = true; + outputEvent.values = this._getRowsValues(); } } else { - event.values = this.selected.slice(0); + outputEvent.values = this.selected.slice(0); - if (tr.isActive === true) { - event.values.push(tr.selectableValue); + if (event.isActive === true) { + outputEvent.values.push(event.selectableValue); } else { - let index = event.values.indexOf(tr.selectableValue); + let index = outputEvent.values.indexOf(event.selectableValue); if (index !== -1) { - event.values.splice(index, 1); + outputEvent.values.splice(index, 1); } } } // dispatch change - this.selected = event.values; - this.onSelectableChange.emit(event); + this.selected = outputEvent.values; + this.onSelectableChange.emit(outputEvent); } /** @@ -78,7 +77,20 @@ export class MdDataTable { */ _getRowsValues(): any[] { return this._rows.toArray() - .map((tr: MdDataTableRow) => tr.selectableValue); + .map((tr: MdDataTableSelectableRow) => tr.selectableValue); + } + + ngAfterContentInit() { + if (this.selectable === true) { + if (isPresent(this._masterRow)) { + this._masterRow.onChange.subscribe(this.change.bind(this)); + } + + this._rows.toArray() + .map((tr: MdDataTableSelectableRow) => { + tr.onChange.subscribe(this.change.bind(this)); + }); + } } } diff --git a/ng2-material/components/data_table/data_table_selectable_tr.ts b/ng2-material/components/data_table/data_table_selectable_tr.ts new file mode 100644 index 00000000..da8e3dff --- /dev/null +++ b/ng2-material/components/data_table/data_table_selectable_tr.ts @@ -0,0 +1,144 @@ +import {Component, Output, Input, EventEmitter, Inject, Optional, forwardRef, ElementRef, AfterContentInit} from "angular2/core"; +import {isPresent} from "angular2/src/facade/lang"; +import 'rxjs/add/operator/share'; +import 'rxjs/add/operator/map'; +import {MdCheckbox} from "../checkbox/checkbox"; +import {MdDataTable} from './data_table'; + +/** + * MdDataTable row + */ +export interface ITableSelectableRow { + selectableValue: string; + onChange: EventEmitter; + isActive: boolean; + change: () => void; + ngAfterContentInit: () => void; +} + +/** + * Selectable change event data + */ +export interface ITableSelectableRowSelectionChange { + name: string; + target: ITableSelectableRow; + isActive: boolean; + selectableValue: string; +} + +export abstract class AbstractMdDataTableSelectableRow implements AfterContentInit, ITableSelectableRow { + @Input('selectable-value') + selectableValue: string; + @Output() + onChange: EventEmitter = new EventEmitter(false); + isActive: boolean = false; + + constructor(@Optional() + @Inject(forwardRef(() => MdDataTable)) + public table: MdDataTable, protected _element: ElementRef) { + this.onChange.share(); + } + + /** + * Change active status + */ + change() { + this.isActive = !this.isActive; + + let event: ITableSelectableRowSelectionChange = { + name: 'selectable_row_change', + target: this, + isActive: this.isActive, + selectableValue: this.selectableValue + } + this.onChange.emit(event); + } + + ngAfterContentInit() {} +} + +@Component({ + selector: 'tr[md-data-table-header-selectable-row]', + template: ` + + + + + `, + directives: [MdCheckbox], + host: { + '[class.active]': 'isActive', + '(click)': 'change()' + } +}) +export class MdDataTableHeaderSelectableRow extends AbstractMdDataTableSelectableRow { + constructor(@Optional() + @Inject(forwardRef(() => MdDataTable)) + public table: MdDataTable, protected _element: ElementRef) { + super(table, _element); + } + + _bindListener(): void { + this.table.onSelectableChange + .map(event => event.allSelected) + .subscribe(newActiveStatus => this.isActive = newActiveStatus); + } + + ngAfterContentInit() { + if (isPresent(this.table)) { + this._bindListener(); + } + } +} + +@Component({ + selector: 'tr[md-data-table-selectable-row]', + template: ` + + + + + `, + directives: [MdCheckbox], + host: { + '[class.active]': 'isActive', + '(click)': 'change()' + } +}) +export class MdDataTableSelectableRow extends AbstractMdDataTableSelectableRow { + constructor(@Optional() + @Inject(forwardRef(() => MdDataTable)) + public table: MdDataTable, protected _element: ElementRef) { + super(table, _element); + } + + /** + * @param {any} element + * + * @returns {string} + */ + _getIndex(element): string { + return Array.prototype.indexOf.call(element.parentNode.children, element).toString(); + } + + _bindListener(): void { + this.table.onSelectableChange + .map(event => { + return event.values !== undefined && + event.values.length && + (event.values.findIndex((value: string) => value === this.selectableValue)) !== -1; + }) + .subscribe(newActiveStatus => this.isActive = newActiveStatus); + } + + ngAfterContentInit() { + let element = this._element.nativeElement; + if (this.selectableValue === undefined) { + this.selectableValue = this._getIndex(element); + } + + if (isPresent(this.table)) { + this._bindListener(); + } + } +} diff --git a/ng2-material/components/data_table/data_table_tr.ts b/ng2-material/components/data_table/data_table_tr.ts deleted file mode 100644 index cc3ec64d..00000000 --- a/ng2-material/components/data_table/data_table_tr.ts +++ /dev/null @@ -1,121 +0,0 @@ -import {Component, Input, Inject, forwardRef, ElementRef, AfterContentInit} from "angular2/core"; -import {isPresent} from "angular2/src/facade/lang"; -import {MdDataTable} from "./data_table"; -import {MdCheckbox} from "../checkbox/checkbox"; -import "rxjs/add/operator/map"; - - -/** - * MdDataTable row - */ -export interface ITableRow { - selectableValue: string; - isActive: boolean; -} - -@Component({ - selector: 'tr[md-data-table-header-row]', - template: ` - - - `, - directives: [MdCheckbox], - host: { - '[class.active]': 'isActive', - '(click)': 'table?.selectable && change()' - } -}) -export class MdDataTableHeaderRow implements AfterContentInit, ITableRow { - @Input('selectable-value') - selectableValue: string; - isActive: boolean = false; - - constructor(@Inject(forwardRef(() => MdDataTable)) - public table: MdDataTable) { - } - - /** - * Change active status - */ - change() { - this.isActive = !this.isActive; - this.table.toggleActivity(this); - } - - /** - * init listener to selectableChange event - */ - _initListeners() { - this.table.onSelectableChange - .map(event => event.allSelected) - .subscribe(newActiveStatus => this.isActive = newActiveStatus); - } - - ngAfterContentInit() { - if (isPresent(this.table)) { - this._initListeners(); - } - } -} - -@Component({ - selector: 'tr[md-data-table-row]', - template: ` - - - `, - directives: [MdCheckbox], - host: { - '[class.active]': 'isActive', - '(click)': 'table?.selectable && change()' - } -}) -export class MdDataTableRow implements AfterContentInit, ITableRow { - @Input('selectable-value') - selectableValue: string; - isActive: boolean = false; - - constructor(@Inject(forwardRef(() => MdDataTable)) - public table: MdDataTable, private _element: ElementRef) { - } - - /** - * Change active status - */ - change() { - this.isActive = !this.isActive; - this.table.toggleActivity(this); - } - - /** - * init listener to selectableChange event - */ - _initListeners() { - this.table.onSelectableChange - .map(event => { - return event.values !== undefined && - event.values.length && - (event.values.findIndex((value: string) => value === this.selectableValue)) !== -1; - }) - .subscribe(newActiveStatus => this.isActive = newActiveStatus); - } - - ngAfterContentInit() { - if (isPresent(this.table)) { - let element = this._element.nativeElement; - this._initListeners(); - - if (this.selectableValue === undefined) { - this.selectableValue = Array.prototype.indexOf.call(element.parentNode.children, element).toString(); - } - } - } -} diff --git a/test/components/data_table/data_table_spec.ts b/test/components/data_table/data_table_spec.ts index 69e36cc4..a8b8475b 100644 --- a/test/components/data_table/data_table_spec.ts +++ b/test/components/data_table/data_table_spec.ts @@ -1,4 +1,4 @@ -import {componentSanityCheck} from "../../util"; +import {componentSanityCheck} from '../../util'; import { TestComponentBuilder, beforeEach, @@ -8,15 +8,12 @@ import { it, injectAsync, ComponentFixture -} from "angular2/testing"; -import {Component, DebugElement} from "angular2/core"; -import {CORE_DIRECTIVES} from "angular2/common"; -import {MdDataTable, MdDataTableRow} from 'ng2-material/components/data_table/data_table'; -import {DOM} from "angular2/src/platform/dom/dom_adapter"; -import {KeyCodes} from "../../../ng2-material/core/key_codes"; -import {By} from "angular2/platform/browser"; -import {MdDataTableHeaderRow} from '../../../ng2-material/components/data_table/data_table_tr'; - +} from 'angular2/testing'; +import {Component, DebugElement} from 'angular2/core'; +import {CORE_DIRECTIVES} from 'angular2/common'; +import {MdDataTable} from 'ng2-material/components/data_table/data_table'; +import {By} from 'angular2/platform/browser'; +import {MdDataTableHeaderSelectableRow, MdDataTableSelectableRow} from '../../../ng2-material/components/data_table/data_table_selectable_tr'; export function main() { @@ -27,18 +24,18 @@ export function main() { } @Component({ selector: 'test-app', - directives: [CORE_DIRECTIVES, MdDataTable, MdDataTableHeaderRow, MdDataTableRow], + directives: [CORE_DIRECTIVES, MdDataTable, MdDataTableHeaderSelectableRow, MdDataTableSelectableRow], template: ` - + Unit price - + $2.90 - + $1.25 diff --git a/tsconfig.json b/tsconfig.json index 3f426774..d632b193 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,7 @@ "ng2-material/components/checkbox/checkbox.ts", "ng2-material/components/content/content.ts", "ng2-material/components/data_table/data_table.ts", - "ng2-material/components/data_table/data_table_tr.ts", + "ng2-material/components/data_table/data_table_selectable_tr.ts", "ng2-material/components/dialog/dialog.ts", "ng2-material/components/dialog/dialog_basic.ts", "ng2-material/components/dialog/dialog_config.ts", @@ -115,4 +115,4 @@ "test/test_url_resolver.ts", "test/util.ts" ] -} \ No newline at end of file +}