Skip to content
This repository has been archived by the owner on Feb 2, 2019. It is now read-only.

Commit

Permalink
feat: Data table component with selectable rows
Browse files Browse the repository at this point in the history
refactor: selectable is now activate by an @input instead of a class
feat: selectable embed value and fire events on change
refactor: code review
feat: add some tests about selecatable data table
refactor: revamp all selectable process to follow observable pattern
  • Loading branch information
Gregcop1 committed Mar 24, 2016
1 parent 68019e8 commit 04d1152
Show file tree
Hide file tree
Showing 15 changed files with 580 additions and 114 deletions.
3 changes: 3 additions & 0 deletions examples/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import CardBasicUsage from "./components/card/basic_usage";
import CardInlineActions from "./components/card/inline_actions";
import ButtonBasicUsage from "./components/button/basic_usage";
import CardActionButtons from "./components/card/action_buttons";
import DataTableBasicUsage from './components/data_table/basic_usage';
import DataTableSelectableUsage from './components/data_table/selectable_usage';
import DialogBasicUsage from "./components/dialog/basic_usage";
import ToolbarBasicUsage from "./components/toolbar/basic_usage";
import ToolbarScrollShrink from "./components/toolbar/scroll_shrink";
Expand All @@ -30,6 +32,7 @@ export const DEMO_DIRECTIVES: Type[] = CONST_EXPR([
CardBasicUsage, CardInlineActions, CardActionButtons,
ButtonBasicUsage,
CheckboxBasicUsage, CheckboxSyncing,
DataTableBasicUsage, DataTableSelectableUsage,
DialogBasicUsage,
InputBasicUsage,
InputFormBuilder,
Expand Down
22 changes: 22 additions & 0 deletions examples/components/data_table/basic_usage.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<md-content class="md-padding" layout="row" layout-wrap layout-align="center start">
<div flex="50" layout="column" flex-xs="100">

<md-data-table>
<thead>
<tr>
<th class="md-data-table-cell-non-numeric">Material</th>
<th>Quantity</th>
<th>Unit price</th>
</tr>
</thead>
<tbody>
<tr *ngFor="#material of materials" >
<td class="md-data-table-cell-non-numeric">{{ material.name }}</td>
<td>{{ material.quantity }}</td>
<td>{{ material.price }}</td>
</tr>
</tbody>
</md-data-table>

</div>
</md-content>
15 changes: 15 additions & 0 deletions examples/components/data_table/basic_usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {Component} from 'angular2/core';
import {MATERIAL_DIRECTIVES} from 'ng2-material/all';

@Component({
selector: 'data-table-basic-usage',
templateUrl: 'examples/components/data_table/basic_usage.html',
directives: [MATERIAL_DIRECTIVES]
})
export default class DataTableBasicUsage {
materials: Array<any> = [
{'id': 1, 'name': 'Acrylic (Transparent)', 'quantity': '25', 'price': '$2.90'},
{'id': 2, 'name': 'Plywood (Birch)', 'quantity': '50', 'price': '$1.25'},
{'id': 3, 'name': 'Laminate (Gold on Blue)', 'quantity': '10', 'price': '$2.35'}
]
}
1 change: 1 addition & 0 deletions examples/components/data_table/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
An enhanced version of data table, with selectable row (in progress), sortable column (todo) and auto-truncate of header's label (todo)
22 changes: 22 additions & 0 deletions examples/components/data_table/selectable_usage.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<md-content class="md-padding" layout="row" layout-wrap layout-align="center start">
<div flex="50" layout="column" flex-xs="100">

<md-data-table [selectable]="true" (onSelectableChange)="logEvent($event)">
<thead>
<tr>
<th class="md-data-table-cell-non-numeric">Material</th>
<th>Quantity</th>
<th>Unit price</th>
</tr>
</thead>
<tbody>
<tr *ngFor="#material of materials" [selectableValue]="material.id">
<td class="md-data-table-cell-non-numeric">{{ material.name }}</td>
<td>{{ material.quantity }}</td>
<td>{{ material.price }}</td>
</tr>
</tbody>
</md-data-table>

</div>
</md-content>
19 changes: 19 additions & 0 deletions examples/components/data_table/selectable_usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {Component} from 'angular2/core';
import {MATERIAL_DIRECTIVES} from 'ng2-material/all';

@Component({
selector: 'data-table-selectable-usage',
templateUrl: 'examples/components/data_table/selectable_usage.html',
directives: [MATERIAL_DIRECTIVES]
})
export default class DataTableSelectableUsage {
materials: Array<any> = [
{'id': 1, 'name': 'Acrylic (Transparent)', 'quantity': '25', 'price': '$2.90'},
{'id': 2, 'name': 'Plywood (Birch)', 'quantity': '50', 'price': '$1.25'},
{'id': 3, 'name': 'Laminate (Gold on Blue)', 'quantity': '10', 'price': '$2.35'}
];

logEvent(datas) {
console.log('Event fired', datas);
}
}
6 changes: 5 additions & 1 deletion ng2-material/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +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, MdDataTableTr} from './components/data_table/data_table';
import {MdDialog} from "./components/dialog/dialog";
import {MdDivider} from "./components/divider/divider";
import {MdIcon} from "./components/icon/icon";
Expand Down Expand Up @@ -35,6 +36,8 @@ export * from './components/checkbox/checkbox';

export * from './components/content/content';

export * from './components/data_table/data_table';

export * from './components/dialog/dialog';
export * from './components/divider/divider';

Expand Down Expand Up @@ -97,7 +100,8 @@ export const MATERIAL_DIRECTIVES: Type[] = CONST_EXPR([
MdSubheader,
MdSwitch,
MdToolbar,
MdTab, MdTabs
MdTab, MdTabs,
MdDataTable, MdDataTableTr
]);

/**
Expand Down
1 change: 1 addition & 0 deletions ng2-material/components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@import "components/card/card";
@import "components/content/content";
@import "components/checkbox/checkbox";
@import "components/data_table/data_table";
@import "components/dialog/dialog";
@import "components/divider/divider";
@import "components/icon/icon";
Expand Down
40 changes: 40 additions & 0 deletions ng2-material/components/data_table/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# MdDataTable
MdDataTable is an enhancment of classic data tables.

## Basic data table
### Classes
| Name | Target | Description |
| --- | --- | --- |
| md-data-table-cell-non-numeric | thead th, tbody td | Disable the automatic right alignement of the cells. |

## Selectable data table
### Properties
| Name | Target | Type | Description |
| --- | --- | --- | --- |
| selectable | md-data-table | boolean | Enable one checkbox per line and a master checkbox to rule them all. |
| selectableValue | 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 |

## Examples
```
<md-data-table [selectable]="true">
<thead>
<tr>
<th class="md-data-table-cell-non-numeric">Material</th>
<th>Quantity</th>
<th>Unit price</th>
</tr>
</thead>
<tbody>
<tr *ngFor="#material of materials" [selectableValue]="material.id">
<td class="md-data-table-cell-non-numeric">{{ material.name }}</td>
<td>{{ material.quantity }}</td>
<td>{{ material.price }}</td>
</tr>
</tbody>
</md-data-table>
```
46 changes: 46 additions & 0 deletions ng2-material/components/data_table/data_table.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@import "../../core/style/variables";

// TODO(jelbourn): This goes away.
@import "../../core/style/default-theme";

md-data-table {
display: table;
border-spacing: 0;
border-collapse: collapse;

md-checkbox {
margin: 0;
}
tr {
vertical-align: top;
}
th {
padding: 22px 12px;
font-size: 12px;
font-weight: 600;
text-align: left;
color: md-color($md-foreground, text);
}
td {
border-top: 1px solid md-color($md-foreground, divider);
padding: 14px 12px;
font-size: 13px;
text-align: right;
color: md-color($md-foreground, secondary-text);

&.md-data-table-cell-non-numeric { text-align: left; }
}
th:first-child, td:first-child{
padding-left: 24px;
}
th:last-child, td:last-child{
padding-right: 24px;
}

tr:hover td {
background-color: md-color($md-grey, 200);
}
.active td {
background-color: md-color($md-grey, 100);
}
}
71 changes: 71 additions & 0 deletions ng2-material/components/data_table/data_table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {Component, Input, Output, EventEmitter, ContentChildren, QueryList} from 'angular2/core';
import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
import {MdDataTableTr} from './data_table_tr';
import {MdCheckbox} from '../checkbox/checkbox';
import 'rxjs/add/operator/share';

export * from './data_table_tr';

@Component({
selector: 'md-data-table',
template: `<ng-content></ng-content>`,
directives: [MdDataTableTr]
})
export class MdDataTable {
@Input() selectable: boolean;
@Output() onSelectableAll: EventEmitter<any> = new EventEmitter(false);
@Output() onSelectableChange: EventEmitter<any> = new EventEmitter(false);

@ContentChildren(MdDataTableTr, true) _rows: QueryList<MdDataTableTr>;
selected: Array<string> = [];

constructor() {
this.onSelectableChange.share();
}

/**
* Fill/Empty the array of selected values
*
* @param {MdDataTableTr} tr
*/
toggleActivity(tr: MdDataTableTr) {
let event: any = {
name: 'selectable_change',
allSelected: false,
values: []
};

if (true === tr.isInHeader) {
if (true === tr.isActive) {
event.allSelected = true;
event.values = this._getRowsValues();
}
} else {
event.values = this.selected.slice(0);

if (true === tr.isActive) {
event.values.push(tr.selectableValue);
} else {
let index = event.values.indexOf(tr.selectableValue);
if (-1 !== index) {
event.values.splice(index, 1);
}
}
}

// dispatch change
this.selected = event.values;
this.onSelectableChange.emit(event);
}

/**
* @returns {Array<string>}
*/
_getRowsValues() {
return this._rows.toArray()
.filter((data, index) => index > 0)
.map((tr: MdDataTableTr) => tr.selectableValue);
}

}
72 changes: 72 additions & 0 deletions ng2-material/components/data_table/data_table_tr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {Component, Input, Inject, forwardRef, ElementRef, AfterContentInit} from 'angular2/core';
import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
import 'rxjs/add/operator/map';
import {MdDataTable} from './data_table';
import {MdCheckbox} from '../checkbox/checkbox';

