From 14ed60e752120d4cd391edba1f9ad43725b0ede9 Mon Sep 17 00:00:00 2001 From: Torgeir Helgevold Date: Sun, 20 Mar 2016 12:08:22 -0400 Subject: [PATCH 1/2] docs:(created virtual grid cookbook) docs: update component name to have Component suffix + update display value for path in code samples data.json rc img new image core lint update syntax directive --- .../_examples/cb-virtual-grid/e2e-spec.ts | 49 +++++++ .../cb-virtual-grid/ts/app/.gitignore | 1 + .../cb-virtual-grid/ts/app/app.component.ts | 12 ++ .../cb-virtual-grid/ts/app/cell.component.ts | 39 ++++++ .../cb-virtual-grid/ts/app/column.ts | 12 ++ .../ts/app/hero-data.service.ts | 41 ++++++ .../ts/app/hero-grid-sorting.service.ts | 26 ++++ .../ts/app/hero-grid.component.ts | 64 +++++++++ .../ts/app/hero-grid.service.ts | 124 ++++++++++++++++++ .../ts/app/key-code.service.ts | 17 +++ .../_examples/cb-virtual-grid/ts/app/main.ts | 5 + .../_examples/cb-virtual-grid/ts/app/row.ts | 15 +++ .../_examples/cb-virtual-grid/ts/demo.css | 23 ++++ .../cb-virtual-grid/ts/example-config.json | 0 .../_examples/cb-virtual-grid/ts/index.html | 29 ++++ .../_examples/cb-virtual-grid/ts/plnkr.json | 8 ++ public/docs/ts/latest/cookbook/_data.json | 5 + .../docs/ts/latest/cookbook/virtual-grid.jade | 90 +++++++++++++ .../cookbooks/virtual-grid/virtual-grid.png | Bin 0 -> 30872 bytes 19 files changed, 560 insertions(+) create mode 100644 public/docs/_examples/cb-virtual-grid/e2e-spec.ts create mode 100644 public/docs/_examples/cb-virtual-grid/ts/app/.gitignore create mode 100644 public/docs/_examples/cb-virtual-grid/ts/app/app.component.ts create mode 100644 public/docs/_examples/cb-virtual-grid/ts/app/cell.component.ts create mode 100644 public/docs/_examples/cb-virtual-grid/ts/app/column.ts create mode 100644 public/docs/_examples/cb-virtual-grid/ts/app/hero-data.service.ts create mode 100644 public/docs/_examples/cb-virtual-grid/ts/app/hero-grid-sorting.service.ts create mode 100644 public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.component.ts create mode 100644 public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.service.ts create mode 100644 public/docs/_examples/cb-virtual-grid/ts/app/key-code.service.ts create mode 100644 public/docs/_examples/cb-virtual-grid/ts/app/main.ts create mode 100644 public/docs/_examples/cb-virtual-grid/ts/app/row.ts create mode 100644 public/docs/_examples/cb-virtual-grid/ts/demo.css create mode 100644 public/docs/_examples/cb-virtual-grid/ts/example-config.json create mode 100644 public/docs/_examples/cb-virtual-grid/ts/index.html create mode 100644 public/docs/_examples/cb-virtual-grid/ts/plnkr.json create mode 100644 public/docs/ts/latest/cookbook/virtual-grid.jade create mode 100644 public/resources/images/cookbooks/virtual-grid/virtual-grid.png diff --git a/public/docs/_examples/cb-virtual-grid/e2e-spec.ts b/public/docs/_examples/cb-virtual-grid/e2e-spec.ts new file mode 100644 index 0000000000..ff4787430f --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/e2e-spec.ts @@ -0,0 +1,49 @@ +/// +'use strict'; + +describe('Virtual Grid', function () { + + beforeEach(function () { + browser.get(''); + }); + + it('should sort the grid', function() { + ValidateRankingColumn(['1000', '999', '998', '997', '996', '995', '994', '993', '992', '991', '990']); + + // click Ranking column and make sure values are sorted + element(by.xpath('//tbody/tr[1]/td[3]')).click().then(function(){ + ValidateRankingColumn(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']); + }); + }); + + function ValidateRankingColumn(range: string[]) { + for (let i = 0; i < range.length; i++) { + let rankCell = element.all(by.xpath('//tbody/tr[' + (i + 2) + ']/td[3]//input')).get(0); + expect(rankCell.getAttribute('value')).toEqual(range[i]); + } + } + + it('should bind to new row but keep DOM constant', function () { + + let lastRowFirstTextCell = element(by.css('#cell10-0')); + + // check that the last row number in the grid is 10 + expect(element(by.xpath('//tbody/tr[12]/td[1][contains(text(),10)]')).isPresent()).toBe(true); + + let trs = element.all(by.xpath('//tbody/tr')); + expect(trs.count()).toBe(12); + + lastRowFirstTextCell.click().then(function(){ + lastRowFirstTextCell.clear(); + lastRowFirstTextCell.sendKeys('New Hero'); + expect(lastRowFirstTextCell.getAttribute('value')).toEqual('New Hero'); + + // page down one row + lastRowFirstTextCell.sendKeys(protractor.Key.ARROW_DOWN); + // navigating does not increase the number of rows + trs = element.all(by.xpath('//tbody/tr')); + expect(trs.count()).toBe(12); + }); + }); +}); + diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/.gitignore b/public/docs/_examples/cb-virtual-grid/ts/app/.gitignore new file mode 100644 index 0000000000..2cb7d2a2e9 --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/app/.gitignore @@ -0,0 +1 @@ +**/*.js diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/app.component.ts b/public/docs/_examples/cb-virtual-grid/ts/app/app.component.ts new file mode 100644 index 0000000000..b29f0ef054 --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/app/app.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +import { HeroGridComponent } from './hero-grid.component'; + +@Component({ + selector: 'my-app', + template: '', + directives: [HeroGridComponent] +}) + +export class AppComponent { +} diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/cell.component.ts b/public/docs/_examples/cb-virtual-grid/ts/app/cell.component.ts new file mode 100644 index 0000000000..4e276e17c3 --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/app/cell.component.ts @@ -0,0 +1,39 @@ +// #docregion +import { Component, Input, Output, EventEmitter, ElementRef, Renderer, ViewChild } from '@angular/core'; + +import { Column } from './column'; +import { HeroGridService } from './hero-grid.service'; +import { KeyCodeService } from './key-code.service'; + +@Component({ + selector: 'grid-cell', + template: `` +}) + +export class CellComponent { + @Input() col: Column; + @Input() id: string; + @Output() navigate = new EventEmitter(); + @ViewChild('input') input: ElementRef; + + constructor(public heroGridService: HeroGridService, private elementRef: ElementRef, + private renderer: Renderer, private keyCodeService: KeyCodeService) { + } + + select() { + this.renderer.invokeElementMethod(this.input.nativeElement, 'focus'); + } + + onKeyDown(e: any) { + let key = this.keyCodeService.getNavigationKey(e.keyCode); + if (key.isArrowKey) { + this.navigate.emit(e); + } + return !key.tab; + } +} + diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/column.ts b/public/docs/_examples/cb-virtual-grid/ts/app/column.ts new file mode 100644 index 0000000000..5f1c4d50a6 --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/app/column.ts @@ -0,0 +1,12 @@ +// #docregion +import { Row } from './row'; + +export class Column { + cellValue: string; + row: Row; + + constructor(public columnIndex: number, row: Row) { + this.cellValue = ''; + this.row = row; + } +} diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/hero-data.service.ts b/public/docs/_examples/cb-virtual-grid/ts/app/hero-data.service.ts new file mode 100644 index 0000000000..b3868b2f89 --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/app/hero-data.service.ts @@ -0,0 +1,41 @@ +// #docregion +import { Row } from './row'; + +export class HeroDataService { + + getApplicants(count: number): Array { + + let rows: Array = []; + let heroes: Array = ['Mr. Nice', + 'Narco', + 'Bombasto', + 'Celeritas', + 'Magneta', + 'RubberMan', + 'Dynama', + 'Dr IQ', + 'Magma', + 'Tornado']; + + for (let i = 0; i < count; i++) { + let heroIndex = this.generateRandomNumber(heroes.length - 1); + let heroData = [heroes[heroIndex], count - i, this.generateRandomNumber(70) + 30]; + rows.push(this.createCell(i, heroData)); + } + return rows; + } + + private generateRandomNumber(upperBound: number) { + return Math.floor(Math.random() * upperBound); + } + + private createCell(index: number, values: Array): Row { + let row = new Row(values.length); + + for (let i = 0; i < values.length; i++) { + row.columns[i].cellValue = values[i]; + } + return row; + } +} + diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid-sorting.service.ts b/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid-sorting.service.ts new file mode 100644 index 0000000000..b8a7e25876 --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid-sorting.service.ts @@ -0,0 +1,26 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { Row } from './row'; + +@Injectable() +export class HeroGridSortingService { + sortDirection: number = 1; + + sort(rows: Array, colIndex: number) { + this.sortDirection *= -1; + rows.sort((a, b) => { + if (a.columns[colIndex].cellValue === b.columns[colIndex].cellValue) { + return 0; + } + + if (a.columns[colIndex].cellValue > b.columns[colIndex].cellValue) { + return -1 * this.sortDirection; + } + + if (a.columns[colIndex].cellValue < b.columns[colIndex].cellValue) { + return 1 * this.sortDirection; + } + }); + } +} diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.component.ts b/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.component.ts new file mode 100644 index 0000000000..5de301ae60 --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.component.ts @@ -0,0 +1,64 @@ +// #docregion +import { Component, AfterViewChecked, ViewChildren, QueryList } from '@angular/core'; + +import { CellComponent } from './cell.component'; +import { HeroGridSortingService } from './hero-grid-sorting.service'; +import { HeroGridService } from './hero-grid.service'; +import { KeyCodeService } from './key-code.service'; +import { HeroDataService } from './hero-data.service'; +import { Row } from './row'; + +@Component({ + selector: 'hero-grid', + directives: [CellComponent], + providers: [HeroGridService, KeyCodeService, HeroDataService, HeroGridSortingService], + template: `

