diff --git a/package.json b/package.json
index a42d655546..875623c756 100644
--- a/package.json
+++ b/package.json
@@ -53,14 +53,16 @@
"@angular/core": "^2.0.0",
"@angular/forms": "^2.0.0",
"@angular/http": "^2.0.0",
+ "@angular/material": "2.0.0-alpha.9-3",
"@angular/platform-browser": "^2.0.0",
"@angular/platform-browser-dynamic": "^2.0.0",
"@angular/platform-server": "^2.0.0",
"@angular/router": "^3.0.0",
- "@angular/material": "2.0.0-alpha.9-3",
+ "@types/lodash": "^4.14.36",
"core-js": "^2.4.1",
"hammerjs": "^2.0.8",
"highlight.js": "9.6.0",
+ "lodash": "^4.16.3",
"rxjs": "5.0.0-beta.12",
"showdown": "1.4.2",
"zone.js": "0.6.21"
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 6579ffe005..0505324e12 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -16,6 +16,7 @@ import { CovalentHttpModule } from '../platform/http';
import { CovalentMarkdownModule } from '../platform/markdown';
import { CovalentJsonFormatterModule } from '../platform/json-formatter';
import { CovalentChipsModule } from '../platform/chips';
+import { CovalentDataTableModule } from '../platform/data-table';
@NgModule({
declarations: [
@@ -35,6 +36,7 @@ import { CovalentChipsModule } from '../platform/chips';
CovalentMarkdownModule.forRoot(),
CovalentJsonFormatterModule.forRoot(),
CovalentChipsModule.forRoot(),
+ CovalentDataTableModule.forRoot(),
appRoutes,
], // modules needed to run this module
providers: [
diff --git a/src/app/components/components/components.component.ts b/src/app/components/components/components.component.ts
index f14901768a..09508e57a9 100644
--- a/src/app/components/components/components.component.ts
+++ b/src/app/components/components/components.component.ts
@@ -42,6 +42,11 @@ export class ComponentsComponent {
icon: 'open_in_browser',
route: 'dialogs',
title: 'Simple Dialogs',
+ }, {
+ description: 'Data Table',
+ icon: 'grid_on',
+ route: 'data-table',
+ title: 'Data Table',
}, {
description: 'Highlighting your code snippets',
icon: 'code',
diff --git a/src/app/components/components/components.module.ts b/src/app/components/components/components.module.ts
index 748ce717ae..66412f4e68 100644
--- a/src/app/components/components/components.module.ts
+++ b/src/app/components/components/components.module.ts
@@ -17,6 +17,7 @@ import { ChipsDemoComponent } from './chips/chips.component';
import { DialogsDemoComponent } from './dialogs/dialogs.component';
import { DirectivesComponent } from './directives/directives.component';
import { PipesComponent } from './pipes/pipes.component';
+import { DataTableDemoComponent } from './data-table/data-table.component';
import { CovalentCoreModule } from '../../../platform/core';
import { CovalentFileModule } from '../../../platform/file-upload';
@@ -24,6 +25,7 @@ import { CovalentHighlightModule } from '../../../platform/highlight';
import { CovalentMarkdownModule } from '../../../platform/markdown';
import { CovalentJsonFormatterModule } from '../../../platform/json-formatter';
import { CovalentChipsModule } from '../../../platform/chips';
+import { CovalentDataTableModule } from '../../../platform/data-table';
@NgModule({
declarations: [
@@ -42,6 +44,7 @@ import { CovalentChipsModule } from '../../../platform/chips';
DialogsDemoComponent,
DirectivesComponent,
PipesComponent,
+ DataTableDemoComponent,
],
imports: [
CovalentCoreModule.forRoot(),
@@ -50,6 +53,7 @@ import { CovalentChipsModule } from '../../../platform/chips';
CovalentMarkdownModule.forRoot(),
CovalentJsonFormatterModule.forRoot(),
CovalentChipsModule.forRoot(),
+ CovalentDataTableModule.forRoot(),
componentsRoutes,
],
})
diff --git a/src/app/components/components/components.routes.ts b/src/app/components/components/components.routes.ts
index 7b64fbd594..a9971a59d2 100644
--- a/src/app/components/components/components.routes.ts
+++ b/src/app/components/components/components.routes.ts
@@ -15,6 +15,7 @@ import { ChipsDemoComponent } from './chips/chips.component';
import { DialogsDemoComponent } from './dialogs/dialogs.component';
import { DirectivesComponent } from './directives/directives.component';
import { PipesComponent } from './pipes/pipes.component';
+import { DataTableDemoComponent } from './data-table/data-table.component';
const routes: Routes = [{
children: [{
@@ -59,6 +60,9 @@ const routes: Routes = [{
}, {
component: PipesComponent,
path: 'pipes',
+ }, {
+ component: DataTableDemoComponent,
+ path: 'data-table',
},
],
component: ComponentsComponent,
diff --git a/src/app/components/components/data-table/data-table.component.html b/src/app/components/components/data-table/data-table.component.html
new file mode 100644
index 0000000000..0c778f0e97
--- /dev/null
+++ b/src/app/components/components/data-table/data-table.component.html
@@ -0,0 +1,135 @@
+
+ Data Table
+ Data driven table layout with search, sorting and pagination
+
+
+ Basic Demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TdDataTableComponent
+ How to use this component
+
+
+ ]]>
+ Use ]]>
element to generate a table.
+ Pass an array of javascript objects with the information to be displayed on the table to the [data] attribute.
+ Properties:
+ The ]]>
component has {{dataTableAttrs.length}} properties:
+
+
+
+ {{attr.name}}: {{attr.type}}
+ {{attr.description}}
+
+
+
+
+ Example:
+ HTML:
+
+
+
+ ]]>
+
+ Typescript:
+
+ v.toFixed(2) },
+ ];
+ }
+ ]]>
+
+ Setup:
+ Import the [CovalentDataTableModule] using the forRoot()
method in your NgModule:
+
+
+
+
+
diff --git a/src/app/components/components/data-table/data-table.component.scss b/src/app/components/components/data-table/data-table.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/components/components/data-table/data-table.component.ts b/src/app/components/components/data-table/data-table.component.ts
new file mode 100644
index 0000000000..8838ebbb10
--- /dev/null
+++ b/src/app/components/components/data-table/data-table.component.ts
@@ -0,0 +1,232 @@
+import { Component } from '@angular/core';
+
+import { TdDataTableSortingOrder } from '../../../../platform/data-table';
+
+const NUMBER_FORMAT: any = (v: {value: number}) => v.value;
+const DECIMAL_FORMAT: any = (v: {value: number}) => v.value.toFixed(2);
+
+@Component({
+ selector: 'data-table-demo',
+ styleUrls: ['data-table.component.scss'],
+ templateUrl: 'data-table.component.html',
+})
+export class DataTableDemoComponent {
+
+ dataTableAttrs: Object[] = [{
+ description: `Rows of data to be displayed`,
+ name: 'data',
+ type: 'any[]',
+ }, {
+ description: `List of columns to be displayed`,
+ name: 'columns?',
+ type: 'ITdDataTableColumn[]',
+ }, {
+ description: `If present will display a title before the table`,
+ name: 'title?',
+ type: 'string',
+ }, {
+ description: `Enables pagination`,
+ name: 'pagination?',
+ type: 'boolean',
+ }, {
+ description: `Number of rows per page, when omitted defaults to 10`,
+ name: 'pageSize?',
+ type: 'number',
+ }, {
+ description: `Enables sorting by column`,
+ name: 'sorting?',
+ type: 'boolean',
+ }, {
+ description: `Name of the column to use for sorting`,
+ name: 'sortBy?',
+ type: 'string',
+ }, {
+ description: `Sorting order - ascending or descending`,
+ name: 'sortOrder?',
+ type: `'ASC' | 'DESC'`,
+ }, {
+ description: `Enables search`,
+ name: 'search?',
+ type: `boolean`,
+ }, {
+ description: `Adds a checkbox column to allow user to select rows`,
+ name: 'rowSelection?',
+ type: 'boolean',
+ }, {
+ description: `Toggles between multiple or single row selection`,
+ name: 'multiple?',
+ type: 'boolean',
+ }];
+
+ columns: any[] = [
+ { name: 'name', label: 'Dessert (100g serving)' },
+ { name: 'type', label: 'Type' },
+ { name: 'calories', label: 'Calories', numeric: true, format: NUMBER_FORMAT },
+ { name: 'fat', label: 'Fat (g)', numeric: true, format: DECIMAL_FORMAT },
+ { name: 'carbs', label: 'Carbs (g)', numeric: true, format: NUMBER_FORMAT },
+ { name: 'protein', label: 'Protein (g)', numeric: true, format: DECIMAL_FORMAT },
+ { name: 'sodium', label: 'Sodium (mg)', numeric: true, format: NUMBER_FORMAT },
+ { name: 'calcium', label: 'Calcium (%)', numeric: true, format: NUMBER_FORMAT },
+ { name: 'iron', label: 'Iron (%)', numeric: true, format: NUMBER_FORMAT },
+ ];
+
+ sorting: boolean = true;
+ pagination: boolean = true;
+ pageSize: number = 5;
+
+ data: any[] = [
+ {
+ 'name': 'Frozen yogurt',
+ 'type': 'Ice cream',
+ 'calories': { 'value': 159.0 },
+ 'fat': { 'value': 6.0 },
+ 'carbs': { 'value': 24.0 },
+ 'protein': { 'value': 4.0 },
+ 'sodium': { 'value': 87.0 },
+ 'calcium': { 'value': 14.0 },
+ 'iron': { 'value': 1.0 },
+ }, {
+ 'name': 'Ice cream sandwich',
+ 'type': 'Ice cream',
+ 'calories': { 'value': 237.0 },
+ 'fat': { 'value': 9.0 },
+ 'carbs': { 'value': 37.0 },
+ 'protein': { 'value': 4.3 },
+ 'sodium': { 'value': 129.0 },
+ 'calcium': { 'value': 8.0 },
+ 'iron': { 'value': 1.0 },
+ }, {
+ 'name': 'Eclair',
+ 'type': 'Pastry',
+ 'calories': { 'value': 262.0 },
+ 'fat': { 'value': 16.0 },
+ 'carbs': { 'value': 24.0 },
+ 'protein': { 'value': 6.0 },
+ 'sodium': { 'value': 337.0 },
+ 'calcium': { 'value': 6.0 },
+ 'iron': { 'value': 7.0 },
+ }, {
+ 'name': 'Cupcake',
+ 'type': 'Pastry',
+ 'calories': { 'value': 305.0 },
+ 'fat': { 'value': 3.7 },
+ 'carbs': { 'value': 67.0 },
+ 'protein': { 'value': 4.3 },
+ 'sodium': { 'value': 413.0 },
+ 'calcium': { 'value': 3.0 },
+ 'iron': { 'value': 8.0 },
+ }, {
+ 'name': 'Jelly bean',
+ 'type': 'Candy',
+ 'calories': { 'value': 375.0 },
+ 'fat': { 'value': 0.0 },
+ 'carbs': { 'value': 94.0 },
+ 'protein': { 'value': 0.0 },
+ 'sodium': { 'value': 50.0 },
+ 'calcium': { 'value': 0.0 },
+ 'iron': { 'value': 0.0 },
+ }, {
+ 'name': 'Lollipop',
+ 'type': 'Candy',
+ 'calories': { 'value': 392.0 },
+ 'fat': { 'value': 0.2 },
+ 'carbs': { 'value': 98.0 },
+ 'protein': { 'value': 0.0 },
+ 'sodium': { 'value': 38.0 },
+ 'calcium': { 'value': 0.0 },
+ 'iron': { 'value': 2.0 },
+ }, {
+ 'name': 'Honeycomb',
+ 'type': 'Other',
+ 'calories': { 'value': 408.0 },
+ 'fat': { 'value': 3.2 },
+ 'carbs': { 'value': 87.0 },
+ 'protein': { 'value': 6.5 },
+ 'sodium': { 'value': 562.0 },
+ 'calcium': { 'value': 0.0 },
+ 'iron': { 'value': 45.0 },
+ }, {
+ 'name': 'Donut',
+ 'type': 'Pastry',
+ 'calories': { 'value': 452.0 },
+ 'fat': { 'value': 25.0 },
+ 'carbs': { 'value': 51.0 },
+ 'protein': { 'value': 4.9 },
+ 'sodium': { 'value': 326.0 },
+ 'calcium': { 'value': 2.0 },
+ 'iron': { 'value': 22.0 },
+ }, {
+ 'name': 'KitKat',
+ 'type': 'Candy',
+ 'calories': { 'value': 518.0 },
+ 'fat': { 'value': 26.0 },
+ 'carbs': { 'value': 65.0 },
+ 'protein': { 'value': 7.0 },
+ 'sodium': { 'value': 54.0 },
+ 'calcium': { 'value': 12.0 },
+ 'iron': { 'value': 6.0 },
+ },
+ ];
+
+ sortBy: string = 'name';
+ sortOrder: string = 'ASC';
+
+ rowSelection: boolean = false;
+ multiple: boolean = true;
+
+ toggleRowSelection(): void {
+ this.rowSelection = !this.rowSelection;
+ }
+
+ toggleRowSelectionMultiple(): void {
+ this.multiple = !this.multiple;
+ }
+
+ toggleSorting(): void {
+ this.sorting = !this.sorting;
+ }
+
+ toggleSortBy(): void {
+ const columns: any[] = this.columns.map((c: any) => c.name);
+ const idx: number = columns.indexOf(this.sortBy);
+ if (idx < columns.length - 1) {
+ this.sortBy = columns[idx + 1];
+ } else {
+ this.sortBy = columns[0];
+ }
+ }
+
+ toggleSortOrder(): void {
+ this.sortOrder = this.sortOrder === 'ASC' ? 'DESC' : 'ASC';
+ }
+
+ areTooltipsOn(): boolean {
+ return this.columns[0].hasOwnProperty('tooltip');
+ }
+
+ toggleTooltips(): void {
+ if (this.columns[0].tooltip) {
+ this.columns.forEach((c: any) => delete c.tooltip);
+ } else {
+ this.columns.forEach((c: any) => c.tooltip = `This is ${c.label}!`);
+ }
+ }
+
+ togglePagination(): void {
+ this.pagination = !this.pagination;
+ }
+
+ togglePageSize(): void {
+ this.pageSize += 5;
+ if (this.pageSize > 15) {
+ this.pageSize = 5;
+ }
+ }
+
+ sortChanged(changes: any): void {
+ const { column, order }: any = changes;
+
+ this.sortBy = column.name;
+ this.sortOrder = order === TdDataTableSortingOrder.Ascending ? 'ASC' : 'DESC';
+ }
+}
diff --git a/src/app/components/components/overview/overview.component.ts b/src/app/components/components/overview/overview.component.ts
index 5704b67aed..dd169d7f05 100644
--- a/src/app/components/components/overview/overview.component.ts
+++ b/src/app/components/components/overview/overview.component.ts
@@ -36,6 +36,11 @@ export class ComponentsOverviewComponent {
icon: 'open_in_browser',
route: 'dialogs',
title: 'Simple Dialogs',
+ }, {
+ color: 'green-700',
+ icon: 'grid_on',
+ route: 'data-table',
+ title: 'Data Table',
}, {
color: 'pink-700',
icon: 'code',
diff --git a/src/platform/data-table/README.md b/src/platform/data-table/README.md
new file mode 100644
index 0000000000..91798afa37
--- /dev/null
+++ b/src/platform/data-table/README.md
@@ -0,0 +1,53 @@
+# td-data-table
+
+`td-data-table` element generates a data driven table layout with search, sorting and pagination.
+
+## API Summary
+
+Properties:
+
+| Name | Type | Description |
+| --- | --- | --- |
+| `data` | `any[]` | Rows of data to be displayed
+| `columns` | `ITdDataTableColumn[]` | List of columns to be displayed
+| `title?` | `string` | If present will display a title before the table
+| `pagination?` | `boolean` | Enables pagination
+| `pageSize?` | `number` | Number of rows per page, when omitted defaults to 10
+| `sorting?` | `boolean` | Enables sorting by column
+| `sortBy?` | `string` | Name of the column to use for sorting
+| `sortOrder?` | `'ASC' | 'DESC'` | Sorting order - ascending or descending
+| `search?` | `boolean` | Enables search
+| `rowSelection?` | `boolean` | Adds a checkbox column to allow user to select rows
+| `multiple?` | `boolean` | Toggles between multiple or single row selection
+
+## Setup
+
+Import the [CovalentDataTableModule] using the forRoot() method in your NgModule:
+
+```typescript
+import { CovalentDataTableModule } from '@covalent/chips';
+@NgModule({
+ imports: [
+ CovalentDataTableModule.forRoot(),
+ ...
+ ],
+ ...
+})
+export class MyModule {}
+```
+
+## Usage
+
+Example for HTML usage:
+
+ ```html
+
+
+ ```
diff --git a/src/platform/data-table/_data-table-theme.scss b/src/platform/data-table/_data-table-theme.scss
new file mode 100644
index 0000000000..b800380c84
--- /dev/null
+++ b/src/platform/data-table/_data-table-theme.scss
@@ -0,0 +1,62 @@
+@import '~@angular/material/core/theming/palette';
+@import '~@angular/material/core/theming/theming';
+
+// TODO properly wrap in mixin to apply theme like material2 components
+$foreground: $md-light-theme-foreground;
+$background: $md-light-theme-background;
+$accent: md-palette($md-light-blue, 50);
+
+// Table
+.md-row {
+ border-bottom-color: md-color($foreground, divider);
+}
+.md-checkbox-cell,
+.md-checkbox-column {
+ color: md-color($foreground, secondary-text);
+}
+.md-column {
+ color: md-color($foreground, secondary-text);
+ md-icon {
+ &.md-sort-icon {
+ color: md-color($foreground, disabled);
+ }
+ }
+ &.md-active,
+ &.md-active md-icon {
+ color: md-color($foreground, base);
+ }
+}
+&.md-row-select tbody.md-body > tr.md-row {
+ &:not([disabled]):hover {
+ background-color: md-color($background, 'hover');
+ }
+ &.md-selected {
+ background-color: md-color($accent, 0.12);
+ }
+}
+
+// Pagination
+.md-table-pagination {
+ color: md-color($foreground, secondary-text);
+
+ md-select {
+ &:not([disabled]):focus .md-select-value {
+ color: md-color($foreground, secondary-text);
+ }
+ }
+}
+
+// Dialog
+md-edit-dialog {
+ background-color: md-color($background, dialog);
+ > .md-content {
+ .md-title {
+ color: md-color($foreground, text);
+ }
+ md-input-container {
+ .md-errors-spacer {
+ color: md-color($foreground, secondary-text);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/platform/data-table/data-table.component.html b/src/platform/data-table/data-table.component.html
new file mode 100644
index 0000000000..7ea7842934
--- /dev/null
+++ b/src/platform/data-table/data-table.component.html
@@ -0,0 +1,100 @@
+
+ {{_title}}
+
+
+
+
+
+ search
+
+
+
+
+ 0" title>
+
+
+
+
+
+
+ |
+
+
+ arrow_upward
+
+
+ {{column.label}}
+ {{column.label}}
+
+
+ arrow_upward
+
+ |
+
+
+
+
+
+
+
+ |
+
+ {{ row[column.name] }}
+ |
+
+
+
+
+
+
+ No results
+
+
+
+
+
+ No pages
+
\ No newline at end of file
diff --git a/src/platform/data-table/data-table.component.scss b/src/platform/data-table/data-table.component.scss
new file mode 100644
index 0000000000..4a97a63a4c
--- /dev/null
+++ b/src/platform/data-table/data-table.component.scss
@@ -0,0 +1,364 @@
+@import 'data-table-theme';
+
+$backdrop-index: 80;
+$checkbox-size: 18px;
+
+.md-table-container {
+ display: block;
+ max-width: 100%;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+}
+
+table.md-table {
+ width: 100%;
+ border-spacing: 0;
+ overflow: hidden;
+ border-collapse: collapse;
+
+ tr.md-row {
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ }
+
+ thead.md-head > tr.md-row {
+ height: 56px;
+ }
+
+ tbody.md-body > tr.md-row,
+ tfoot.md-foot > tr.md-row {
+ height: 48px;
+ }
+
+ thead.md-head + .md-table-progress md-progress-linear {
+ top: -3px;
+ }
+
+ .md-table-progress th {
+ padding: 0;
+
+ md-progress-linear {
+ height: 0;
+ transition: opacity 1s;
+
+ &.ng-hide {
+ opacity: 0;
+ }
+
+ > .md-container {
+ height: 3px;
+ top: 0;
+ transition: none;
+
+ > .md-bar {
+ height: 3px;
+ }
+ }
+ }
+ }
+
+ th.md-column {
+ //color: $md-dark-secondary;
+ font-size: 12px;
+ font-weight: bold;
+ white-space: nowrap;
+
+ &.md-sort {
+ cursor: pointer;
+ }
+
+ md-icon {
+ height: 16px;
+ width: 16px;
+ font-size: 16px !important;
+ line-height: 16px !important;
+
+ &.md-sort-icon {
+ opacity: 0;
+ transition: transform 0.25s, opacity 0.25s;
+
+ &.md-asc {
+ transform: rotate(0deg);
+ }
+
+ &.md-desc {
+ transform: rotate(180deg);
+ }
+ }
+
+ &:not(:first-child) {
+ margin-left: 8px;
+ }
+
+ &:not(:last-child) {
+ margin-right: 8px;
+ }
+ }
+
+ &:hover md-icon.md-sort-icon,
+ &.md-active md-icon.md-sort-icon {
+ opacity: 1;
+ }
+ }
+
+ tr.md-row {
+ &[ng\:repeat].ng-leave,
+ &[ng-repeat].ng-leave,
+ &[x-ng-repeat].ng-leave,
+ &[data-ng-repeat].ng-leave {
+ display: none;
+ }
+ }
+
+ &.md-row-select tbody.md-body > tr.md-row {
+ transition: background-color 0.2s;
+ }
+
+ &.md-row-select td.md-cell,
+ &.md-row-select th.md-column {
+ &:first-child {
+ width: 20px;
+ padding: 0 0 0 24px;
+ }
+
+ &:nth-child(2) {
+ padding: 0 24px;
+ }
+
+ &:nth-child(n+3):nth-last-child(n+2) {
+ padding: 0 56px 0 0;
+ }
+ }
+
+ &:not(.md-row-select) td.md-cell,
+ &:not(.md-row-select) th.md-column {
+ &:first-child {
+ padding: 0 24px;
+ }
+
+ &:nth-child(n+2):nth-last-child(n+2) {
+ padding: 0 56px 0 0;
+ }
+ }
+
+ td.md-cell,
+ th.md-column {
+ vertical-align: middle;
+ text-align: left;
+
+ > * {
+ vertical-align: middle;
+ }
+
+ &:last-child {
+ padding: 0 24px 0 0;
+ }
+
+ &.md-clickable {
+ cursor: pointer;
+
+ &:focus {
+ outline: none;
+ }
+ }
+
+ &.md-numeric {
+ text-align: right;
+ }
+ }
+
+ td.md-checkbox-cell,
+ th.md-checkbox-column {
+ width: $checkbox-size;
+ font-size: 0 !important;
+ md-checkbox {
+ /deep/ .md-checkbox-inner-container {
+ width: $checkbox-size;
+ height: $checkbox-size;
+ margin: 0;
+ }
+ }
+ }
+
+ td.md-cell {
+ font-size: 13px;
+ //border-top: 1px $md-dark-divider solid;
+
+ &.md-numeric md-select {
+ justify-content: flex-end;
+
+ .md-select-value {
+ flex: 0 0 auto;
+ }
+ }
+
+ // &.md-placeholder {
+ //color: $md-dark-disabled;
+ // }
+
+ md-select > .md-select-value > span.md-select-icon {
+ justify-content: flex-end;
+ //color: $md-dark-secondary;
+ width: 18px;
+ text-align: right;
+
+ &:after {
+ transform: scaleY(0.4) scaleX(0.8);
+ }
+ }
+ }
+}
+
+// Table in a card
+md-card {
+ > md-toolbar.md-table-toolbar:first-child,
+ > md-table-container:first-child {
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+ }
+
+ > md-toolbar.md-table-toolbar:last-child,
+ > md-table-container:last-child {
+ border-bottom-left-radius: 2px;
+ border-bottom-right-radius: 2px;
+ }
+}
+
+// Pagination
+.md-table-pagination {
+
+ md-select {
+ justify-content: flex-end;
+ min-width: 64px;
+
+ .md-select-value {
+ flex: 0 0 auto;
+
+ span.md-select-icon {
+ justify-content: center;
+ text-align: center;
+ margin-right: -6px !important;
+
+ &:after {
+ top: initial;
+ transform: scaleY(0.5) scaleX(1);
+ }
+ }
+ }
+ }
+}
+
+// Select
+md-select.md-table-select {
+ margin: 0;
+
+ > .md-select-value {
+ padding: 0;
+ min-width: 0;
+ min-height: 24px;
+ border-bottom: 0 !important;
+
+ > span {
+ display: block;
+ height: auto;
+ transform: none !important;
+
+ > .md-text {
+ display: inherit;
+ height: inherit;
+ transform: inherit;
+ }
+
+ &.md-select-icon {
+ display: flex;
+ align-items: center;
+ height: 24px;
+ margin: 0;
+
+ &:after {
+ top: initial;
+ }
+ }
+ }
+ }
+}
+
+.md-select-menu-container.md-table-select,
+.md-select-menu-container.md-pagination-select {
+ margin-left: -2px;
+ border-radius: 2px;
+
+ md-select-menu,
+ md-content {
+ border-radius: inherit;
+ }
+
+ md-content {
+ padding: 0;
+ }
+}
+
+.md-select-menu-container.md-table-select .md-text {
+ font-size: 13px;
+}
+
+.md-select-menu-container.md-pagination-select .md-text {
+ font-size: 12px;
+}
+
+// Dialog
+md-backdrop.md-edit-dialog-backdrop {
+ z-index: $backdrop-index;
+}
+
+md-edit-dialog {
+ display: flex;
+ flex-direction: column;
+ position: fixed;
+ z-index: $backdrop-index + 1;
+ border-radius: 2px;
+ cursor: default;
+
+ > .md-content {
+ padding: 16px 24px 0;
+
+ .md-title {
+ margin-bottom: 8px;
+ }
+
+ md-input-container {
+ margin: 0;
+ font-size: 13px;
+
+ input {
+ float: none;
+ }
+
+ .md-errors-spacer {
+ min-height: auto;
+ min-width: auto;
+
+ .md-char-counter {
+ padding: 5px 2px 5px 0;
+ }
+ }
+
+ [ng-message] {
+ padding: 5px 0 5px 2px;
+ }
+ }
+ }
+
+ > .md-actions {
+ margin: 0 16px 8px;
+
+ .md-button {
+ margin: 0;
+ min-width: initial;
+
+ & + .md-button {
+ margin-left: 8px;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/platform/data-table/data-table.component.ts b/src/platform/data-table/data-table.component.ts
new file mode 100644
index 0000000000..d248e6d3de
--- /dev/null
+++ b/src/platform/data-table/data-table.component.ts
@@ -0,0 +1,330 @@
+import { Component, OnInit, AfterViewInit, Input, Output,
+ EventEmitter, ViewChildren, QueryList, Renderer } from '@angular/core';
+import { FormControl } from '@angular/forms';
+import { MdInput } from '@angular/material';
+import 'rxjs/add/operator/debounceTime';
+
+import * as _ from 'lodash';
+
+export enum TdDataTableSortingOrder {
+ Ascending, Descending
+};
+
+export interface ITdDataTableColumn {
+ name: string;
+ label: string;
+ tooltip?: string;
+ numeric?: boolean;
+ format?: { (value: any): any };
+};
+
+export interface ITdDataTableSortEvent {
+ column: ITdDataTableColumn;
+ order: TdDataTableSortingOrder;
+};
+
+@Component({
+ selector: 'td-data-table',
+ styleUrls: [ 'data-table.component.scss' ],
+ templateUrl: 'data-table.component.html',
+})
+export class TdDataTableComponent implements OnInit, AfterViewInit {
+
+ /** internal attributes */
+ private _data: any[];
+ private _visibleData: any[];
+ private _columns: ITdDataTableColumn[];
+ private _rowSelection: boolean;
+ private _rowSelectionField: string = 'selected';
+ private _multiple: boolean = true;
+ private _search: boolean = false;
+ private _hasData: boolean = false;
+ private _initialized: boolean = false;
+
+ /** pagination */
+ private _pageSize: number = 10;
+ private _currentPage: number = 0;
+ private _totalPages: number = 0;
+ private _pagination: boolean = false;
+
+ /** sorting */
+ private _sorting: boolean = false;
+ private _sortBy: ITdDataTableColumn;
+ private _sortOrder: TdDataTableSortingOrder = TdDataTableSortingOrder.Ascending;
+
+ /** search by term */
+ private _searchVisible: boolean = false;
+ private _searchTerm: string = '';
+ private _searchTermControl: FormControl = new FormControl();
+
+ @ViewChildren(MdInput) _searchTermInput: QueryList;
+
+ /** events */
+ @Output() sortChanged: EventEmitter = new EventEmitter();
+
+ /** td-data-table element attributes */
+ @Input('title') _title: string;
+
+ @Input('data')
+ set data(data: Object[]) {
+ this._data = data;
+ this.resetPagination();
+ }
+
+ @Input('pageSize')
+ set pageSize(size: string | number) {
+ if (typeof size === 'string') {
+ this._pageSize = parseInt(size, 10);
+ } else {
+ this._pageSize = size;
+ }
+ this._pagination = true;
+ this.resetPagination();
+ }
+
+ @Input('rowSelectionField')
+ set rowSelectionField(field: string) {
+ this._rowSelectionField = field;
+ }
+
+ @Input('rowSelection')
+ set rowSelection(selection: string | boolean) {
+ const settingAsString: boolean = typeof selection === 'string'
+ && selection !== 'true' && selection !== 'false';
+
+ if (settingAsString) {
+ this._rowSelection = true;
+ this._rowSelectionField = '' + selection;
+ } else {
+ this._rowSelection = selection !== '' ? (selection === 'true' || selection === true) : true;
+ }
+ }
+
+ @Input('multiple')
+ set multiple(multiple: string | boolean) {
+ this._multiple = multiple !== '' ? (multiple === 'true' || multiple === true) : true;
+ }
+
+ @Input('search')
+ set search(search: string | boolean) {
+ this._search = search !== '' ? (search === 'true' || search === true) : true;
+ }
+
+ @Input('columns')
+ set columns(cols: ITdDataTableColumn[]) {
+ this._columns = cols;
+ }
+ get columns(): ITdDataTableColumn[] {
+ if (this._columns) {
+ return this._columns;
+ }
+
+ if (!this._data) {
+ return [];
+ }
+
+ this._columns = [];
+ this._data.forEach((row: any) => {
+ Object.keys(row).forEach((k: string) => {
+ if (!this._columns.find((c: any) => c.name === k)) {
+ this._columns.push({ name: k, label: k });
+ }
+ });
+ });
+
+ return this._columns;
+ }
+
+ @Input('sorting')
+ set sorting(sorting: string | boolean) {
+ this._sorting = sorting !== '' ? (sorting === 'true' || sorting === true) : true;
+ }
+
+ @Input('sortBy')
+ set sortBy(columnName: string) {
+ if (!columnName) {
+ return;
+ }
+ const column: ITdDataTableColumn = this.columns.find((c: any) => c.name === columnName);
+ if (!column) {
+ throw '[sortBy] must be a valid column name';
+ }
+
+ this._sortBy = column;
+ this._sorting = true;
+ this.filterData();
+ }
+
+ @Input('sortOrder')
+ set sortOrder(order: 'ASC' | 'DESC') {
+ let sortOrder: string = order ? order.toUpperCase() : 'ASC';
+ if (sortOrder !== 'DESC' && sortOrder !== 'ASC') {
+ throw '[sortOrder] must be empty, ASC or DESC';
+ }
+
+ this._sortOrder = sortOrder === 'ASC' ?
+ TdDataTableSortingOrder.Ascending : TdDataTableSortingOrder.Descending;
+ this.filterData();
+ }
+
+ @Input('pagination')
+ set pagination(pagination: string | boolean) {
+ this._pagination = pagination !== '' ? (pagination === 'true' || pagination === true) : true;
+ this.resetPagination();
+ }
+
+ get hasData(): boolean {
+ return this._hasData;
+ }
+
+ constructor(private renderer: Renderer) {}
+
+ ngOnInit(): void {
+ this._searchTermControl.valueChanges
+ .debounceTime(250)
+ .subscribe(this.searchTermChanged.bind(this));
+ }
+
+ ngAfterViewInit(): void {
+ this.preprocessData();
+ this._initialized = true;
+ this.filterData();
+ }
+
+ areAllSelected(): boolean {
+ const match: string =
+ this._data.find((d: any) => !d[this._rowSelectionField]);
+ return typeof match === 'undefined';
+ }
+
+ selectAll(checked: boolean): void {
+ this._data.forEach((d: any) => d[this._rowSelectionField] = checked);
+ }
+
+ select(row: any, checked: boolean, event: Event): void {
+ event.preventDefault();
+ // clears all the fields for the dataset
+ if (!this._multiple) {
+ this.selectAll(false);
+ }
+
+ // toggles the selection field
+ row[this._rowSelectionField] = checked;
+ }
+
+ toggleSearch(): void {
+ this._searchVisible = !this._searchVisible;
+
+ if (this._searchVisible) {
+ setTimeout(this.focusOnSearch.bind(this));
+ } else {
+ setTimeout(this.clearSearch.bind(this));
+ }
+ }
+
+ focusOnSearch(): void {
+ this._searchTermInput.first.focus();
+ }
+
+ clearSearch(): void {
+ this._searchTermControl.setValue('');
+ }
+
+ setSorting(column: ITdDataTableColumn): void {
+ if (this._sortBy === column) {
+ this._sortOrder = this._sortOrder === TdDataTableSortingOrder.Ascending ?
+ TdDataTableSortingOrder.Descending : TdDataTableSortingOrder.Ascending;
+ } else {
+ this._sortBy = column;
+ this._sortOrder = TdDataTableSortingOrder.Ascending;
+ }
+ this.notifySortChanged();
+ this.filterData();
+ }
+
+ notifySortChanged(): void {
+ if (!this._initialized) {
+ return;
+ }
+ this.sortChanged.next({ column: this._sortBy, order: this._sortOrder });
+ }
+
+ nextPage(): void {
+ if (this._currentPage < this._totalPages) {
+ this._currentPage = this._currentPage + 1;
+ this.filterData();
+ }
+ }
+
+ prevPage(): void {
+ this._currentPage = Math.max(this._currentPage - 1, 1);
+ this.filterData();
+ }
+
+ isAscending(): boolean {
+ return this._sortOrder === TdDataTableSortingOrder.Ascending;
+ }
+
+ isDescending(): boolean {
+ return this._sortOrder === TdDataTableSortingOrder.Descending;
+ }
+
+ private preprocessData(): void {
+ this._data = _.cloneDeep(this._data);
+ this._data = this._data.map((row: any) => {
+ this.columns.filter((c: any) => c.format).forEach((c: any) => {
+ row[c.name] = c.format(row[c.name]);
+ });
+ return row;
+ });
+ }
+
+ private searchTermChanged(value: string): void {
+ this._searchTerm = value;
+ this.resetPagination();
+ }
+
+ private resetPagination(): void {
+ this._currentPage = 1;
+ this._totalPages = 0;
+
+ this.filterData();
+ }
+
+ private filterData(): void {
+ this._visibleData = this._data;
+
+ if (!this._initialized) {
+ return;
+ }
+
+ let filter: string = this._searchTerm.toLowerCase();
+ if (filter) {
+ this._visibleData = this._visibleData.filter((item: any) => {
+ const res: any = Object.keys(item).find((key: string) => {
+ const itemValue: string = ('' + item[key]).toLowerCase();
+ return itemValue.indexOf(filter) > -1;
+ });
+ return !(typeof res === 'undefined');
+ });
+ }
+
+ if (this._sorting && this._sortBy) {
+ this._visibleData = _.sortBy(this._visibleData, this._sortBy.name);
+ if (this._sortOrder === TdDataTableSortingOrder.Descending) {
+ this._visibleData = _.reverse(this._visibleData);
+ }
+ }
+
+ if (this._pagination) {
+ const pageStart: number = (this._currentPage - 1) * this._pageSize;
+ const pageEnd: number = Math.min(pageStart + this._pageSize, this._visibleData.length);
+
+ this._totalPages = Math.ceil(this._visibleData.length / this._pageSize);
+ this._visibleData = this._visibleData.slice(pageStart, pageEnd);
+ }
+
+ this._hasData = this._visibleData.length > 0;
+ }
+
+}
diff --git a/src/platform/data-table/data-table.module.ts b/src/platform/data-table/data-table.module.ts
new file mode 100644
index 0000000000..6676426bcc
--- /dev/null
+++ b/src/platform/data-table/data-table.module.ts
@@ -0,0 +1,48 @@
+import { NgModule, ModuleWithProviders, Type } from '@angular/core';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { CommonModule } from '@angular/common';
+
+import { MaterialModule } from '@angular/material';
+
+import { TdDataTableComponent } from './data-table.component';
+
+import { TdDataTableTableDirective } from './directives/table.directive';
+import { TdDataTableHeadDirective } from './directives/head.directive';
+import { TdDataTableBodyDirective } from './directives/body.directive';
+import { TdDataTableRowDirective } from './directives/row.directive';
+import { TdDataTableColumnDirective } from './directives/column.directive';
+import { TdDataTableCellDirective } from './directives/cell.directive';
+
+export const TD_DATA_TABLE_DIRECTIVES: Type[] = [
+ TdDataTableComponent,
+
+ TdDataTableTableDirective,
+ TdDataTableHeadDirective,
+ TdDataTableBodyDirective,
+ TdDataTableRowDirective,
+ TdDataTableColumnDirective,
+ TdDataTableCellDirective,
+];
+
+@NgModule({
+ imports: [
+ MaterialModule.forRoot(),
+ CommonModule,
+ FormsModule,
+ ReactiveFormsModule,
+ ],
+ declarations: [
+ TD_DATA_TABLE_DIRECTIVES,
+ ],
+ exports: [
+ TD_DATA_TABLE_DIRECTIVES,
+ ],
+})
+export class CovalentDataTableModule {
+ static forRoot(): ModuleWithProviders {
+ return {
+ ngModule: CovalentDataTableModule,
+ providers: [],
+ };
+ }
+}
diff --git a/src/platform/data-table/directives/body.directive.ts b/src/platform/data-table/directives/body.directive.ts
new file mode 100644
index 0000000000..4d586b97ca
--- /dev/null
+++ b/src/platform/data-table/directives/body.directive.ts
@@ -0,0 +1,13 @@
+import { Directive, Renderer, ElementRef } from '@angular/core';
+
+@Directive({
+ selector: '[td-body]',
+})
+export class TdDataTableBodyDirective {
+
+ private _class: string = 'md-body';
+
+ constructor(private _elementRef: ElementRef, private _renderer: Renderer) {
+ this._renderer.setElementClass(this._elementRef.nativeElement, this._class, true);
+ }
+}
diff --git a/src/platform/data-table/directives/cell.directive.ts b/src/platform/data-table/directives/cell.directive.ts
new file mode 100644
index 0000000000..8df8a7ecc0
--- /dev/null
+++ b/src/platform/data-table/directives/cell.directive.ts
@@ -0,0 +1,13 @@
+import { Directive, Renderer, ElementRef } from '@angular/core';
+
+@Directive({
+ selector: '[td-cell]',
+})
+export class TdDataTableCellDirective {
+
+ private _class: string = 'md-cell';
+
+ constructor(private _elementRef: ElementRef, private _renderer: Renderer) {
+ this._renderer.setElementClass(this._elementRef.nativeElement, this._class, true);
+ }
+}
diff --git a/src/platform/data-table/directives/column.directive.ts b/src/platform/data-table/directives/column.directive.ts
new file mode 100644
index 0000000000..31e2672fa3
--- /dev/null
+++ b/src/platform/data-table/directives/column.directive.ts
@@ -0,0 +1,13 @@
+import { Directive, Renderer, ElementRef } from '@angular/core';
+
+@Directive({
+ selector: '[td-column]',
+})
+export class TdDataTableColumnDirective {
+
+ private _class: string = 'md-column';
+
+ constructor(private _elementRef: ElementRef, private _renderer: Renderer) {
+ this._renderer.setElementClass(this._elementRef.nativeElement, this._class, true);
+ }
+}
diff --git a/src/platform/data-table/directives/head.directive.ts b/src/platform/data-table/directives/head.directive.ts
new file mode 100644
index 0000000000..b39343f878
--- /dev/null
+++ b/src/platform/data-table/directives/head.directive.ts
@@ -0,0 +1,13 @@
+import { Directive, Renderer, ElementRef } from '@angular/core';
+
+@Directive({
+ selector: '[td-head]',
+})
+export class TdDataTableHeadDirective {
+
+ private _class: string = 'md-head';
+
+ constructor(private _elementRef: ElementRef, private _renderer: Renderer) {
+ this._renderer.setElementClass(this._elementRef.nativeElement, this._class, true);
+ }
+}
diff --git a/src/platform/data-table/directives/row.directive.ts b/src/platform/data-table/directives/row.directive.ts
new file mode 100644
index 0000000000..df55c3e117
--- /dev/null
+++ b/src/platform/data-table/directives/row.directive.ts
@@ -0,0 +1,13 @@
+import { Directive, Renderer, ElementRef } from '@angular/core';
+
+@Directive({
+ selector: '[td-row]',
+})
+export class TdDataTableRowDirective {
+
+ private _class: string = 'md-row';
+
+ constructor(private _elementRef: ElementRef, private _renderer: Renderer) {
+ this._renderer.setElementClass(this._elementRef.nativeElement, this._class, true);
+ }
+}
diff --git a/src/platform/data-table/directives/table.directive.ts b/src/platform/data-table/directives/table.directive.ts
new file mode 100644
index 0000000000..9d572c3e81
--- /dev/null
+++ b/src/platform/data-table/directives/table.directive.ts
@@ -0,0 +1,13 @@
+import { Directive, Renderer, ElementRef } from '@angular/core';
+
+@Directive({
+ selector: '[td-table]',
+})
+export class TdDataTableTableDirective {
+
+ private _class: string = 'md-table';
+
+ constructor(private _elementRef: ElementRef, private _renderer: Renderer) {
+ this._renderer.setElementClass(this._elementRef.nativeElement, this._class, true);
+ }
+}
diff --git a/src/platform/data-table/index.ts b/src/platform/data-table/index.ts
new file mode 100644
index 0000000000..85ed36f4b9
--- /dev/null
+++ b/src/platform/data-table/index.ts
@@ -0,0 +1,2 @@
+export { TdDataTableComponent, TdDataTableSortingOrder } from './data-table.component';
+export { CovalentDataTableModule } from './data-table.module';
diff --git a/src/platform/data-table/package.json b/src/platform/data-table/package.json
new file mode 100644
index 0000000000..4cd52628b0
--- /dev/null
+++ b/src/platform/data-table/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "@covalent/data-table",
+ "version": "0.7.0",
+ "description": "Teradata UI Platform Data Table Module",
+ "keywords": [
+ "angular",
+ "components",
+ "reusable",
+ "data-table"
+ ],
+ "scripts": {
+ },
+ "engines": {
+ "node": ">4.4 < 5",
+ "npm": ">= 3"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/teradata/covalent.git"
+ },
+ "bugs": {
+ "url": "https://github.com/teradata/covalent/issues"
+ },
+ "license": "MIT",
+ "author": "Teradata UX",
+ "contributors": [
+ "Kyle Ledbetter ",
+ "Richa Vyas ",
+ "Ed Morales ",
+ "Jason Weaver ",
+ "Jeremy Wilken "
+ ],
+ "dependencies": {
+ "@covalent/core": "0.7.0",
+ "@types/lodash": "^4.14.36",
+ "lodash": "^4.16.2"
+ }
+}