@Component({
selector: 'tr',
template: `
<th [ngClass]="{active: isActive}" *ngIf="thDisplayed">
<md-checkbox #check
[checked]="isActive"
(click)="change()"></md-checkbox>
</th>
<td [ngClass]="{active: isActive}" *ngIf="tdDisplayed">
<md-checkbox #check
[checked]="isActive"
(click)="change()"></md-checkbox>
</td>
<ng-content></ng-content>
`,
directives: [MdCheckbox],
host: {
'[class.active]': 'isActive'
}
})
export class MdDataTableTr implements AfterContentInit {
@Input() selectableValue: string;
isInHeader: boolean = false;
isActive: boolean = false;
thDisplayed: boolean = false;
tdDisplayed: boolean = false;

constructor(@Inject(forwardRef(() => MdDataTable)) public table: MdDataTable, private _element: ElementRef) {

}

change() {
this.isActive = !this.isActive;
this.table.toggleActivity(this);
}

_initListeners() {
if (true === this.isInHeader) {
this.table.onSelectableChange
.map(event => event.allSelected)
.subscribe(newActiveStatus => this.isActive = newActiveStatus);
} else {
this.table.onSelectableChange
.map(event => {
return undefined !== event.values &&
event.values.length &&
(event.values.findIndex((value: string) => value === this.selectableValue)) !== -1;
})
.subscribe(newActiveStatus => this.isActive = newActiveStatus);
}
}

ngAfterContentInit() {
let element = this._element.nativeElement;
this.isInHeader = element.parentElement.localName === 'thead';
this._initListeners();

this.thDisplayed = this.table.selectable && this.isInHeader;
this.tdDisplayed = this.table.selectable && !this.isInHeader;

if (false === this.isInHeader && undefined === this.selectableValue) {
this.selectableValue = Array.prototype.indexOf.call(element.parentNode.children, element).toString();
}
}
}
Loading

0 comments on commit 04d1152

Please sign in to comment.