Hero Grid

+ + + + + + + + + +
+ {{columnHeader}} +
+ {{heroGridService.rows.indexOf(row)}} + + + +
` +}) + +export class HeroGridComponent implements AfterViewChecked { + + visibleRows: Array = []; + heroGridService: HeroGridService; + @ViewChildren(CellComponent) cells: QueryList; + + constructor(heroGridService: HeroGridService) { + this.heroGridService = heroGridService; + this.visibleRows = this.heroGridService.getVisibleRows(); + } + + navigate($event: any) { + this.heroGridService.navigate($event.keyCode); + this.visibleRows = this.heroGridService.getVisibleRows(); + } + + sort(columnIndex: number) { + this.heroGridService.sort(columnIndex); + this.visibleRows = this.heroGridService.getVisibleRows(); + } + + ngAfterViewChecked() { + let id = this.heroGridService.getCurrentCellSelector(); + let currentCell = this.cells.toArray().find(cell => cell.id === id); + currentCell.select(); + } +} diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.service.ts b/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.service.ts new file mode 100644 index 0000000000..12d1d9e369 --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.service.ts @@ -0,0 +1,124 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { Column } from './column'; +import { HeroDataService } from './hero-data.service'; +import { HeroGridSortingService } from './hero-grid-sorting.service'; +import { KeyCodeService } from './key-code.service'; +import { Row } from './row'; + +@Injectable() +export class HeroGridService { + static maxRows = 1000; + header: Array = ['Name', 'Ranking', 'Age']; + rows: Array; + currentColumn: Column; + currentRowIndex: number = 0; + + private gridWindow: any; + + keyCodeService: KeyCodeService; + sortingService: HeroGridSortingService; + + constructor(keyCodeService: KeyCodeService, heroDataService: HeroDataService, sortingService: HeroGridSortingService) { + this.keyCodeService = keyCodeService; + this.rows = heroDataService.getApplicants(HeroGridService.maxRows); + this.sortingService = sortingService; + + this.init(); + + let missingRows = this.gridWindow.pageSize - this.rows.length; + + for (let i = 0; i <= missingRows; i++) { + this.rows.push(new Row(this.header.length)); + } + } + + selectColumn(col: Column) { + this.currentColumn = col; + this.currentRowIndex = this.rows.indexOf(this.currentColumn.row); + } + + sort(colIndex: number) { + this.sortingService.sort(this.rows, colIndex); + this.init(); + } + + createCellSelector(row: Row, col: Column): string { + return 'cell' + this.rows.indexOf(row) + '-' + row.columns.indexOf(col); + } + + getCurrentCellSelector() { + let cellIndex = this.rows[this.currentRowIndex].columns.indexOf(this.currentColumn); + return 'cell' + this.currentRowIndex + '-' + cellIndex; + } + + getVisibleRows() { + let visible: Array = []; + for (let i = this.gridWindow.start; i <= this.gridWindow.end; i++) { + visible.push(this.rows[i]); + } + return visible; + } + + navigate(keyCode: number) { + let navDirection = this.keyCodeService.getNavigationKey(keyCode); + + if (navDirection.down) { + this.ensureRow(); + this.currentColumn = this.rows[this.currentRowIndex + 1].columns[this.currentColumn.columnIndex]; + this.adjustRowRangeDownward(); + } + + if (navDirection.up) { + if (this.currentRowIndex > 0) { + this.currentColumn = this.rows[this.currentRowIndex - 1].columns[this.currentColumn.columnIndex]; + this.adjustRowRangeUpward(); + } + } + + if (navDirection.left) { + if (this.currentColumn.columnIndex > 0) { + this.currentColumn = this.rows[this.currentRowIndex].columns[this.currentColumn.columnIndex - 1]; + } + } + + if (navDirection.right) { + if (this.currentColumn.columnIndex < this.header.length - 1) { + this.currentColumn = this.rows[this.currentRowIndex].columns[this.currentColumn.columnIndex + 1]; + } + } + + this.currentRowIndex = this.rows.indexOf(this.currentColumn.row); + } + + private adjustRowRangeUpward() { + if (this.currentRowIndex <= this.gridWindow.start) { + this.shiftRowsBy(-1); + } + } + + private adjustRowRangeDownward() { + if (this.currentRowIndex === this.gridWindow.end) { + this.shiftRowsBy(1); + } + } + + private shiftRowsBy(offset: number) { + this.gridWindow.start = this.gridWindow.start + offset; + this.gridWindow.end = this.gridWindow.end + offset; + } + + private ensureRow() { + if (this.currentRowIndex + 1 >= this.rows.length) { + this.rows.push(new Row(this.header.length)); + } + } + + private init() { + this.gridWindow = {pageSize: 10, start: 0, end: 10}; + this.currentColumn = this.rows[0].columns[0]; + this.currentRowIndex = 0; + } +} + diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/key-code.service.ts b/public/docs/_examples/cb-virtual-grid/ts/app/key-code.service.ts new file mode 100644 index 0000000000..f23f2921f5 --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/app/key-code.service.ts @@ -0,0 +1,17 @@ +// #docregion +export class KeyCodeService { + + getNavigationKey(keyCode: number): any { + let key: any = { + up: keyCode === 38, + down: keyCode === 40, + right: keyCode === 39, + left: keyCode === 37, + tab: keyCode === 9, + }; + key.isArrowKey = key.up || key.down || key.right || key.left; + + return key; + } +} + diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/main.ts b/public/docs/_examples/cb-virtual-grid/ts/app/main.ts new file mode 100644 index 0000000000..ad256f0823 --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/app/main.ts @@ -0,0 +1,5 @@ +import { bootstrap } from '@angular/platform-browser-dynamic'; + +import { AppComponent } from './app.component'; + +bootstrap(AppComponent); diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/row.ts b/public/docs/_examples/cb-virtual-grid/ts/app/row.ts new file mode 100644 index 0000000000..824036869f --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/app/row.ts @@ -0,0 +1,15 @@ +// #docregion +import { Column } from './column'; + +export class Row { + + columns: Array; + + constructor(public columnCount: number) { + this.columns = []; + + for (let j = 0; j < this.columnCount; j++) { + this.columns.push(new Column(j, this)); + } + } +} diff --git a/public/docs/_examples/cb-virtual-grid/ts/demo.css b/public/docs/_examples/cb-virtual-grid/ts/demo.css new file mode 100644 index 0000000000..cc1e641ea2 --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/demo.css @@ -0,0 +1,23 @@ +.row-number-column{ + width: 40px; + background-color: #eeeeee; + text-align: center; +} + +#hero-grid td{ + border: 1px solid gray; +} + +#hero-grid input{ + border:0; +} + +#hero-grid .columnHeader{ + background-color: #eeeeee; + text-transform: uppercase; + text-align: center; +} + +#hero-grid{ + border-collapse: collapse; +} diff --git a/public/docs/_examples/cb-virtual-grid/ts/example-config.json b/public/docs/_examples/cb-virtual-grid/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-virtual-grid/ts/index.html b/public/docs/_examples/cb-virtual-grid/ts/index.html new file mode 100644 index 0000000000..a13c5d0946 --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/index.html @@ -0,0 +1,29 @@ + + + + + Virtual Grid + + + + + + + + + + + + + + + + + loading... + + + diff --git a/public/docs/_examples/cb-virtual-grid/ts/plnkr.json b/public/docs/_examples/cb-virtual-grid/ts/plnkr.json new file mode 100644 index 0000000000..50b17a7a26 --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/plnkr.json @@ -0,0 +1,8 @@ +{ + "description": "Virtual Grid Cookbook samples", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ], + "tags":["cookbook", "component"] +} \ No newline at end of file diff --git a/public/docs/ts/latest/cookbook/_data.json b/public/docs/ts/latest/cookbook/_data.json index f82d816cef..ecd862e394 100644 --- a/public/docs/ts/latest/cookbook/_data.json +++ b/public/docs/ts/latest/cookbook/_data.json @@ -58,6 +58,11 @@ "intro": "Convert Angular 2 TypeScript examples into ES5 JavaScript" }, + "virtual-grid": { + "title": "Virtual Grid", + "description": "Scalable grid based on virtualization" + }, + "visual-studio-2015": { "title": "Visual Studio 2015 QuickStart", "intro": "Use Visual Studio 2015 with the QuickStart files" diff --git a/public/docs/ts/latest/cookbook/virtual-grid.jade b/public/docs/ts/latest/cookbook/virtual-grid.jade new file mode 100644 index 0000000000..8e34efcaf6 --- /dev/null +++ b/public/docs/ts/latest/cookbook/virtual-grid.jade @@ -0,0 +1,90 @@ +include ../_util-fns + + +:marked + Well, it's application season again, and as usual we have been inundated with applications from eager heroes. This year's surge in applications has forced us to improve the performance of the grid we use to display information about the heroes. + + In this cookbook we show how use virtualization to create a highly scalable grid, capable of handling thousands or records and still be responsive. Rendering elements to the DOM is slow, so the idea behind virtualization is to only render a fixed number of DOM elements based on a sliding window into the full dataset. Currently the grid supports basic features like sorting and key based navigation, but we might expand the feature set over time. + + +:marked + ## Table of contents + + [Grid Component](#grid-component) + + [Grid Service](#grid-service) + + [Grid Sorting](#grid-sorting) + + [Object Model](#object-model) + + [Key Codes](#key-codes) + + [Large Dataset](#large-dataset) + +:marked + **See the [live example](/resources/live-examples/cb-virtual-grid/ts/plnkr.html)**. + +.l-main-section + +:marked + ## Grid Component + + The first step is to create a simple `HeroGridComponent` with support for arrow key navigation and sorting. + + `HeroGrid` defines the necessary template and binding logic to render the set of visible rows. + ++makeExample('cb-virtual-grid/ts/app/hero-grid.component.ts',null,'app/hero-grid.component.ts')(format='.') + + +:marked + ## Grid Service + +:marked + Next we have defined `HeroGridService` to manage the entire set of rows and columns in the grid. `HeroGridService` controls the sliding window of currently visible rows. We will only render UI elements for visible rows. Based on user actions `HeroGridService` will recalculate the set of visible rows from the full dataset. ++makeExample('cb-virtual-grid/ts/app/hero-grid.service.ts',null,'app/hero-grid.service.ts')(format='.') + + +:marked + ## Grid Sorting + +:marked + We are doing the sorting in `HeroGridSortingService`. ++makeExample('cb-virtual-grid/ts/app/hero-grid-sorting.service.ts',null,'app/hero-grid-sorting.service.ts')(format='.') + + +:marked + ## Object Model +:marked + The grid is bound to an object model consisting of `Row` and `Column` objects. + ++makeExample('cb-virtual-grid/ts/app/row.ts',null,'app/row.ts')(format='.') + ++makeExample('cb-virtual-grid/ts/app/column.ts',null,'app/column.ts')(format='.') + + +:marked + ## Key Codes + +:marked + The grid supports arrow key based navigation, so to map supported keys we have created `KeyCodeService`. + ++makeExample('cb-virtual-grid/ts/app/key-code.service.ts',null,'app/key-code.service.ts')(format='.') + + +:marked + ## Large Dataset + +:marked + We are getting the list of applicants from `HeroDataService`. Typically this data would come from a database, but for demo purposes we are using `HeroDataService` to simulate a large datset. Despite the large dataset we can see that the grid performs really well when sorting and paging through the data. + + Paging through the data does not cause the grid to render more UI elements, so if we inspect the DOM we will see that the number of `tr` elements remains fixed. Instead we data-bind a sliding window of `Row` and `Column` objects to the same set of `tr` elements. Not increasing the number of rows to render is key to performance since DOM rendering is the slowest operation. ++makeExample('cb-virtual-grid/ts/app/hero-data.service.ts',null,'app/hero-data.service.ts')(format='.') + +:marked + The final grid looks like this: +figure.image-display + img(src="/resources/images/cookbooks/virtual-grid/virtual-grid.png" alt="Virtual-Grid") + +:marked + [Back to top](#top) \ No newline at end of file diff --git a/public/resources/images/cookbooks/virtual-grid/virtual-grid.png b/public/resources/images/cookbooks/virtual-grid/virtual-grid.png new file mode 100644 index 0000000000000000000000000000000000000000..ad8cc8ffba78e6b5559b56608013130f08b0845d GIT binary patch literal 30872 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 literal 0 HcmV?d00001 From b275cc086d06151dbcbc372f3dd423bbe44210f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20Rodri=CC=81guez?= Date: Sun, 28 Aug 2016 19:13:20 +0200 Subject: [PATCH 2/2] Jesus' edits --- .../cb-virtual-grid/ts/app/app.component.ts | 5 +-- .../cb-virtual-grid/ts/app/app.module.ts | 13 +++++++ .../cb-virtual-grid/ts/app/cell.component.ts | 6 ++-- .../ts/app/hero-data.service.ts | 33 +++++++++-------- .../ts/app/hero-grid-sorting.service.ts | 4 +-- .../ts/app/hero-grid.component.ts | 19 +++++----- .../ts/app/hero-grid.service.ts | 35 ++++++++----------- .../ts/app/key-code.service.ts | 3 ++ .../_examples/cb-virtual-grid/ts/app/main.ts | 6 ++-- 9 files changed, 66 insertions(+), 58 deletions(-) create mode 100644 public/docs/_examples/cb-virtual-grid/ts/app/app.module.ts diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/app.component.ts b/public/docs/_examples/cb-virtual-grid/ts/app/app.component.ts index b29f0ef054..676a44966d 100644 --- a/public/docs/_examples/cb-virtual-grid/ts/app/app.component.ts +++ b/public/docs/_examples/cb-virtual-grid/ts/app/app.component.ts @@ -1,11 +1,8 @@ import { Component } from '@angular/core'; -import { HeroGridComponent } from './hero-grid.component'; - @Component({ selector: 'my-app', - template: '', - directives: [HeroGridComponent] + template: '' }) export class AppComponent { diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/app.module.ts b/public/docs/_examples/cb-virtual-grid/ts/app/app.module.ts new file mode 100644 index 0000000000..dfe69ff33e --- /dev/null +++ b/public/docs/_examples/cb-virtual-grid/ts/app/app.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { CellComponent } from './cell.component'; +import { HeroGridComponent } from './hero-grid.component'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ AppComponent, CellComponent, HeroGridComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule {} diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/cell.component.ts b/public/docs/_examples/cb-virtual-grid/ts/app/cell.component.ts index 4e276e17c3..bf90c3ba72 100644 --- a/public/docs/_examples/cb-virtual-grid/ts/app/cell.component.ts +++ b/public/docs/_examples/cb-virtual-grid/ts/app/cell.component.ts @@ -7,7 +7,7 @@ import { KeyCodeService } from './key-code.service'; @Component({ selector: 'grid-cell', - template: ` { +@Injectable() +export class HeroDataService { - let rows: Array = []; - let heroes: Array = ['Mr. Nice', - 'Narco', - 'Bombasto', - 'Celeritas', - 'Magneta', - 'RubberMan', - 'Dynama', - 'Dr IQ', - 'Magma', - 'Tornado']; + getApplicants(count: number): Row[] { + let rows: Row[] = []; + let heroes: string[] = ['Mr. Nice', + 'Narco', + 'Bombasto', + 'Celeritas', + 'Magneta', + 'RubberMan', + 'Dynama', + 'Dr IQ', + 'Magma', + 'Tornado' + ]; for (let i = 0; i < count; i++) { let heroIndex = this.generateRandomNumber(heroes.length - 1); @@ -25,7 +28,7 @@ export class HeroDataService { return rows; } - private generateRandomNumber(upperBound: number) { + private generateRandomNumber(upperBound: number): number { return Math.floor(Math.random() * upperBound); } diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid-sorting.service.ts b/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid-sorting.service.ts index b8a7e25876..c93dcab0db 100644 --- a/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid-sorting.service.ts +++ b/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid-sorting.service.ts @@ -5,9 +5,9 @@ import { Row } from './row'; @Injectable() export class HeroGridSortingService { - sortDirection: number = 1; + sortDirection = 1; - sort(rows: Array, colIndex: number) { + sort(rows: Array, colIndex: number): void { this.sortDirection *= -1; rows.sort((a, b) => { if (a.columns[colIndex].cellValue === b.columns[colIndex].cellValue) { diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.component.ts b/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.component.ts index 5de301ae60..f74fd8b4d7 100644 --- a/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.component.ts +++ b/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.component.ts @@ -10,13 +10,12 @@ import { Row } from './row'; @Component({ selector: 'hero-grid', - directives: [CellComponent], providers: [HeroGridService, KeyCodeService, HeroDataService, HeroGridSortingService], template: `

Hero Grid

- @@ -26,10 +25,10 @@ import { Row } from './row'; {{heroGridService.rows.indexOf(row)}}
{{columnHeader}} - - +
` @@ -38,25 +37,23 @@ import { Row } from './row'; export class HeroGridComponent implements AfterViewChecked { visibleRows: Array = []; - heroGridService: HeroGridService; @ViewChildren(CellComponent) cells: QueryList; - constructor(heroGridService: HeroGridService) { - this.heroGridService = heroGridService; + constructor(public heroGridService: HeroGridService) { this.visibleRows = this.heroGridService.getVisibleRows(); } - navigate($event: any) { + navigate($event: any): void { this.heroGridService.navigate($event.keyCode); this.visibleRows = this.heroGridService.getVisibleRows(); } - sort(columnIndex: number) { + sort(columnIndex: number): void { this.heroGridService.sort(columnIndex); this.visibleRows = this.heroGridService.getVisibleRows(); } - ngAfterViewChecked() { + ngAfterViewChecked(): void { let id = this.heroGridService.getCurrentCellSelector(); let currentCell = this.cells.toArray().find(cell => cell.id === id); currentCell.select(); diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.service.ts b/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.service.ts index 12d1d9e369..84f54b460e 100644 --- a/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.service.ts +++ b/public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.service.ts @@ -10,20 +10,15 @@ import { Row } from './row'; @Injectable() export class HeroGridService { static maxRows = 1000; - header: Array = ['Name', 'Ranking', 'Age']; - rows: Array; + header: string[] = ['Name', 'Ranking', 'Age']; + rows: Row[]; currentColumn: Column; - currentRowIndex: number = 0; + currentRowIndex = 0; private gridWindow: any; - keyCodeService: KeyCodeService; - sortingService: HeroGridSortingService; - - constructor(keyCodeService: KeyCodeService, heroDataService: HeroDataService, sortingService: HeroGridSortingService) { - this.keyCodeService = keyCodeService; + constructor(private keyCodeService: KeyCodeService, private sortingService: HeroGridSortingService, heroDataService: HeroDataService) { this.rows = heroDataService.getApplicants(HeroGridService.maxRows); - this.sortingService = sortingService; this.init(); @@ -34,12 +29,12 @@ export class HeroGridService { } } - selectColumn(col: Column) { + selectColumn(col: Column): void { this.currentColumn = col; this.currentRowIndex = this.rows.indexOf(this.currentColumn.row); } - sort(colIndex: number) { + sort(colIndex: number): void { this.sortingService.sort(this.rows, colIndex); this.init(); } @@ -48,20 +43,20 @@ export class HeroGridService { return 'cell' + this.rows.indexOf(row) + '-' + row.columns.indexOf(col); } - getCurrentCellSelector() { + getCurrentCellSelector(): string { let cellIndex = this.rows[this.currentRowIndex].columns.indexOf(this.currentColumn); return 'cell' + this.currentRowIndex + '-' + cellIndex; } - getVisibleRows() { - let visible: Array = []; + getVisibleRows(): Row[] { + let visible: Row[] = []; for (let i = this.gridWindow.start; i <= this.gridWindow.end; i++) { visible.push(this.rows[i]); } return visible; } - navigate(keyCode: number) { + navigate(keyCode: number): void { let navDirection = this.keyCodeService.getNavigationKey(keyCode); if (navDirection.down) { @@ -92,30 +87,30 @@ export class HeroGridService { this.currentRowIndex = this.rows.indexOf(this.currentColumn.row); } - private adjustRowRangeUpward() { + private adjustRowRangeUpward(): void { if (this.currentRowIndex <= this.gridWindow.start) { this.shiftRowsBy(-1); } } - private adjustRowRangeDownward() { + private adjustRowRangeDownward(): void { if (this.currentRowIndex === this.gridWindow.end) { this.shiftRowsBy(1); } } - private shiftRowsBy(offset: number) { + private shiftRowsBy(offset: number): void { this.gridWindow.start = this.gridWindow.start + offset; this.gridWindow.end = this.gridWindow.end + offset; } - private ensureRow() { + private ensureRow(): void { if (this.currentRowIndex + 1 >= this.rows.length) { this.rows.push(new Row(this.header.length)); } } - private init() { + private init(): void { this.gridWindow = {pageSize: 10, start: 0, end: 10}; this.currentColumn = this.rows[0].columns[0]; this.currentRowIndex = 0; diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/key-code.service.ts b/public/docs/_examples/cb-virtual-grid/ts/app/key-code.service.ts index f23f2921f5..c6a087a54d 100644 --- a/public/docs/_examples/cb-virtual-grid/ts/app/key-code.service.ts +++ b/public/docs/_examples/cb-virtual-grid/ts/app/key-code.service.ts @@ -1,4 +1,7 @@ // #docregion +import { Injectable } from '@angular/core'; + +@Injectable() export class KeyCodeService { getNavigationKey(keyCode: number): any { diff --git a/public/docs/_examples/cb-virtual-grid/ts/app/main.ts b/public/docs/_examples/cb-virtual-grid/ts/app/main.ts index ad256f0823..6af7a5b2ae 100644 --- a/public/docs/_examples/cb-virtual-grid/ts/app/main.ts +++ b/public/docs/_examples/cb-virtual-grid/ts/app/main.ts @@ -1,5 +1,5 @@ -import { bootstrap } from '@angular/platform-browser-dynamic'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppComponent } from './app.component'; +import { AppModule } from './app.module'; -bootstrap(AppComponent); +platformBrowserDynamic().bootstrapModule(AppModule);