Skip to content

Commit d1e8ad9

Browse files
committed
feat(data-table): initial prototype with sorting
1 parent 0560eab commit d1e8ad9

18 files changed

+1232
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
md-data-table {
2+
padding: 16px;
3+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<md-input-container>
2+
<input mdInput name="searchTerm"
3+
placeholder="Data filter"
4+
[ngModel]="dataSource.filter"
5+
(ngModelChange)="onSearchChanged($event)">
6+
</md-input-container>
7+
8+
<md-data-table class="mat-elevation-z1"
9+
[dataSource]="dataSource"
10+
(onSort)="onSort($event)"
11+
[initialSortColumn]="'name'"
12+
[defaultSortOrder]="'ascending'">
13+
<md-header-row>
14+
<md-header-cell sortKey="name">Name</md-header-cell>
15+
<md-header-cell sortKey="movie">Movie</md-header-cell>
16+
</md-header-row>
17+
<md-row *mdRowContext="let characterData = row; when: characterIsVillan" (click)="rowClicked(characterData)">
18+
<md-cell>{{characterData.name}} (villan).</md-cell>
19+
<md-cell>{{characterData.movie}}</md-cell>
20+
</md-row>
21+
<md-row *mdRowContext="let characterData = row; when: lastCharacterDisplayed" (click)="rowClicked(characterData)">
22+
<md-cell>{{characterData.name}} is last.</md-cell>
23+
<md-cell>{{characterData.movie}}</md-cell>
24+
</md-row>
25+
<md-row *mdRowContext="let characterData = row" (click)="rowClicked(characterData)">
26+
<md-cell>{{characterData.name}}</md-cell>
27+
<md-cell>{{characterData.movie}}</md-cell>
28+
</md-row>
29+
</md-data-table>
30+
31+
<p *ngIf="lastRowClicked">
32+
You just clicked on: {{lastRowClicked.name}} from {{lastRowClicked.movie}}
33+
</p>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {Component} from '@angular/core';
2+
import {TableDemoDataSource, Character} from './demo-data-source';
3+
import {MdTableSortData, NgForContext} from '@angular/material';
4+
5+
6+
@Component({
7+
moduleId: module.id,
8+
selector: 'data-table-demo',
9+
templateUrl: 'data-table-demo.html',
10+
styleUrls: ['data-table-demo.css']
11+
})
12+
export class DataTableDemo {
13+
dataSource = new TableDemoDataSource();
14+
15+
lastRowClicked: Character;
16+
17+
onSearchChanged(val: string) {
18+
this.dataSource.filter = val;
19+
this.dataSource.loadTableRows();
20+
}
21+
22+
onSort(event: MdTableSortData) {
23+
this.dataSource.sortOrder = event.sortOrder;
24+
this.dataSource.sortColumn = event.sortColumn;
25+
this.dataSource.loadTableRows();
26+
}
27+
28+
characterIsVillan(row: Character): boolean {
29+
return row.villan;
30+
}
31+
32+
lastCharacterDisplayed(row: Character, context: NgForContext) {
33+
return context.last;
34+
}
35+
36+
rowClicked(row: Character) {
37+
this.lastRowClicked = row;
38+
}
39+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import {Observable} from 'rxjs/Observable';
2+
import {BehaviorSubject} from 'rxjs/Rx';
3+
import {MdTableSortOrder, MdTableDataSource, MdTableRows} from '@angular/material';
4+
5+
export interface Character {
6+
name: string;
7+
movie: string;
8+
villan?: boolean;
9+
}
10+
11+
export const CHARACTERS = [
12+
{name: 'Goofy', movie: 'A Goofy Movie'},
13+
{name: 'Tinker Bell', movie: 'Peter Pan'},
14+
{name: 'Thumper', movie: 'Bambi'},
15+
{name: 'Mad Hatter', movie: 'Alice in Wonderland'},
16+
{name: 'Kronk', movie: 'The Emperor\'s New Groove', villan: true},
17+
{name: 'Gus Gus', movie: 'Cinderella'},
18+
{name: 'Jiminy Cricket', movie: 'Pinocchio'},
19+
{name: 'Tigger', movie: 'Winnie the Pooh'},
20+
{name: 'Gaston', movie: 'Beauty and the Beast', villan: true},
21+
{name: 'Dumbo', movie: 'Dumbo'},
22+
{name: 'Jafar', movie: 'Aladdin', villan: true},
23+
{name: 'Lilo', movie: 'Lilo and Stitch'},
24+
{name: 'Sebastian', movie: 'The Little Mermaid'},
25+
{name: 'Jane', movie: 'Tarzan'},
26+
{name: 'Pumbaa', movie: 'The Lion King'},
27+
{name: 'Mulan', movie: 'Mulan'},
28+
];
29+
30+
export class TableDemoDataSource implements MdTableDataSource<Character> {
31+
private readonly rowSubject =
32+
new BehaviorSubject<MdTableRows<Character>>({rows: [], rowCount: 0});
33+
34+
filter: string;
35+
sortOrder: MdTableSortOrder;
36+
sortColumn: string;
37+
38+
constructor() {
39+
this.loadTableRows();
40+
}
41+
42+
/**
43+
* Returns an observable the table watches in order to update rows.
44+
* @override
45+
*/
46+
getRows(): Observable<MdTableRows<Character>> {
47+
return this.rowSubject.asObservable();
48+
}
49+
50+
/**
51+
* Updates the table based on the table settings and filters.
52+
*/
53+
loadTableRows() {
54+
this.getRowsFromServer().subscribe(filteredRows => {
55+
const rows = {rows: filteredRows, rowCount: filteredRows.length};
56+
this.rowSubject.next(rows);
57+
});
58+
}
59+
60+
/**
61+
* Simulates getting a list of filtered rows from the server with a delay.
62+
*/
63+
getRowsFromServer(): Observable<Character[]> {
64+
const filteredRows = CHARACTERS.filter(this.matchesSearchTerm.bind(this));
65+
if (this.sortColumn) {
66+
filteredRows.sort(this.compareRows.bind(this));
67+
if (this.sortOrder === 'descending') {
68+
filteredRows.reverse();
69+
}
70+
}
71+
72+
return Observable.of(filteredRows);
73+
}
74+
75+
private matchesSearchTerm(row: Character): boolean {
76+
if (!this.filter) {
77+
return true; // Everything matches.
78+
}
79+
80+
return (row.name + row.movie).toLowerCase().indexOf(this.filter.toLowerCase()) != -1;
81+
}
82+
83+
private compareRows(a: Character, b: Character): number {
84+
if (!this.sortColumn) { return 0; }
85+
86+
let valueA: string;
87+
let valueB: string;
88+
if (this.sortColumn == 'name') {
89+
valueA = a.name;
90+
valueB = b.name;
91+
} else if (this.sortColumn == 'movie') {
92+
valueA = a.movie;
93+
valueB = b.movie;
94+
}
95+
96+
// For arbitrary objects, if the valueOf method is overridden, then
97+
// comparison will use that. Otherwise, sorting will do nothing.
98+
if (valueA < valueB) {
99+
return -1;
100+
} else if (valueA > valueB) {
101+
return 1;
102+
} else {
103+
return 0;
104+
}
105+
}
106+
}

src/demo-app/demo-app-module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {PlatformDemo} from './platform/platform-demo';
4343
import {AutocompleteDemo} from './autocomplete/autocomplete-demo';
4444
import {InputDemo} from './input/input-demo';
4545
import {StyleDemo} from './style/style-demo';
46+
import {DataTableDemo} from './data-table/data-table-demo';
4647

4748
@NgModule({
4849
imports: [
@@ -62,6 +63,7 @@ import {StyleDemo} from './style/style-demo';
6263
CardDemo,
6364
ChipsDemo,
6465
CheckboxDemo,
66+
DataTableDemo,
6567
DemoApp,
6668
DialogDemo,
6769
GesturesDemo,

src/demo-app/demo-app/demo-app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class DemoApp {
2626
{name: 'Card', route: 'card'},
2727
{name: 'Chips', route: 'chips'},
2828
{name: 'Checkbox', route: 'checkbox'},
29+
{name: 'Data Table', route: 'data-table'},
2930
{name: 'Dialog', route: 'dialog'},
3031
{name: 'Gestures', route: 'gestures'},
3132
{name: 'Grid List', route: 'grid-list'},

src/demo-app/demo-app/routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@ import {PlatformDemo} from '../platform/platform-demo';
3333
import {AutocompleteDemo} from '../autocomplete/autocomplete-demo';
3434
import {InputDemo} from '../input/input-demo';
3535
import {StyleDemo} from '../style/style-demo';
36+
import {DataTableDemo} from '../data-table/data-table-demo';
3637

3738
export const DEMO_APP_ROUTES: Routes = [
3839
{path: '', component: Home},
3940
{path: 'autocomplete', component: AutocompleteDemo},
4041
{path: 'button', component: ButtonDemo},
4142
{path: 'card', component: CardDemo},
4243
{path: 'chips', component: ChipsDemo},
44+
{path: 'data-table', component: DataTableDemo},
4345
{path: 'radio', component: RadioDemo},
4446
{path: 'select', component: SelectDemo},
4547
{path: 'sidenav', component: SidenavDemo},

src/lib/data-table/data-source.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {Observable} from 'rxjs/Observable';
2+
3+
export interface MdTableRows<T> {
4+
rows: T[];
5+
rowCount: number;
6+
}
7+
8+
export interface MdTableDataSource<T> {
9+
getRows(): Observable<MdTableRows<T>>;
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {MdError} from '../core/errors/error';
2+
3+
/**
4+
* Exception thrown when a tooltip has an invalid position.
5+
* @docs-private
6+
*/
7+
export class MdTableInvalidDataSourceError extends MdError {
8+
constructor() {
9+
super('MdDataTable: No dataSource provided.');
10+
}
11+
}

src/lib/data-table/data-table.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<ng-content select="md-header-row"></ng-content>
2+
<template *ngFor="let row of rows; let index = index; let first = first;
3+
let even = even; let odd = odd;"
4+
[ngTemplateOutlet]="getTemplateForRow(row, index, first, even, odd)"
5+
[ngOutletContext]="{
6+
row: row,
7+
index: index,
8+
first: first,
9+
last: isLast(index),
10+
even: even,
11+
odd: odd
12+
}">
13+
</template>

src/lib/data-table/data-table.scss

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
$table-border-color: #000;
2+
3+
.mat-table {
4+
display: block;
5+
position: relative;
6+
}
7+
8+
.mat-table-row,
9+
.mat-table-header-row {
10+
display: flex;
11+
width: 100%;
12+
}
13+
14+
.mat-table-cell,
15+
.mat-table-header-cell {
16+
display: flex;
17+
height: 48px;
18+
justify-content: flex-start;
19+
align-items: center;
20+
flex: 1;
21+
}
22+
23+
.mat-table-header-cell {
24+
font-weight: bold;
25+
26+
&.mat-table-sortable {
27+
cursor: pointer;
28+
}
29+
30+
@mixin mat-table-sort-arrow($direction) {
31+
&::after {
32+
content: '\\25b2';
33+
font-size: small;
34+
35+
@if $direction == 'ascending' {
36+
padding-right: 4px;
37+
transform: rotate(180deg);
38+
} @else {
39+
padding-left: 4px;
40+
}
41+
}
42+
}
43+
44+
&.mat-table-sort-descending {
45+
@include mat-table-sort-arrow('descending');
46+
}
47+
48+
&.mat-table-sort-ascending {
49+
@include mat-table-sort-arrow('ascending');
50+
}
51+
}

0 commit comments

Comments
 (0)