Skip to content
This repository was archived by the owner on Dec 4, 2017. It is now read-only.

docs:(created virtual grid cookbook) #993

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions public/docs/_examples/cb-virtual-grid/e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/// <reference path='../_protractor/e2e.d.ts' />
'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);
});
});
});

1 change: 1 addition & 0 deletions public/docs/_examples/cb-virtual-grid/ts/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/*.js
9 changes: 9 additions & 0 deletions public/docs/_examples/cb-virtual-grid/ts/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Component } from '@angular/core';

@Component({
selector: 'my-app',
template: '<hero-grid></hero-grid>'
})

export class AppComponent {
}
13 changes: 13 additions & 0 deletions public/docs/_examples/cb-virtual-grid/ts/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -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 {}
39 changes: 39 additions & 0 deletions public/docs/_examples/cb-virtual-grid/ts/app/cell.component.ts
Original file line number Diff line number Diff line change
@@ -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: `<input #input
[value]="col.cellValue"
[id]="id"
(input)="col.cellValue = $event.target.value"
(click)="heroGridService.selectColumn(col)" (keydown)="onKeyDown($event)" />`
})

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(): void {
this.renderer.invokeElementMethod(this.input.nativeElement, 'focus');
}

onKeyDown(e: any): boolean {
let key = this.keyCodeService.getNavigationKey(e.keyCode);
if (key.isArrowKey) {
this.navigate.emit(e);
}
return !key.tab;
}
}

12 changes: 12 additions & 0 deletions public/docs/_examples/cb-virtual-grid/ts/app/column.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
44 changes: 44 additions & 0 deletions public/docs/_examples/cb-virtual-grid/ts/app/hero-data.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// #docregion
import { Injectable } from '@angular/core';

import { Row } from './row';

@Injectable()
export class HeroDataService {

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);
let heroData = [heroes[heroIndex], count - i, this.generateRandomNumber(70) + 30];
rows.push(this.createCell(i, heroData));
}
return rows;
}

private generateRandomNumber(upperBound: number): number {
return Math.floor(Math.random() * upperBound);
}

private createCell(index: number, values: Array<any>): Row {
let row = new Row(values.length);

for (let i = 0; i < values.length; i++) {
row.columns[i].cellValue = values[i];
}
return row;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// #docregion
import { Injectable } from '@angular/core';

import { Row } from './row';

@Injectable()
export class HeroGridSortingService {
sortDirection = 1;

sort(rows: Array<Row>, colIndex: number): void {
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;
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// #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',
providers: [HeroGridService, KeyCodeService, HeroDataService, HeroGridSortingService],
template: `<h1>Hero Grid</h1>
<table id="hero-grid">
<tr>
<td class="row-number-column"></td>
<td (click)="sort(colIndex)" class="columnHeader"
*ngFor="let columnHeader of heroGridService.header; let colIndex = index">
{{columnHeader}}
</td>
</tr>
<tr *ngFor="let row of visibleRows">
<td class="row-number-column">
{{heroGridService.rows.indexOf(row)}}
</td>
<td *ngFor="let col of row.columns">
<grid-cell [id]="heroGridService.createCellSelector(row, col)"
[col]="col"
(navigate)="navigate($event)">
</grid-cell>
</td>
</tr>
</table>`
})

export class HeroGridComponent implements AfterViewChecked {

visibleRows: Array<Row> = [];
@ViewChildren(CellComponent) cells: QueryList<CellComponent>;

constructor(public heroGridService: HeroGridService) {
this.visibleRows = this.heroGridService.getVisibleRows();
}

navigate($event: any): void {
this.heroGridService.navigate($event.keyCode);
this.visibleRows = this.heroGridService.getVisibleRows();
}

sort(columnIndex: number): void {
this.heroGridService.sort(columnIndex);
this.visibleRows = this.heroGridService.getVisibleRows();
}

ngAfterViewChecked(): void {
let id = this.heroGridService.getCurrentCellSelector();
let currentCell = this.cells.toArray().find(cell => cell.id === id);
currentCell.select();
}
}
119 changes: 119 additions & 0 deletions public/docs/_examples/cb-virtual-grid/ts/app/hero-grid.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// #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: string[] = ['Name', 'Ranking', 'Age'];
rows: Row[];
currentColumn: Column;
currentRowIndex = 0;

private gridWindow: any;

constructor(private keyCodeService: KeyCodeService, private sortingService: HeroGridSortingService, heroDataService: HeroDataService) {
this.rows = heroDataService.getApplicants(HeroGridService.maxRows);

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): void {
this.currentColumn = col;
this.currentRowIndex = this.rows.indexOf(this.currentColumn.row);
}

sort(colIndex: number): void {
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(): string {
let cellIndex = this.rows[this.currentRowIndex].columns.indexOf(this.currentColumn);
return 'cell' + this.currentRowIndex + '-' + cellIndex;
}

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): void {
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(): void {
if (this.currentRowIndex <= this.gridWindow.start) {
this.shiftRowsBy(-1);
}
}

private adjustRowRangeDownward(): void {
if (this.currentRowIndex === this.gridWindow.end) {
this.shiftRowsBy(1);
}
}

private shiftRowsBy(offset: number): void {
this.gridWindow.start = this.gridWindow.start + offset;
this.gridWindow.end = this.gridWindow.end + offset;
}

private ensureRow(): void {
if (this.currentRowIndex + 1 >= this.rows.length) {
this.rows.push(new Row(this.header.length));
}
}

private init(): void {
this.gridWindow = {pageSize: 10, start: 0, end: 10};
this.currentColumn = this.rows[0].columns[0];
this.currentRowIndex = 0;
}
}

Loading