diff --git a/src/app/components/components/data-table/data-table.component.html b/src/app/components/components/data-table/data-table.component.html
index e17ce8c8a8..35dc040949 100644
--- a/src/app/components/components/data-table/data-table.component.html
+++ b/src/app/components/components/data-table/data-table.component.html
@@ -13,25 +13,25 @@
with custom headings, columns, and inline editing
-
- comment
- Comments
- |
{{column.label}}
|
+
+ comment
+ Comments
+ |
-
-
- |
{{column.format ? column.format(row[column.name]) : row[column.name]}}
|
+
+
+ |
@@ -45,25 +45,25 @@ with custom headings, columns, and inline editing
-
- comment
- Comments
- |
{ {column.label}}
|
+
+ comment
+ Comments
+ |
-
-
- |
{ {column.format ? column.format(row[column.name]) : row[column.name]}}
|
+
+
+ |
@@ -75,69 +75,19 @@ with custom headings, columns, and inline editing
import { ITdDataTableColumn } from '@covalent/core';
import { TdDialogService } from '@covalent/core';
...
- const NUMBER_FORMAT: (v: any) => any = (v: number) => v;
const DECIMAL_FORMAT: (v: any) => any = (v: number) => v.toFixed(2);
...
})
export class Demo {
columns: ITdDataTableColumn[] = [
- { name: 'name', label: 'Dessert (100g serving)' },
- { name: 'type', label: 'Type', filter: true },
- { 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 },
+ { name: 'first_name', label: 'First Name' },
+ { name: 'last_name', label: 'Last Name' },
+ { name: 'gender', label: 'Gender' },
+ { name: 'email', label: 'Email' },
+ { name: 'balance', label: 'Balance', numeric: true, format: DECIMAL_FORMAT },
];
- data: any[] = {
- 'id': 1,
- 'name': 'Frozen yogurt',
- 'type': 'Ice cream',
- 'calories': 159.0,
- 'fat': 6.0,
- 'carbs': 24.0,
- 'protein': 4.0,
- 'sodium': 87.0,
- 'calcium': 14.0,
- 'iron': 1.0,
- 'comments': 'I love froyo!',
- }, {
- 'id': 2,
- 'name': 'Ice cream sandwich',
- 'type': 'Ice cream',
- 'calories': 237.0,
- 'fat': 9.0,
- 'carbs': 37.0,
- 'protein': 4.3,
- 'sodium': 129.0,
- 'calcium': 8.0,
- 'iron': 1.0,
- }, {
- 'id': 3,
- 'name': 'Eclair',
- 'type': 'Pastry',
- 'calories': 262.0,
- 'fat': 16.0,
- 'carbs': 24.0,
- 'protein': 6.0,
- 'sodium': 337.0,
- 'calcium': 6.0,
- 'iron': 7.0,
- }, {
- 'id': 4,
- 'name': 'Cupcake',
- 'type': 'Pastry',
- 'calories': 305.0,
- 'fat': 3.7,
- 'carbs': 67.0,
- 'protein': 4.3,
- 'sodium': 413.0,
- 'calcium': 3.0,
- 'iron': 8.0,
- }];
+ basicData: any[] = [...]; // see data json
constructor(private _dialogService: TdDialogService) {}
@@ -154,6 +104,9 @@ with custom headings, columns, and inline editing
}
]]>
+ Data:
+
+
@@ -161,7 +114,7 @@ with custom headings, columns, and inline editing
Basic Data Table
- with nested data, formatting, configurable width columns and templates
+ with formatting, configurable width columns and templates
@@ -169,12 +122,8 @@ with nested data, formatting, configurable width columns
-
-
- {{value}}
-
- star
-
+
+
@@ -186,12 +135,8 @@ with nested data, formatting, configurable width columns
-
-
- { {value}} // or { {row[column]}}
-
- star
-
+
+
]]>
@@ -201,56 +146,25 @@ with nested data, formatting, configurable width columns
any = (v: number) => v;
const DECIMAL_FORMAT: (v: any) => any = (v: number) => v.toFixed(2);
...
})
- export class Demo {
+ export class Demo implements OnInit {
configWidthColumns: ITdDataTableColumn[] = [
- { name: 'name', label: 'Dessert (100g serving)', width: 300 },
- { name: 'type', label: 'Type', width: { min: 150, max: 250 } },
- { name: 'calories', label: 'Calories', numeric: true, format: NUMBER_FORMAT},
- { name: 'fat', label: 'Fat (g)', numeric: true, format: DECIMAL_FORMAT},
+ { name: 'first_name', label: 'First name', width: 150 },
+ { name: 'last_name', label: 'Last name', width: { min: 150, max: 250 } },
+ { name: 'gender', label: 'Gender'},
+ { name: 'email', label: 'Email', width: 250},
+ { name: 'img', label: 'Avatar', width: 50},
];
- basicData: any[] = [
- {
- 'id': 1,
- 'food': {
- 'name': 'Frozen yogurt',
- 'type': 'Ice cream',
- },
- 'calories': 159.0,
- 'fat': 6.0,
- }, {
- 'id': 2,
- 'food': {
- 'name': 'Ice cream sandwich',
- 'type': 'Ice cream',
- },
- 'calories': 237.0,
- 'fat': 9.0,
- }, {
- 'id': 3,
- 'food': {
- 'name': 'Eclair',
- 'type': 'Pastry',
- },
- 'calories': 262.0,
- 'fat': 16.0,
- }, {
- 'id': 4,
- 'food': {
- 'name': 'Cupcake',
- 'type': 'Pastry',
- },
- 'calories': 305.0,
- 'fat': 3.7,
- }
- ];
+ basicData: any[] = [...]; // see data json
}
]]>
+
Data:
+
+
@@ -296,7 +210,7 @@ No results to display.
Rows per page:
-
+
{{size}}
@@ -340,7 +254,7 @@ No results to display.
Rows per page:
-
+
{ {size} }
@@ -354,148 +268,19 @@ No results to display.
import { TdDataTableService, TdDataTableSortingOrder, ITdDataTableSortChangeEvent, ITdDataTableColumn } from '@covalent/core';
import { IPageChangeEvent } from '@covalent/core';
...
- const NUMBER_FORMAT: (v: any) => any = (v: number) => v;
const DECIMAL_FORMAT: (v: any) => any = (v: number) => v.toFixed(2);
...
})
export class Demo {
columns: ITdDataTableColumn[] = [
- { name: 'name', label: 'Dessert (100g serving)', sortable: true, width: 200 },
- { name: 'type', label: 'Type', filter: true },
- { name: 'calories', label: 'Calories', numeric: true, format: NUMBER_FORMAT, sortable: true, hidden: false },
- { name: 'fat', label: 'Fat (g)', numeric: true, format: DECIMAL_FORMAT, sortable: true },
- { 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 },
+ { name: 'first_name', label: 'First Name', sortable: true, width: 150 },
+ { name: 'last_name', label: 'Last Name', filter: true },
+ { name: 'gender', label: 'Gender', hidden: false },
+ { name: 'email', label: 'Email', sortable: true, width: 250 },
+ { name: 'balance', label: 'Balance', numeric: true, format: DECIMAL_FORMAT },
];
- data: any[] = [
- {
- 'id': 1,
- 'name': 'Frozen yogurt',
- 'type': 'Ice cream',
- 'calories': 159.0,
- 'fat': 6.0,
- 'carbs': 24.0,
- 'protein': 4.0,
- 'sodium': 87.0,
- 'calcium': 14.0,
- 'iron': 1.0,
- 'comments': 'I love froyo!',
- }, {
- 'id': 2,
- 'name': 'Ice cream sandwich',
- 'type': 'Ice cream',
- 'calories': 237.0,
- 'fat': 9.0,
- 'carbs': 37.0,
- 'protein': 4.3,
- 'sodium': 129.0,
- 'calcium': 8.0,
- 'iron': 1.0,
- }, {
- 'id': 3,
- 'name': 'Eclair',
- 'type': 'Pastry',
- 'calories': 262.0,
- 'fat': 16.0,
- 'carbs': 24.0,
- 'protein': 6.0,
- 'sodium': 337.0,
- 'calcium': 6.0,
- 'iron': 7.0,
- }, {
- 'id': 4,
- 'name': 'Cupcake',
- 'type': 'Pastry',
- 'calories': 305.0,
- 'fat': 3.7,
- 'carbs': 67.0,
- 'protein': 4.3,
- 'sodium': 413.0,
- 'calcium': 3.0,
- 'iron': 8.0,
- }, {
- 'id': 5,
- 'name': 'Jelly bean',
- 'type': 'Candy',
- 'calories': 375.0,
- 'fat': 0.0,
- 'carbs': 94.0,
- 'protein': 0.0,
- 'sodium': 50.0,
- 'calcium': 0.0,
- 'iron': 0.0,
- }, {
- 'id': 6,
- 'name': 'Lollipop',
- 'type': 'Candy',
- 'calories': 392.0,
- 'fat': 0.2,
- 'carbs': 98.0,
- 'protein': 0.0,
- 'sodium': 38.0,
- 'calcium': 0.0,
- 'iron': 2.0,
- }, {
- 'id': 7,
- 'name': 'Honeycomb',
- 'type': 'Other',
- 'calories': 408.0,
- 'fat': 3.2,
- 'carbs': 87.0,
- 'protein': 6.5,
- 'sodium': 562.0,
- 'calcium': 0.0,
- 'iron': 45.0,
- }, {
- 'id': 8,
- 'name': 'Donut',
- 'type': 'Pastry',
- 'calories': 452.0,
- 'fat': 25.0,
- 'carbs': 51.0,
- 'protein': 4.9,
- 'sodium': 326.0,
- 'calcium': 2.0,
- 'iron': 22.0,
- }, {
- 'id': 9,
- 'name': 'KitKat',
- 'type': 'Candy',
- 'calories': 518.0,
- 'fat': 26.0,
- 'carbs': 65.0,
- 'protein': 7.0,
- 'sodium': 54.0,
- 'calcium': 12.0,
- 'iron': 6.0,
- }, {
- 'id': 10,
- 'name': 'Chocolate',
- 'type': 'Candy',
- 'calories': 518.0,
- 'fat': 26.0,
- 'carbs': 65.0,
- 'protein': 7.0,
- 'sodium': 54.0,
- 'calcium': 12.0,
- 'iron': 6.0,
- }, {
- 'id': 11,
- 'name': 'Chamoy',
- 'type': 'Candy',
- 'calories': 518.0,
- 'fat': 26.0,
- 'carbs': 65.0,
- 'protein': 7.0,
- 'sodium': 54.0,
- 'calcium': 12.0,
- 'iron': 6.0,
- },
- ];
+ data: any[] = [...]; // see json data
filteredData: any[] = this.data;
filteredTotal: number = this.data.length;
@@ -503,8 +288,8 @@ No results to display.
searchTerm: string = '';
fromRow: number = 1;
currentPage: number = 1;
- pageSize: number = 10;
- sortBy: string = 'name';
+ pageSize: number = 50;
+ sortBy: string = 'first_name';
selectedRows: any[] = [];
sortOrder: TdDataTableSortingOrder = TdDataTableSortingOrder.Descending;
@@ -550,6 +335,9 @@ No results to display.
}
]]>
+ Data:
+
+
@@ -575,10 +363,10 @@ No results to display.
- Hide calories
+ Hide gender
- Type column is searchable (search for candy
)
+ Type column is searchable (search for lifsey
)
diff --git a/src/app/components/components/data-table/data-table.component.ts b/src/app/components/components/data-table/data-table.component.ts
index 474c89d5bf..02575fdc22 100644
--- a/src/app/components/components/data-table/data-table.component.ts
+++ b/src/app/components/components/data-table/data-table.component.ts
@@ -7,6 +7,10 @@ import { TdDataTableSortingOrder, TdDataTableService, TdDataTableComponent,
import { IPageChangeEvent } from '../../../../platform/core';
import { TdDialogService } from '../../../../platform/core';
+import { InternalDocsService } from '../../../services';
+
+import { toPromise } from 'rxjs/operator/toPromise';
+
const NUMBER_FORMAT: (v: any) => any = (v: number) => v;
const DECIMAL_FORMAT: (v: any) => any = (v: number) => v.toFixed(2);
@@ -78,167 +82,41 @@ export class DataTableDemoComponent implements OnInit {
}];
configWidthColumns: ITdDataTableColumn[] = [
- { name: 'name', label: 'Dessert (100g serving)', width: 300 },
- { name: 'type', label: 'Type', width: { min: 150, max: 250 } },
- { name: 'calories', label: 'Calories', numeric: true, format: NUMBER_FORMAT},
- { name: 'fat', label: 'Fat (g)', numeric: true, format: DECIMAL_FORMAT},
+ { name: 'first_name', label: 'First name', width: 150 },
+ { name: 'last_name', label: 'Last name', width: { min: 150, max: 250 } },
+ { name: 'gender', label: 'Gender'},
+ { name: 'email', label: 'Email', width: 250},
+ { name: 'img', label: 'Avatar', width: 50},
];
columns: ITdDataTableColumn[] = [
- { name: 'name', label: 'Dessert (100g serving)', sortable: true, width: 200 },
- { name: 'type', label: 'Type', filter: true },
- { name: 'calories', label: 'Calories', numeric: true, format: NUMBER_FORMAT, sortable: true, hidden: false },
- { name: 'fat', label: 'Fat (g)', numeric: true, format: DECIMAL_FORMAT, sortable: true },
- { 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 },
+ { name: 'first_name', label: 'First Name', sortable: true, width: 150 },
+ { name: 'last_name', label: 'Last Name', filter: true },
+ { name: 'gender', label: 'Gender', hidden: false },
+ { name: 'email', label: 'Email', sortable: true, width: 250 },
+ { name: 'balance', label: 'Balance', numeric: true, format: DECIMAL_FORMAT },
];
- data: any[] = [
- {
- 'id': 1,
- 'name': 'Frozen yogurt',
- 'type': 'Ice cream',
- 'calories': 159.0,
- 'fat': 6.0,
- 'carbs': 24.0,
- 'protein': 4.0,
- 'sodium': 87.0,
- 'calcium': 14.0,
- 'iron': 1.0,
- 'comments': 'I love froyo!',
- }, {
- 'id': 2,
- 'name': 'Ice cream sandwich',
- 'type': 'Ice cream',
- 'calories': 237.0,
- 'fat': 9.0,
- 'carbs': 37.0,
- 'protein': 4.3,
- 'sodium': 129.0,
- 'calcium': 8.0,
- 'iron': 1.0,
- }, {
- 'id': 3,
- 'name': 'Eclair',
- 'type': 'Pastry',
- 'calories': 262.0,
- 'fat': 16.0,
- 'carbs': 24.0,
- 'protein': 6.0,
- 'sodium': 337.0,
- 'calcium': 6.0,
- 'iron': 7.0,
- }, {
- 'id': 4,
- 'name': 'Cupcake',
- 'type': 'Pastry',
- 'calories': 305.0,
- 'fat': 3.7,
- 'carbs': 67.0,
- 'protein': 4.3,
- 'sodium': 413.0,
- 'calcium': 3.0,
- 'iron': 8.0,
- }, {
- 'id': 5,
- 'name': 'Jelly bean',
- 'type': 'Candy',
- 'calories': 375.0,
- 'fat': 0.0,
- 'carbs': 94.0,
- 'protein': 0.0,
- 'sodium': 50.0,
- 'calcium': 0.0,
- 'iron': 0.0,
- }, {
- 'id': 6,
- 'name': 'Lollipop',
- 'type': 'Candy',
- 'calories': 392.0,
- 'fat': 0.2,
- 'carbs': 98.0,
- 'protein': 0.0,
- 'sodium': 38.0,
- 'calcium': 0.0,
- 'iron': 2.0,
- }, {
- 'id': 7,
- 'name': 'Honeycomb',
- 'type': 'Other',
- 'calories': 408.0,
- 'fat': 3.2,
- 'carbs': 87.0,
- 'protein': 6.5,
- 'sodium': 562.0,
- 'calcium': 0.0,
- 'iron': 45.0,
- }, {
- 'id': 8,
- 'name': 'Donut',
- 'type': 'Pastry',
- 'calories': 452.0,
- 'fat': 25.0,
- 'carbs': 51.0,
- 'protein': 4.9,
- 'sodium': 326.0,
- 'calcium': 2.0,
- 'iron': 22.0,
- }, {
- 'id': 9,
- 'name': 'KitKat',
- 'type': 'Candy',
- 'calories': 518.0,
- 'fat': 26.0,
- 'carbs': 65.0,
- 'protein': 7.0,
- 'sodium': 54.0,
- 'calcium': 12.0,
- 'iron': 6.0,
- }, {
- 'id': 10,
- 'name': 'Chocolate',
- 'type': 'Candy',
- 'calories': 518.0,
- 'fat': 26.0,
- 'carbs': 65.0,
- 'protein': 7.0,
- 'sodium': 54.0,
- 'calcium': 12.0,
- 'iron': 6.0,
- }, {
- 'id': 11,
- 'name': 'Chamoy',
- 'type': 'Candy',
- 'calories': 518.0,
- 'fat': 26.0,
- 'carbs': 65.0,
- 'protein': 7.0,
- 'sodium': 54.0,
- 'calcium': 12.0,
- 'iron': 6.0,
- },
- ];
- basicData: any[] = this.data.slice(0, 4);
+ data: any[];
+ basicData: any[];
selectable: boolean = true;
clickable: boolean = false;
multiple: boolean = true;
filterColumn: boolean = true;
- filteredData: any[] = this.data;
- filteredTotal: number = this.data.length;
+ filteredData: any[];
+ filteredTotal: number ;
selectedRows: any[] = [];
searchTerm: string = '';
fromRow: number = 1;
currentPage: number = 1;
- pageSize: number = 10;
- sortBy: string = 'name';
+ pageSize: number = 50;
+ sortBy: string = 'first_name';
sortOrder: TdDataTableSortingOrder = TdDataTableSortingOrder.Descending;
constructor(private _dataTableService: TdDataTableService,
+ private _internalDocsService: InternalDocsService,
private _dialogService: TdDialogService) {}
openPrompt(row: any, name: string): void {
@@ -252,7 +130,9 @@ export class DataTableDemoComponent implements OnInit {
});
}
- ngOnInit(): void {
+ async ngOnInit(): Promise {
+ this.data = await toPromise.call(this._internalDocsService.queryData());
+ this.basicData = this.data.slice(0, 10);
this.filter();
}
diff --git a/src/app/services/internal-docs.service.ts b/src/app/services/internal-docs.service.ts
index 3fe4c1c0a5..9443462348 100644
--- a/src/app/services/internal-docs.service.ts
+++ b/src/app/services/internal-docs.service.ts
@@ -41,4 +41,22 @@ export class InternalDocsService {
});
}
+ queryData(): Observable {
+ return new Observable((subscriber: Subscriber) => {
+ this._http.get(INTERNAL_DOCS_URL + '/data.json').subscribe((response: Response) => {
+ let data: ITemplate[];
+ try {
+ data = response.json();
+ } catch (e) {
+ data = [];
+ subscriber.error();
+ }
+ subscriber.next(data);
+ subscriber.complete();
+ }, (error: any) => {
+ subscriber.error();
+ });
+ });
+ }
+
}
diff --git a/src/platform/core/data-table/data-table-cell/data-table-cell.component.scss b/src/platform/core/data-table/data-table-cell/data-table-cell.component.scss
index f3f809197b..c283623b12 100644
--- a/src/platform/core/data-table/data-table-cell/data-table-cell.component.scss
+++ b/src/platform/core/data-table/data-table-cell/data-table-cell.component.scss
@@ -6,7 +6,6 @@
padding: 0;
> .td-data-table-cell-content-wrapper {
padding: 0 28px 0 28px;
- height: 48px;
}
&:first-child > .td-data-table-cell-content-wrapper {
diff --git a/src/platform/core/data-table/data-table-row/data-table-row.component.ts b/src/platform/core/data-table/data-table-row/data-table-row.component.ts
index 31f5efea99..6d3b65455f 100644
--- a/src/platform/core/data-table/data-table-row/data-table-row.component.ts
+++ b/src/platform/core/data-table/data-table-row/data-table-row.component.ts
@@ -40,6 +40,14 @@ export class TdDataTableRowComponent {
return this._selected;
}
+ get height(): number {
+ let height: number = 48;
+ if (this._elementRef.nativeElement) {
+ height = (this._elementRef.nativeElement).getBoundingClientRect().height;
+ }
+ return height;
+ }
+
constructor(private _elementRef: ElementRef, private _renderer: Renderer2) {
this._renderer.addClass(this._elementRef.nativeElement, 'td-data-table-row');
}
diff --git a/src/platform/core/data-table/data-table.component.html b/src/platform/core/data-table/data-table.component.html
index 6faef52a02..511ed8bac3 100644
--- a/src/platform/core/data-table/data-table.component.html
+++ b/src/platform/core/data-table/data-table.component.html
@@ -43,7 +43,7 @@
#dtRow
[tabIndex]="selectable ? 0 : -1"
[selected]="(clickable || selectable) && isRowSelected(row)"
- *ngFor="let row of data | slice:fromRow:toRow; let rowIndex = index"
+ *ngFor="let row of virtualData; let rowIndex = index"
(click)="handleRowClick(row, $event)"
(keyup)="selectable && _rowKeyup($event, row, rowIndex)"
(keydown.space)="blockEvent($event)"
diff --git a/src/platform/core/data-table/data-table.component.ts b/src/platform/core/data-table/data-table.component.ts
index eb77101ef4..f8b431f1f3 100644
--- a/src/platform/core/data-table/data-table.component.ts
+++ b/src/platform/core/data-table/data-table.component.ts
@@ -73,6 +73,16 @@ export interface IInternalColumnWidth {
max?: boolean;
}
+/**
+ * Constant to set the rows offset before and after the viewport
+ */
+const TD_VIRTUAL_OFFSET: number = 2;
+
+/**
+ * Constant to set default row height if none is provided
+ */
+const TD_VIRTUAL_DEFAULT_ROW_HEIGHT: number = 48;
+
@Component({
providers: [ TD_DATA_TABLE_CONTROL_VALUE_ACCESSOR ],
selector: 'td-data-table',
@@ -103,74 +113,52 @@ export class TdDataTableComponent implements ControlValueAccessor, OnInit, After
private _verticalScrollSubs: Subscription;
private _horizontalScrollSubs: Subscription;
private _scrollHorizontalOffset: number = 0;
- private _scrollVerticalOffset: number = 0;
- private _hostHeight: number = 0;
private _onHorizontalScroll: Subject = new Subject();
private _onVerticalScroll: Subject = new Subject();
- /**
- * Returns the height of the row
- * For now we assume thats 49px.
- */
- get rowHeight(): number {
- return 49;
- }
+ // Array of cached row heights to allow dynamic row heights
+ private _rowHeightCache: number[] = [];
+ // Total pseudo height of all the elements
+ private _totalHeight: number = 0;
+ // Total host height for the viewport
+ private _hostHeight: number = 0;
+ // Scrolled vertical pixels
+ private _scrollVerticalOffset: number = 0;
+ // Style to move the content a certain offset depending on scrolled offset
+ private _offsetTransform: SafeStyle;
- /**
- * Returns the number of rows that are rendered outside the viewport.
- */
- get offsetRows(): number {
- return 2;
- }
+ // Variables that set from and to which rows will be rendered
+ private _fromRow: number = 0;
+ private _toRow: number = 0;
/**
* Returns the offset style with a proper calculation on how much it should move
* over the y axis of the total height
*/
get offsetTransform(): SafeStyle {
- let offset: number = 0;
- if (this._scrollVerticalOffset > (this.offsetRows * this.rowHeight)) {
- offset = this.fromRow * this.rowHeight;
- }
- return this._domSanitizer.bypassSecurityTrustStyle('translateY(' + (offset - this.totalHeight) + 'px)');
+ return this._offsetTransform;
}
/**
* Returns the assumed total height of the rows
*/
get totalHeight(): number {
- if (this._data) {
- return this._data.length * this.rowHeight;
- }
- return 0;
+ return this._totalHeight;
}
/**
* Returns the initial row to render in the viewport
*/
get fromRow(): number {
- if (this._data) {
- // we calculate how many rows would have been scrolled minus an offset
- let fromRow: number = Math.floor((this._scrollVerticalOffset / this.rowHeight)) - this.offsetRows;
- return fromRow > 0 ? fromRow : 0;
- }
- return 0;
+ return this._fromRow;
}
/**
* Returns the last row to render in the viewport
*/
get toRow(): number {
- if (this._data) {
- // we calculate how many rows would fit in the viewport and add an offset
- let toRow: number = Math.floor((this._hostHeight / this.rowHeight)) + this.fromRow + (this.offsetRows * 2);
- if (toRow > this._data.length) {
- toRow = this._data.length;
- }
- return toRow;
- }
- return 0;
+ return this._toRow;
}
/**
@@ -182,6 +170,8 @@ export class TdDataTableComponent implements ControlValueAccessor, OnInit, After
/** internal attributes */
private _data: any[];
+ // data virtually iterated by component
+ private _virtualData: any[];
private _columns: ITdDataTableColumn[];
private _selectable: boolean = false;
private _clickable: boolean = false;
@@ -226,7 +216,7 @@ export class TdDataTableComponent implements ControlValueAccessor, OnInit, After
/**
* Returns true if all values are not deselected
- * and atleast one is.
+ * and at least one is.
*/
get indeterminate(): boolean {
return this._indeterminate;
@@ -251,6 +241,7 @@ export class TdDataTableComponent implements ControlValueAccessor, OnInit, After
@Input('data')
set data(data: any[]) {
this._data = data;
+ this._rowHeightCache = [];
Promise.resolve().then(() => {
this.refresh();
// scroll back to the top if the data has changed
@@ -261,6 +252,10 @@ export class TdDataTableComponent implements ControlValueAccessor, OnInit, After
return this._data;
}
+ get virtualData(): any[] {
+ return this._virtualData;
+ }
+
/**
* columns?: ITdDataTableColumn[]
* Sets additional column configuration. [ITdDataTableColumn.name] has to exist in [data] as key.
@@ -434,8 +429,14 @@ export class TdDataTableComponent implements ControlValueAccessor, OnInit, After
*/
ngOnInit(): void {
// initialize observable for resize calculations
- this._resizeSubs = debounceTime.call(this._onResize.asObservable(), 10).subscribe(() => {
+ this._resizeSubs = this._onResize.asObservable().subscribe(() => {
+ if (this._rows) {
+ this._rows.toArray().forEach((row: TdDataTableRowComponent, index: number) => {
+ this._rowHeightCache[this.fromRow + index] = row.height + 1;
+ });
+ }
this._calculateWidths();
+ this._calculateVirtualRows();
});
// initialize observable for scroll column header reposition
this._horizontalScrollSubs = this._onHorizontalScroll.asObservable()
@@ -447,6 +448,7 @@ export class TdDataTableComponent implements ControlValueAccessor, OnInit, After
this._verticalScrollSubs = this._onVerticalScroll.asObservable()
.subscribe((verticalScroll: number) => {
this._scrollVerticalOffset = verticalScroll;
+ this._calculateVirtualRows();
this._changeDetectorRef.markForCheck();
});
}
@@ -480,6 +482,7 @@ export class TdDataTableComponent implements ControlValueAccessor, OnInit, After
// if the height of the viewport has changed, then we mark for check
if (this._hostHeight !== newHostHeight) {
this._hostHeight = newHostHeight;
+ this._calculateVirtualRows();
this._changeDetectorRef.markForCheck();
}
}
@@ -493,6 +496,7 @@ export class TdDataTableComponent implements ControlValueAccessor, OnInit, After
this._rowsChangedSubs = debounceTime.call(this._rows.changes, 0).subscribe(() => {
this._onResize.next();
});
+ this._calculateVirtualRows();
}
/**
@@ -566,6 +570,7 @@ export class TdDataTableComponent implements ControlValueAccessor, OnInit, After
* Refreshes data table and rerenders [data] and [columns]
*/
refresh(): void {
+ this._calculateVirtualRows();
this._calculateWidths();
this._calculateCheckboxState();
this._changeDetectorRef.markForCheck();
@@ -745,17 +750,9 @@ export class TdDataTableComponent implements ControlValueAccessor, OnInit, After
case UP_ARROW:
/**
* if users presses the up arrow, we focus the prev row
- * unless its the first row, then we move to the last row
+ * unless its the first row
*/
- if (index === 0) {
- if (!event.shiftKey) {
- this._scrollableDiv.nativeElement.scrollTop = this.totalHeight;
- let subs: Subscription = this._rows.changes.subscribe(() => {
- subs.unsubscribe();
- this._rows.toArray()[this._rows.toArray().length - 1].focus();
- });
- }
- } else {
+ if (index > 0) {
this._rows.toArray()[index - 1].focus();
}
this.blockEvent(event);
@@ -766,17 +763,9 @@ export class TdDataTableComponent implements ControlValueAccessor, OnInit, After
case DOWN_ARROW:
/**
* if users presses the down arrow, we focus the next row
- * unless its the last row, then we move to the first row
+ * unless its the last row
*/
- if (index === (this._rows.toArray().length - 1)) {
- if (!event.shiftKey) {
- this._scrollableDiv.nativeElement.scrollTop = 0;
- let subs: Subscription = this._rows.changes.subscribe(() => {
- subs.unsubscribe();
- this._rows.toArray()[0].focus();
- });
- }
- } else {
+ if (index < (this._rows.toArray().length - 1)) {
this._rows.toArray()[index + 1].focus();
}
this.blockEvent(event);
@@ -978,4 +967,73 @@ export class TdDataTableComponent implements ControlValueAccessor, OnInit, After
let renderedColumns: ITdDataTableColumn[] = this.columns.filter((col: ITdDataTableColumn) => !col.hidden);
return Math.floor(this.hostWidth / renderedColumns.length);
}
+
+ /**
+ * Method to calculate the rows to be rendered in the viewport
+ */
+ private _calculateVirtualRows(): void {
+ let scrolledRows: number = 0;
+ if (this._data) {
+ this._totalHeight = 0;
+ let rowHeightSum: number = 0;
+ // loop through all rows to see if we have their height cached
+ // and sum them all to calculate the total height
+ this._data.forEach((d: any, index: number) => {
+ // iterate through all rows at first and assume all
+ // rows are the same height as the first one
+ if (!this._rowHeightCache[index]) {
+ this._rowHeightCache[index] = this._rowHeightCache[0] || TD_VIRTUAL_DEFAULT_ROW_HEIGHT;
+ }
+ rowHeightSum += this._rowHeightCache[index];
+ // check how many rows have been scrolled
+ if (this._scrollVerticalOffset - rowHeightSum > 0) {
+ scrolledRows++;
+ }
+ });
+ this._totalHeight = rowHeightSum;
+ // set the initial row to be rendered taking into account the row offset
+ let fromRow: number = scrolledRows - TD_VIRTUAL_OFFSET;
+ this._fromRow = fromRow > 0 ? fromRow : 0;
+
+ let hostHeight: number = this._hostHeight;
+ let index: number = 0;
+ // calculate how many rows can fit in the viewport
+ while (hostHeight > 0) {
+ hostHeight -= this._rowHeightCache[this.fromRow + index];
+ index++;
+ }
+ // set the last row to be rendered taking into account the row offset
+ let range: number = (index - 1) + (TD_VIRTUAL_OFFSET * 2);
+ let toRow: number = range + this.fromRow;
+ // if last row is greater than the total length, then we use the total length
+ if (isFinite(toRow) && toRow > this._data.length) {
+ toRow = this._data.length;
+ } else if (!isFinite(toRow)) {
+ toRow = TD_VIRTUAL_OFFSET;
+ }
+ this._toRow = toRow;
+ } else {
+ this._totalHeight = 0;
+ this._fromRow = 0;
+ this._toRow = 0;
+ }
+
+ let offset: number = 0;
+ // calculate the proper offset depending on how many rows have been scrolled
+ if (scrolledRows > TD_VIRTUAL_OFFSET) {
+ for (let index: number = 0; index < this.fromRow; index++) {
+ offset += this._rowHeightCache[index];
+ }
+ }
+
+ this._offsetTransform = this._domSanitizer.bypassSecurityTrustStyle('translateY(' + (offset - this.totalHeight) + 'px)');
+ if (this._data) {
+ this._virtualData = this.data.slice(this.fromRow, this.toRow);
+ }
+ // mark for check at the end of the queue so we are sure
+ // that the changes will be marked
+ Promise.resolve().then(() => {
+ this._changeDetectorRef.markForCheck();
+ });
+ }
}