├── src ├── assets │ └── .gitkeep ├── app │ ├── app.component.css │ ├── components │ │ ├── home │ │ │ ├── home.component.css │ │ │ ├── home.component.html │ │ │ └── home.component.ts │ │ ├── docs-viewer │ │ │ ├── docs-viewer.component.html │ │ │ └── docs-viewer.component.ts │ │ ├── code-viewer │ │ │ ├── code-viewer.component.css │ │ │ ├── code-viewer.component.html │ │ │ └── code-viewer.component.ts │ │ └── datatable │ │ │ ├── demo1 │ │ │ ├── data-table-demo1.css │ │ │ ├── data-table-demo1.ts │ │ │ ├── data-table-demo1.html │ │ │ └── data-table-demo1-data.ts │ │ │ ├── demo4 │ │ │ ├── data-table-demo4.css │ │ │ ├── data-table-resources-custom.ts │ │ │ ├── data-table-demo4.ts │ │ │ ├── data-table-demo4.html │ │ │ └── data-table-demo4-data.ts │ │ │ ├── demo3 │ │ │ ├── data-table-demo3.css │ │ │ ├── data-table-demo3.ts │ │ │ ├── data-table-demo3.html │ │ │ └── data-table-demo3-data.ts │ │ │ ├── demo5 │ │ │ ├── data-table-demo5.css │ │ │ ├── data-table-demo5.ts │ │ │ └── data-table-demo5.html │ │ │ ├── demo2 │ │ │ ├── data-table-demo2-data.ts │ │ │ ├── data-table-demo2.ts │ │ │ └── data-table-demo2.html │ │ │ ├── datatable.module.ts │ │ │ └── datatable-routing.module.ts │ ├── app-routing.module.ts │ ├── app.module.ts │ ├── app.component.ts │ └── app.component.html ├── favicon.ico ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── typings.d.ts ├── styles.css ├── main.ts ├── tsconfig.spec.json ├── tsconfig.packages.json ├── tsconfig.app.json ├── test.ts ├── index.html └── polyfills.ts ├── tslint.json ├── libs └── datatable │ ├── src │ ├── types │ │ ├── data-table-params.type.ts │ │ ├── row-callback.type.ts │ │ ├── cell-callback.type.ts │ │ ├── data-table-translations.type.ts │ │ └── default-translations.type.ts │ ├── utils │ │ ├── min.ts │ │ ├── px.ts │ │ ├── px.spec.ts │ │ ├── min.spec.ts │ │ ├── drag.ts │ │ ├── hide.ts │ │ └── hide.spec.ts │ ├── components │ │ ├── pagination │ │ │ ├── pagination.component.css │ │ │ ├── pagination.component.ts │ │ │ └── pagination.component.html │ │ ├── row │ │ │ ├── row.component.css │ │ │ ├── row.component.ts │ │ │ └── row.component.html │ │ ├── header │ │ │ ├── header.component.css │ │ │ ├── header.component.ts │ │ │ └── header.component.html │ │ └── table │ │ │ ├── table.component.css │ │ │ ├── table.component.html │ │ │ └── table.component.ts │ ├── public_api.ts │ ├── tools │ │ └── data-table-resource.ts │ ├── directives │ │ └── column │ │ │ └── column.directive.ts │ └── datatable.module.ts │ ├── package.json │ └── README.md ├── .editorconfig ├── .npmignore ├── .gitignore ├── tsconfig.json ├── spec.bundle.js ├── karma.conf.js ├── LICENSE ├── package.json ├── .angular-cli.json └── README.md /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/home/home.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint-angular" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/app/components/docs-viewer/docs-viewer.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/components/home/home.component.html: -------------------------------------------------------------------------------- 1 |

Discover the components from above list.

2 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brunano21/angular-4-data-table/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/app/components/code-viewer/code-viewer.component.css: -------------------------------------------------------------------------------- 1 | .nav-tabs { 2 | margin-bottom: 1rem; 3 | } 4 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | html { 3 | font-size: 12px; 4 | } 5 | 6 | body { 7 | padding: 2rem; 8 | } 9 | -------------------------------------------------------------------------------- /libs/datatable/src/types/data-table-params.type.ts: -------------------------------------------------------------------------------- 1 | export interface DataTableParams { 2 | [index: string]: any; 3 | 4 | offset?: number; 5 | limit?: number; 6 | sortBy?: string; 7 | sortAsc?: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /libs/datatable/src/types/row-callback.type.ts: -------------------------------------------------------------------------------- 1 | import { DataTableRowComponent } from '../components/row/row.component'; 2 | 3 | export type RowCallback = (item: any, row: DataTableRowComponent, index: number) => string; 4 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo1/data-table-demo1.css: -------------------------------------------------------------------------------- 1 | 2 | :host /deep/ .data-table-row { 3 | cursor: pointer; 4 | -webkit-user-select: none; 5 | -moz-user-select: none; 6 | -ms-user-select: none; 7 | user-select: none; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo4/data-table-demo4.css: -------------------------------------------------------------------------------- 1 | 2 | :host /deep/ .data-table-row { 3 | cursor: pointer; 4 | -webkit-user-select: none; 5 | -moz-user-select: none; 6 | -ms-user-select: none; 7 | user-select: none; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo3/data-table-demo3.css: -------------------------------------------------------------------------------- 1 | 2 | :host /deep/ .index-column, 3 | :host /deep/ .index-column-header { 4 | text-align: right; 5 | } 6 | 7 | :host /deep/ .data-table .data-table-row.selected { 8 | background-color: #E4EDF9; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo5/data-table-demo5.css: -------------------------------------------------------------------------------- 1 | 2 | :host /deep/ .index-column, 3 | :host /deep/ .index-column-header { 4 | text-align: right; 5 | } 6 | 7 | :host /deep/ .data-table .data-table-row.selected { 8 | background-color: #E4EDF9; 9 | } 10 | -------------------------------------------------------------------------------- /libs/datatable/src/utils/min.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | 4 | @Pipe({ 5 | name: 'min' 6 | }) 7 | export class MinPipe implements PipeTransform { 8 | transform(value: number[], args: string[]): any { 9 | return Math.min.apply(null, value); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /libs/datatable/src/types/cell-callback.type.ts: -------------------------------------------------------------------------------- 1 | import {DataTableRowComponent} from '../components/row/row.component'; 2 | import {DataTableColumnDirective} from '../directives/column/column.directive'; 3 | 4 | export type CellCallback = (item: any, row: DataTableRowComponent, column: DataTableColumnDirective, index: number) => string; 5 | -------------------------------------------------------------------------------- /src/app/components/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.css'] 7 | }) 8 | export class HomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /libs/datatable/src/components/pagination/pagination.component.css: -------------------------------------------------------------------------------- 1 | .pagination-controllers select { 2 | min-width: 5rem; 3 | /*padding: 1px 0px 0px 5px;*/ 4 | text-align: right; 5 | } 6 | 7 | .pagination-pages > * { 8 | margin: 0 0.15rem; 9 | } 10 | 11 | .pagination-limit { 12 | margin-right: 1.5rem; 13 | } 14 | 15 | .pagination-box button { 16 | outline: none !important; 17 | } 18 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # IDEs and editors 5 | /.idea 6 | 7 | # misc 8 | /.sass-cache 9 | /connect.lock 10 | /coverage/* 11 | /libpeerconnection.log 12 | npm-debug.log 13 | testem.log 14 | yarn.lock 15 | yarn-error.log 16 | .npmignore 17 | 18 | #TS files 19 | **/*.ts 20 | *.ts 21 | !**/*.d.ts 22 | !*.d.ts 23 | tsconfig.json 24 | tsconfig-aot.json 25 | 26 | #System Files 27 | .DS_Store -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "", 6 | "module": "commonjs", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ], 11 | "paths": { 12 | "angular5-data-table": [ "../dist/angular5-data-table" ] 13 | } 14 | }, 15 | "files": [ 16 | "test.ts" 17 | ], 18 | "include": [ 19 | "../libs/**/*.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Dependency directories 12 | node_modules 13 | documentation 14 | coverage 15 | 16 | # Optional npm cache directory 17 | .npm 18 | 19 | # IDEs and editors 20 | /.idea 21 | .project 22 | .classpath 23 | *.launch 24 | .settings/ 25 | 26 | #System Files 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # Generated files 31 | /dist 32 | *.js 33 | *.map 34 | *.tgz 35 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo2/data-table-demo2-data.ts: -------------------------------------------------------------------------------- 1 | export const Cars = [ 2 | { year: 1997, maker: 'Ford', model: 'E350', desc: 'ac, abs, moon', price: 3000.00 }, 3 | { year: 1999, maker: 'Chevy', model: 'Venture "Extended Edition"', price: 4900.00 }, 4 | { year: 1999, maker: 'Checy', model: 'Venture "Extended Edition, Very Large"', price: 5000.00 }, 5 | { year: 1996, maker: 'Jeep', model: 'Grand Cherokee', desc: 'air, moon roof, loaded', price: 4799.00 } 6 | ]; 7 | -------------------------------------------------------------------------------- /libs/datatable/src/utils/px.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'px' 5 | }) 6 | export class PixelConverter implements PipeTransform { 7 | transform(value: string | number, args: string[]): any { 8 | if (value === undefined) { 9 | return; 10 | } 11 | if (typeof value === 'string') { 12 | return value; 13 | } 14 | if (typeof value === 'number') { 15 | return value + 'px'; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/tsconfig.packages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "angularCompilerOptions": { 4 | "paths": { 5 | "angular5-data-table": [ "../dist/angular5-data-table" ] 6 | } 7 | }, 8 | "compilerOptions": { 9 | "outDir": "../out-tsc/app", 10 | "module": "es2015", 11 | "baseUrl": "", 12 | "types": [], 13 | "paths": { 14 | "angular5-data-table": [ "../dist/angular5-data-table" ] 15 | } 16 | }, 17 | "exclude": [ 18 | "test.ts", 19 | "**/*.spec.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "angularCompilerOptions": { 4 | "paths": { 5 | "angular5-data-table": [ "../libs/datatable/src/public_api.ts" ] 6 | } 7 | }, 8 | "compilerOptions": { 9 | "outDir": "../out-tsc/app", 10 | "baseUrl": "./", 11 | "module": "es2015", 12 | "types": [], 13 | "paths": { 14 | "angular5-data-table": [ "../libs/datatable/src/public_api.ts" ] 15 | } 16 | }, 17 | "exclude": [ 18 | "test.ts", 19 | "**/*.spec.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/app/components/docs-viewer/docs-viewer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'docs-viewer', 6 | templateUrl: './docs-viewer.component.html' 7 | }) 8 | export class DocsViewerComponent implements OnInit { 9 | 10 | path: string; 11 | 12 | constructor(private route: ActivatedRoute) {} 13 | 14 | ngOnInit() { 15 | this.route.params.subscribe( params => { 16 | this.path = `/assets/${params['component']}/README.md`; 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo4/data-table-resources-custom.ts: -------------------------------------------------------------------------------- 1 | import { DataTableResource, DataTableParams } from 'angular5-data-table'; 2 | 3 | export class DataTableResourceCustom extends DataTableResource { 4 | 5 | constructor(items: T[]) { 6 | super(items); 7 | } 8 | 9 | query(params: DataTableParams, filter?: (item: T, index: number, items: T[]) => boolean): Promise { 10 | const items = super.query(params, filter); 11 | return new Promise((resolve) => { 12 | setTimeout(() => resolve(items), Math.floor(Math.random() * 5000)); 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "dom" 18 | ], 19 | "paths": { 20 | "angular5-data-table": ["dist/angular5-data-table"] 21 | } 22 | }, 23 | "exclude": [ 24 | ".ng_build" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /libs/datatable/src/components/row/row.component.css: -------------------------------------------------------------------------------- 1 | .select-column { 2 | text-align: center; 3 | } 4 | 5 | .row-expand-button { 6 | -webkit-box-sizing: content-box; 7 | -moz-box-sizing: content-box; 8 | box-sizing: content-box; 9 | background: none; 10 | border: 0; 11 | color: inherit; 12 | cursor: pointer; 13 | font: inherit; 14 | line-height: normal; 15 | overflow: visible; 16 | padding: 0.15rem 0.75rem; 17 | -webkit-appearance: button; 18 | -webkit-user-select: none; 19 | -moz-user-select: none; 20 | -ms-user-select: none; 21 | } 22 | 23 | .clickable { 24 | cursor: pointer; 25 | } 26 | 27 | th { 28 | font-weight: initial; 29 | } 30 | -------------------------------------------------------------------------------- /libs/datatable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular5-data-table", 3 | "description": "This library contains an Angualar5-compatible Datatable component.", 4 | "version": "1.0.3", 5 | "private": false, 6 | "license": "MIT", 7 | "repository": "https://github.com/brunano21/angular-4-data-table", 8 | "peerDependencies": { 9 | "@angular/core": "^5.0.0", 10 | "@angular/common": "^5.0.0" 11 | }, 12 | "ngPackage": { 13 | "$schema": "./node_modules/ng-packagr/ng-package.schema.json", 14 | "dest": "../../dist/angular5-data-table", 15 | "workingDirectory": "./.ng_build", 16 | "lib": { 17 | "entryFile": "src/public_api.ts" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('../libs', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo4/data-table-demo4.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import persons from './data-table-demo4-data'; 3 | import { DataTableResourceCustom } from './data-table-resources-custom'; 4 | 5 | @Component({ 6 | selector: 'app-data-table-demo-4', 7 | templateUrl: './data-table-demo4.html', 8 | styleUrls: ['./data-table-demo4.css'] 9 | }) 10 | export class DataTableDemo4Component { 11 | 12 | itemResource = new DataTableResourceCustom(persons); 13 | items = []; 14 | itemCount = 0; 15 | 16 | constructor() { 17 | this.itemResource.count().then(count => this.itemCount = count); 18 | } 19 | 20 | reloadItems(params) { 21 | this.itemResource.query(params).then(items => this.items = items); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /libs/datatable/src/utils/px.spec.ts: -------------------------------------------------------------------------------- 1 | import { PixelConverter } from './px'; 2 | 3 | describe('(datatable): PixelConverter', () => { 4 | let pipe: PixelConverter; 5 | 6 | beforeEach(() => { 7 | pipe = new PixelConverter(); 8 | }); 9 | 10 | it('providing undefined value returns undefined', () => { 11 | expect(pipe.transform(undefined, [])).toBe(undefined); 12 | }); 13 | 14 | it('providing null value returns undefined', () => { 15 | expect(pipe.transform(null, [])).toBe(undefined); 16 | }); 17 | 18 | it('providing string value returns it unchanged', () => { 19 | expect(pipe.transform('30', [])).toBe('30'); 20 | }); 21 | 22 | it('providing integer value returns integer+px', () => { 23 | expect(pipe.transform(30, [])).toBe('30px'); 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo5/data-table-demo5.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild } from '@angular/core'; 2 | import { DataTable, DataTableResource } from 'angular5-data-table'; 3 | 4 | 5 | @Component({ 6 | selector: 'app-data-table-demo-5', 7 | templateUrl: './data-table-demo5.html', 8 | styleUrls: ['./data-table-demo5.css'] 9 | }) 10 | export class DataTableDemo5Component { 11 | 12 | filmResource = new DataTableResource([]); 13 | films = []; 14 | filmCount = 0; 15 | 16 | @ViewChild(DataTable) filmsTable; 17 | 18 | constructor() { 19 | this.filmResource.count().then(count => this.filmCount = count); 20 | } 21 | 22 | reloadFilms(params) { 23 | this.filmResource.query(params).then(filmsRes => this.films = filmsRes); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { HomeComponent } from './components/home/home.component'; 4 | import { DocsViewerComponent } from './components/docs-viewer/docs-viewer.component'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: 'datatable', 9 | loadChildren: 'app/components/datatable/datatable.module#DataTableDemoModule' 10 | }, 11 | { 12 | path: 'docs/:component', 13 | component: DocsViewerComponent 14 | }, 15 | { 16 | path: '**', 17 | component: HomeComponent 18 | } 19 | ]; 20 | 21 | @NgModule({ 22 | imports: [ 23 | RouterModule.forRoot(routes) 24 | ], 25 | exports: [RouterModule], 26 | providers: [] 27 | }) 28 | export class AppRoutingModule { } 29 | -------------------------------------------------------------------------------- /libs/datatable/src/utils/min.spec.ts: -------------------------------------------------------------------------------- 1 | import { MinPipe } from './min'; 2 | 3 | describe('(datatable): MinPipe', () => { 4 | let pipe: MinPipe; 5 | 6 | beforeEach(() => { 7 | pipe = new MinPipe(); 8 | }); 9 | 10 | it('providing [1,2] returns 1', () => { 11 | expect(pipe.transform([1, 2], [])).toBe(1); 12 | }); 13 | 14 | it('providing [-1,2] returns 1', () => { 15 | expect(pipe.transform([-1, 2], [])).toBe(-1); 16 | }); 17 | 18 | it('providing [] returns Infinity', () => { 19 | expect(pipe.transform([], [])).toBe(Infinity); 20 | }); 21 | 22 | it('providing [3, 2, 1, 6, 5, 4] returns 1', () => { 23 | expect(pipe.transform([3, 2, 1, 6, 5, 4], [])).toBe(1); 24 | }); 25 | 26 | it('providing [1.5, 0.5, 1 ] returns 0.5', () => { 27 | expect(pipe.transform([1.5, 0.5, 1], [])).toBe(0.5); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo4/data-table-demo4.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 12 | 13 | 16 | 17 | 18 |
19 |
20 |
21 |

Notes for testers and developers

22 |
    23 |
  • 24 | In this sample, we test the loading spinner when the component is processing data. In order to fake it, we delay the reloadItems() function by a random time (0 to 5000 milliseconds). 25 |
  • 26 |
27 | -------------------------------------------------------------------------------- /libs/datatable/src/types/data-table-translations.type.ts: -------------------------------------------------------------------------------- 1 | export interface DataTableTranslations { 2 | headerReload?: string; 3 | headerColumnSelector?: string; 4 | headerColumnSelectorAdded?: string; 5 | headerColumnSelectorRemoved?: string; 6 | indexColumn?: string; 7 | selectColumn?: string; 8 | selectRow?: string; 9 | selectAllRows?: string; 10 | expandColumn?: string; 11 | expandRow?: string; 12 | sortedAscending?: string; 13 | sortedDescending?: string; 14 | sortAscending?: string; 15 | sortDescending?: string; 16 | paginationLimit?: string; 17 | paginationText?: string; 18 | paginationTotalPages?: string; 19 | firstPage?: string; 20 | prevPage?: string; 21 | pageNumberLabel?: string; 22 | pageNumber?: string; 23 | pageNumberNofM?: string; 24 | nextPage?: string; 25 | lastPage?: string; 26 | loadingText?: string; 27 | loadedText?: string; 28 | } 29 | -------------------------------------------------------------------------------- /spec.bundle.js: -------------------------------------------------------------------------------- 1 | import 'core-js'; 2 | import 'zone.js/dist/zone'; 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | 10 | import { getTestBed } from '@angular/core/testing'; 11 | import { 12 | BrowserDynamicTestingModule, 13 | platformBrowserDynamicTesting 14 | } from '@angular/platform-browser-dynamic/testing'; 15 | 16 | import 'rxjs'; 17 | 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | 23 | const testContext = require.context('./tests', true, /\.spec\.ts/); 24 | 25 | function requireAll(requireContext) { 26 | return requireContext.keys().map(requireContext); 27 | } 28 | 29 | const modules = requireAll(testContext); -------------------------------------------------------------------------------- /libs/datatable/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export { DataTableModule } from './datatable.module'; 2 | export { DataTableComponent as DataTable } from './components/table/table.component'; 3 | export { DataTableColumnDirective as DataTableColumn } from './directives/column/column.directive'; 4 | export { DataTableRowComponent as DataTableRow} from './components/row/row.component'; 5 | export { DataTablePaginationComponent as DataTablePagination} from './components/pagination/pagination.component'; 6 | export { DataTableHeaderComponent as DataTableHeader } from './components/header/header.component'; 7 | export { DataTableResource } from './tools/data-table-resource'; 8 | export { CellCallback } from './types/cell-callback.type'; 9 | export { RowCallback } from './types/row-callback.type'; 10 | export { DataTableTranslations } from './types/data-table-translations.type'; 11 | export { DataTableParams } from './types/data-table-params.type'; 12 | -------------------------------------------------------------------------------- /libs/datatable/src/components/header/header.component.css: -------------------------------------------------------------------------------- 1 | .data-table-header { 2 | min-height: 25px; 3 | margin-bottom: 10px; 4 | } 5 | 6 | .title { 7 | display: inline-block; 8 | margin: 5px 0 0 5px; 9 | } 10 | 11 | .button-panel { 12 | float: right; 13 | } 14 | 15 | .button-panel button { 16 | outline: none !important; 17 | } 18 | 19 | .column-selector-wrapper { 20 | position: relative; 21 | } 22 | 23 | .column-selector-box { 24 | box-shadow: 0 0 10px lightgray; 25 | background: white; 26 | width: 150px; 27 | padding: 10px; 28 | position: absolute; 29 | right: 0; 30 | top: 1px; 31 | z-index: 1060; 32 | } 33 | 34 | .column-selector-box .list-group-item.column-selector-column { 35 | padding: .5rem .25rem; 36 | } 37 | 38 | .column-selector-box .list-group-item.column-selector-column label { 39 | margin-bottom: 0; 40 | } 41 | 42 | .column-selector-box .list-group-item.column-selector-column input { 43 | margin-right: 4px; 44 | font-style: italic; 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { AppComponent } from './app.component'; 4 | import { HomeComponent } from './components/home/home.component'; 5 | import { MarkdownModule } from 'ngx-md'; 6 | import { AppRoutingModule } from './app-routing.module'; 7 | import { CodeViewerComponent } from './components/code-viewer/code-viewer.component'; 8 | import { DocsViewerComponent } from './components/docs-viewer/docs-viewer.component'; 9 | import { CommonModule } from '@angular/common'; 10 | import { FormsModule } from '@angular/forms'; 11 | 12 | @NgModule({ 13 | declarations: [ 14 | AppComponent, 15 | HomeComponent, 16 | CodeViewerComponent, 17 | DocsViewerComponent 18 | ], 19 | imports: [ 20 | CommonModule, 21 | BrowserModule, 22 | AppRoutingModule, 23 | MarkdownModule.forRoot(), 24 | ], 25 | providers: [], 26 | bootstrap: [AppComponent] 27 | }) 28 | export class AppModule { } 29 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo2/data-table-demo2.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild } from '@angular/core'; 2 | import { Cars } from './data-table-demo2-data'; 3 | import { DataTable, DataTableResource } from 'angular5-data-table'; 4 | 5 | 6 | @Component({ 7 | selector: 'app-data-table-demo-2', 8 | templateUrl: './data-table-demo2.html' 9 | }) 10 | export class DataTableDemo2Component { 11 | 12 | yearLimit = 1999; 13 | carResource = new DataTableResource(Cars); 14 | cars = []; 15 | carCount = 0; 16 | 17 | @ViewChild(DataTable) carsTable: DataTable; 18 | 19 | constructor() { 20 | this.rowColors = this.rowColors.bind(this); 21 | 22 | this.carResource.count().then(count => this.carCount = count); 23 | } 24 | 25 | reloadCars(params) { 26 | this.carResource.query(params).then(cars => this.cars = cars); 27 | } 28 | 29 | // custom features: 30 | carClicked(car) { 31 | alert(car.model); 32 | } 33 | 34 | rowColors(car) { 35 | if (car.year >= this.yearLimit) { 36 | return 'rgb(255, 255, 197)'; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bruno Bruzzano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild } from '@angular/core'; 2 | import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; 3 | import { CodeViewerComponent } from './components/code-viewer/code-viewer.component'; 4 | import 'rxjs/add/operator/filter'; 5 | import 'rxjs/add/operator/mergeMap'; 6 | import 'rxjs/add/operator/map'; 7 | 8 | @Component({ 9 | selector: 'app-root', 10 | templateUrl: './app.component.html', 11 | styleUrls: ['./app.component.css'] 12 | }) 13 | export class AppComponent { 14 | 15 | @ViewChild(CodeViewerComponent) viewer; 16 | 17 | constructor(private router: Router, private activatedRoute: ActivatedRoute) { 18 | 19 | router.events.filter(event => event instanceof NavigationEnd) 20 | .map(() => this.activatedRoute) 21 | .map(route => { 22 | while (route.firstChild) { 23 | route = route.firstChild; 24 | } 25 | return route; 26 | }) 27 | .mergeMap(route => route.data) 28 | .subscribe(data => { 29 | this.viewer.setPath(data.src ? data.src : ''); 30 | this.viewer.hideCodeBlock(); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo1/data-table-demo1.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import persons from './data-table-demo1-data'; 3 | import { DataTableResource } from 'angular5-data-table'; 4 | 5 | @Component({ 6 | selector: 'app-data-table-demo-1', 7 | providers: [], 8 | templateUrl: './data-table-demo1.html', 9 | styleUrls: ['./data-table-demo1.css'] 10 | }) 11 | export class DataTableDemo1Component { 12 | 13 | itemResource = new DataTableResource(persons); 14 | items = []; 15 | itemCount = 0; 16 | limits = [10, 20, 40, 80]; 17 | 18 | constructor() { 19 | this.itemResource.count().then(count => this.itemCount = count); 20 | } 21 | 22 | reloadItems(params) { 23 | this.itemResource.query(params).then(items => this.items = items); 24 | } 25 | 26 | // special properties: 27 | rowClick(rowEvent) { 28 | console.log('Clicked: ' + rowEvent.row.item.name); 29 | } 30 | 31 | rowDoubleClick(rowEvent) { 32 | alert('Double clicked: ' + rowEvent.row.item.name); 33 | } 34 | 35 | rowTooltip(item) { 36 | return item.jobTitle; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/components/datatable/datatable.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | import { FormsModule } from '@angular/forms'; 5 | // module 6 | import { DataTableModule } from 'angular5-data-table'; 7 | // demo components 8 | import { DataTableDemo1Component } from './demo1/data-table-demo1'; 9 | import { DataTableDemo2Component } from './demo2/data-table-demo2'; 10 | import { DataTableDemo3Component } from './demo3/data-table-demo3'; 11 | import { DataTableDemo4Component } from './demo4/data-table-demo4'; 12 | import { DataTableDemo5Component } from './demo5/data-table-demo5'; 13 | // routing 14 | import { DataTableRoutingModule } from './datatable-routing.module'; 15 | 16 | @NgModule({ 17 | imports: [ 18 | CommonModule, 19 | FormsModule, 20 | DataTableModule.forRoot(), 21 | DataTableRoutingModule 22 | ], 23 | declarations: [ 24 | DataTableDemo1Component, 25 | DataTableDemo2Component, 26 | DataTableDemo3Component, 27 | DataTableDemo4Component, 28 | DataTableDemo5Component 29 | ] 30 | }) 31 | export class DataTableDemoModule { } 32 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo3/data-table-demo3.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild } from '@angular/core'; 2 | import { films } from './data-table-demo3-data'; 3 | import { DataTable, DataTableResource, DataTableTranslations } from 'angular5-data-table'; 4 | 5 | 6 | @Component({ 7 | selector: 'app-data-table-demo-3', 8 | templateUrl: './data-table-demo3.html', 9 | styleUrls: ['./data-table-demo3.css'] 10 | }) 11 | export class DataTableDemo3Component { 12 | 13 | filmResource = new DataTableResource(films); 14 | films = []; 15 | filmCount = 0; 16 | labels = { 17 | loadingText: 'Your {title} table is reloading - and this is my custom message.', 18 | paginationText: 'Your table displays {from} to {to} of {total} rows.' 19 | }; 20 | 21 | @ViewChild(DataTable) filmsTable; 22 | 23 | constructor() { 24 | this.filmResource.count().then(count => this.filmCount = count); 25 | } 26 | 27 | reloadFilms(params) { 28 | this.filmResource.query(params).then(filmsRes => this.films = filmsRes); 29 | } 30 | 31 | cellColor(car) { 32 | return 'rgb(255, 255,' + (155 + Math.floor(100 - ((car.rating - 8.7) / 1.3) * 100)) + ')'; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NG Commercial 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /libs/datatable/src/utils/drag.ts: -------------------------------------------------------------------------------- 1 | export type MoveHandler = (event: MouseEvent, dx: number, dy: number, x: number, y: number) => void; 2 | export type UpHandler = (event: MouseEvent, x: number, y: number, moved: boolean) => void; 3 | 4 | /*tslint:disable-next-line*/ 5 | export function drag(event: MouseEvent, {move: move, up: up}: { move: MoveHandler, up?: UpHandler }) { 6 | 7 | const startX = event.pageX; 8 | const startY = event.pageY; 9 | let x = startX; 10 | let y = startY; 11 | let moved = false; 12 | 13 | function mouseMoveHandler(evt: MouseEvent) { 14 | const dx = evt.pageX - x; 15 | const dy = evt.pageY - y; 16 | x = evt.pageX; 17 | y = evt.pageY; 18 | if (dx || dy) { 19 | moved = true; 20 | } 21 | 22 | move(evt, dx, dy, x, y); 23 | 24 | event.preventDefault(); // to avoid text selection 25 | } 26 | 27 | function mouseUpHandler(evt: MouseEvent) { 28 | x = evt.pageX; 29 | y = evt.pageY; 30 | 31 | document.removeEventListener('mousemove', mouseMoveHandler); 32 | document.removeEventListener('mouseup', mouseUpHandler); 33 | 34 | if (up) { 35 | up(event, x, y, moved); 36 | } 37 | } 38 | 39 | document.addEventListener('mousemove', mouseMoveHandler); 40 | document.addEventListener('mouseup', mouseUpHandler); 41 | } 42 | -------------------------------------------------------------------------------- /libs/datatable/src/utils/hide.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | ElementRef, 4 | Input, 5 | Renderer2 6 | } from '@angular/core'; 7 | 8 | function isBlank(obj: any): boolean { 9 | return obj === undefined || obj === null; 10 | } 11 | 12 | @Directive({ 13 | selector: '[hide]' 14 | }) 15 | export class HideDirective { 16 | 17 | private _prevCondition = false; 18 | private _displayStyle: string; 19 | 20 | constructor(private _elementRef: ElementRef, private renderer: Renderer2) { 21 | } 22 | 23 | @Input() 24 | set hide(newCondition: boolean) { 25 | this.initDisplayStyle(); 26 | 27 | if (newCondition && (isBlank(this._prevCondition) || !this._prevCondition)) { 28 | this._prevCondition = true; 29 | this.renderer.setStyle(this._elementRef.nativeElement, 'display', 'none'); 30 | } else if (!newCondition && (isBlank(this._prevCondition) || this._prevCondition)) { 31 | this._prevCondition = false; 32 | this.renderer.setStyle(this._elementRef.nativeElement, 'display', this._displayStyle); 33 | } 34 | } 35 | 36 | private initDisplayStyle() { 37 | if (this._displayStyle === undefined) { 38 | const displayStyle = this._elementRef.nativeElement.style.display; 39 | if (displayStyle !== 'none') { 40 | this._displayStyle = displayStyle; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /libs/datatable/src/utils/hide.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { Component, DebugElement } from '@angular/core'; 3 | import { HideDirective } from './hide'; 4 | import { By } from '@angular/platform-browser'; 5 | 6 | @Component({ 7 | template: ` 8 |

Should paragraph element be hide

9 |

Should not paragraph element be hide

10 | ` 11 | }) 12 | class TestComponent { 13 | } 14 | 15 | describe('(datatable): Hide', () => { 16 | let fixture: ComponentFixture; 17 | let debugElements: DebugElement[]; 18 | beforeEach(() => { 19 | fixture = TestBed.configureTestingModule({ 20 | declarations: [HideDirective, TestComponent] 21 | }) 22 | .createComponent(TestComponent); 23 | fixture.detectChanges(); 24 | debugElements = fixture.debugElement.queryAll(By.directive(HideDirective)); 25 | }); 26 | 27 | it('should [hide] directive set style display to \'none\' if it was created with true as value', () => { 28 | expect((debugElements[0].nativeElement as HTMLElement).style.display).toBe('none'); 29 | }); 30 | it('should [hide] directive set style display to \'\' if it was created with false as value', () => { 31 | expect((debugElements[1].nativeElement as HTMLElement).style.display).toBe(''); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /libs/datatable/src/types/default-translations.type.ts: -------------------------------------------------------------------------------- 1 | import { DataTableTranslations } from './data-table-translations.type'; 2 | 3 | export const defaultTranslations: DataTableTranslations = { 4 | headerReload: 'reload {title} table', 5 | headerColumnSelector: 'column selector - adds or removes columns from {title} table', 6 | headerColumnSelectorAdded: '{column_name} added to {title} table', 7 | headerColumnSelectorRemoved: '{column_name} removed from {title} table', 8 | indexColumn: 'index', 9 | selectColumn: 'select', 10 | selectRow: 'select {cell_content}', 11 | selectAllRows: 'select all rows', 12 | expandColumn: 'expand', 13 | expandRow: 'expand {cell_content}', 14 | sortedAscending: '{title} table sorted by {header} ascending', 15 | sortedDescending: '{title} table sorted by {header} descending', 16 | sortAscending: 'activate to sort ascending', 17 | sortDescending: 'activate to sort descending', 18 | paginationLimit: 'Limit', 19 | paginationText: 'Results: {from} to {to} of {total}', 20 | paginationTotalPages: 'of', 21 | firstPage: 'first page', 22 | prevPage: 'previous page', 23 | pageNumberLabel: 'Page', 24 | pageNumber: 'page number', 25 | pageNumberNofM: '({N} of {M})', 26 | nextPage: 'next page', 27 | lastPage: 'last page', 28 | loadingText: '{title} table is loading', 29 | loadedText: '{title} table loaded' 30 | }; 31 | -------------------------------------------------------------------------------- /src/app/components/datatable/datatable-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { DataTableDemo1Component } from './demo1/data-table-demo1'; 4 | import { DataTableDemo2Component } from './demo2/data-table-demo2'; 5 | import { DataTableDemo3Component } from './demo3/data-table-demo3'; 6 | import { DataTableDemo4Component } from './demo4/data-table-demo4'; 7 | import { DataTableDemo5Component } from './demo5/data-table-demo5'; 8 | 9 | const routes: Routes = [ 10 | { 11 | path: 'demo1', 12 | component: DataTableDemo1Component, 13 | data: {src: 'datatable/demo1/data-table-demo1' } 14 | }, 15 | { 16 | path: 'demo2', 17 | component: DataTableDemo2Component, 18 | data: {src: 'datatable/demo2/data-table-demo2' } 19 | }, 20 | { 21 | path: 'demo3', 22 | component: DataTableDemo3Component, 23 | data: {src: 'datatable/demo3/data-table-demo3' } 24 | }, 25 | { 26 | path: 'demo4', 27 | component: DataTableDemo4Component, 28 | data: {src: 'datatable/demo4/data-table-demo4' } 29 | }, 30 | { 31 | path: 'demo5', 32 | component: DataTableDemo5Component, 33 | data: {src: 'datatable/demo5/data-table-demo5' } 34 | } 35 | ]; 36 | 37 | @NgModule({ 38 | imports: [RouterModule.forChild(routes)], 39 | exports: [RouterModule] 40 | }) 41 | export class DataTableRoutingModule { } 42 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo5/data-table-demo5.html: -------------------------------------------------------------------------------- 1 |
2 | 16 | 20 | 21 | 25 | 26 | 30 | 31 | 34 | 35 | 36 |
37 |
38 |
39 |

Notes for testers and developers

40 |
    41 |
  • 42 | This demo wants to demonstrate what will be shown when no items are rendered into the table. 43 |
  • 44 |
45 | 46 | -------------------------------------------------------------------------------- /libs/datatable/src/tools/data-table-resource.ts: -------------------------------------------------------------------------------- 1 | import {DataTableParams} from '../types/data-table-params.type'; 2 | 3 | 4 | export class DataTableResource { 5 | 6 | constructor(private items: T[]) { 7 | } 8 | 9 | query(params: DataTableParams, filter?: (item: T, index: number, items: T[]) => boolean): Promise { 10 | 11 | let result: T[] = []; 12 | if (filter) { 13 | result = this.items.filter(filter); 14 | } else { 15 | result = this.items.slice(); // shallow copy to use for sorting instead of changing the original 16 | } 17 | 18 | if (params.sortBy) { 19 | result.sort((a: DataTableParams, b: DataTableParams) => { 20 | if (typeof a[params.sortBy] === 'string') { 21 | return a[params.sortBy].localeCompare(b[params.sortBy]); 22 | } else { 23 | return a[params.sortBy] - b[params.sortBy]; 24 | } 25 | }); 26 | if (params.sortAsc === false) { 27 | result.reverse(); 28 | } 29 | } 30 | if (params.offset !== undefined) { 31 | if (params.limit === undefined) { 32 | result = result.slice(params.offset, result.length); 33 | } else { 34 | result = result.slice(params.offset, params.offset + params.limit); 35 | } 36 | } 37 | 38 | return new Promise((resolve, reject) => { 39 | setTimeout(() => resolve(result)); 40 | }); 41 | } 42 | 43 | count(): Promise { 44 | return new Promise((resolve, reject) => { 45 | setTimeout(() => resolve(this.items.length)); 46 | }); 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /libs/datatable/src/directives/column/column.directive.ts: -------------------------------------------------------------------------------- 1 | import {ContentChild, Directive, ElementRef, Input, OnInit} from '@angular/core'; 2 | import {DataTableRowComponent} from '../../components/row/row.component'; 3 | import {CellCallback} from '../../types/cell-callback.type'; 4 | 5 | 6 | @Directive({ 7 | selector: 'data-table-column' 8 | }) 9 | export class DataTableColumnDirective implements OnInit { 10 | 11 | private styleClassObject = {}; // for [ngClass] 12 | 13 | // init: 14 | @Input() header: string; 15 | @Input() sortable = false; 16 | @Input() resizable = false; 17 | @Input() property: string; 18 | @Input() styleClass: string; 19 | @Input() cellColors: CellCallback; 20 | 21 | // init and state: 22 | @Input() width: number | string; 23 | @Input() visible = true; 24 | 25 | @ContentChild('dataTableCell') cellTemplate: ElementRef; 26 | @ContentChild('dataTableHeader') headerTemplate: ElementRef; 27 | 28 | getCellColor(row: DataTableRowComponent, index: number) { 29 | if (this.cellColors !== undefined) { 30 | return (this.cellColors)(row.item, row, this, index); 31 | } 32 | } 33 | 34 | ngOnInit() { 35 | this._initCellClass(); 36 | } 37 | 38 | private _initCellClass() { 39 | if (!this.styleClass && this.property) { 40 | if (/^[a-zA-Z0-9_]+$/.test(this.property)) { 41 | this.styleClass = 'column-' + this.property; 42 | } else { 43 | this.styleClass = 'column-' + this.property.replace(/[^a-zA-Z0-9_]/g, ''); 44 | } 45 | } 46 | 47 | if (this.styleClass != null) { 48 | this.styleClassObject = { 49 | [this.styleClass]: true 50 | }; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/components/code-viewer/code-viewer.component.html: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 | 8 |
9 | 10 |
11 | TS 12 | HTML 13 | CSS 14 |
15 | 16 | 17 |
18 |
19 |
{{ tsContent }}
20 |
21 |
22 |
{{ htmlContent }}
23 |
24 |
25 |
{{ cssContent }}
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /libs/datatable/src/components/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ElementRef, 4 | forwardRef, 5 | HostListener, 6 | Inject 7 | } from '@angular/core'; 8 | import { DataTableComponent } from '../table/table.component'; 9 | 10 | @Component({ 11 | selector: 'data-table-header', 12 | templateUrl: './header.component.html', 13 | styleUrls: ['./header.component.css'] 14 | }) 15 | export class DataTableHeaderComponent { 16 | 17 | columnSelectorOpen = false; 18 | 19 | constructor(@Inject(forwardRef(() => DataTableComponent)) public dataTable: DataTableComponent, 20 | private elemRef: ElementRef) { 21 | } 22 | 23 | @HostListener('document:click', ['$event']) onClickHandler(event) { 24 | if (!this.elemRef.nativeElement.contains(event.target)) { 25 | this.columnSelectorOpen = false; 26 | } 27 | } 28 | 29 | @HostListener('document:keyup', ['$event']) onKeyUpHandler(event) { 30 | if (event.keyCode === 27 || (event.keyCode === 9 && !this.elemRef.nativeElement.contains(event.target))) { 31 | this.columnSelectorOpen = false; 32 | } 33 | } 34 | 35 | onChange(event: Event) { 36 | const isChecked = ( event.target).checked; 37 | const columnName = ( event.target).parentElement.textContent.trim(); 38 | const interpolateParams = { 39 | 'column_name': columnName, 40 | 'title': this.dataTable.title 41 | }; 42 | 43 | this.dataTable.columnSelectorNotification = (isChecked ? this.dataTable.labels.headerColumnSelectorAdded : 44 | this.dataTable.labels.headerColumnSelectorRemoved) 45 | .replace('{column_name}', interpolateParams.column_name) 46 | .replace('{title}', interpolateParams.title); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |

Angular5 DataTable Demo App

2 | 3 |
4 | Home 5 | 9 | 10 |
11 |
    12 |
  • 13 | Docs 14 |
  • 15 |
  • 16 | Demo 1 17 |
  • 18 |
  • 19 | Demo 2 20 |
  • 21 |
  • 22 | Demo 3 23 |
  • 24 |
  • 25 | Demo 4 26 |
  • 27 |
  • 28 | Demo 5 29 |
  • 30 |
31 |
32 |
33 |
34 |
35 |
36 | 37 |
38 |
39 | 40 |
41 |
42 |
43 | 44 | -------------------------------------------------------------------------------- /libs/datatable/src/datatable.module.ts: -------------------------------------------------------------------------------- 1 | // modules 2 | import { ModuleWithProviders, NgModule } from '@angular/core'; 3 | import { CommonModule } from '@angular/common'; 4 | import { BrowserModule } from '@angular/platform-browser'; 5 | import { FormsModule } from '@angular/forms'; 6 | 7 | // utils 8 | import { HideDirective } from './utils/hide'; 9 | import { MinPipe } from './utils/min'; 10 | import { PixelConverter } from './utils/px'; 11 | // types & tools 12 | import { DataTableTranslations } from './types/data-table-translations.type'; 13 | import { CellCallback } from './types/cell-callback.type'; 14 | import { RowCallback } from './types/row-callback.type'; 15 | import { DataTableResource } from './tools/data-table-resource'; 16 | import { DataTableParams } from './types/data-table-params.type'; 17 | // components & directives 18 | import { DataTableComponent } from './components/table/table.component'; 19 | import { DataTableRowComponent } from './components/row/row.component'; 20 | import { DataTableColumnDirective } from './directives/column/column.directive'; 21 | import { DataTableHeaderComponent } from './components/header/header.component'; 22 | import { DataTablePaginationComponent } from './components/pagination/pagination.component'; 23 | 24 | export { 25 | DataTableComponent, DataTableColumnDirective, DataTableRowComponent, DataTablePaginationComponent, DataTableHeaderComponent, 26 | DataTableResource, 27 | DataTableParams, DataTableTranslations, 28 | CellCallback, RowCallback 29 | }; 30 | 31 | @NgModule({ 32 | declarations: [ 33 | DataTableComponent, DataTableColumnDirective, 34 | DataTableRowComponent, DataTablePaginationComponent, DataTableHeaderComponent, 35 | PixelConverter, HideDirective, MinPipe 36 | ], 37 | imports: [ 38 | CommonModule, 39 | FormsModule 40 | ], 41 | exports: [DataTableComponent, DataTableColumnDirective] 42 | }) 43 | 44 | export class DataTableModule { 45 | public static forRoot(): ModuleWithProviders { 46 | return { 47 | ngModule: DataTableModule, 48 | providers: [] 49 | }; 50 | } 51 | } 52 | 53 | 54 | -------------------------------------------------------------------------------- /libs/datatable/src/components/row/row.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ElementRef, 4 | EventEmitter, 5 | forwardRef, 6 | Inject, 7 | Input, 8 | OnDestroy, 9 | OnInit, 10 | Output, 11 | Renderer2 12 | } from '@angular/core'; 13 | import { DataTableComponent } from '../table/table.component'; 14 | 15 | @Component({ 16 | selector: '[dataTableRow]', 17 | templateUrl: './row.component.html', 18 | styleUrls: ['./row.component.css'] 19 | }) 20 | export class DataTableRowComponent implements OnInit, OnDestroy { 21 | 22 | public _this = this; 23 | 24 | @Input() item: any; 25 | @Input() index: number; 26 | 27 | expanded: boolean; 28 | 29 | private _listeners = []; 30 | 31 | // row selection: 32 | private _selected: boolean; 33 | 34 | @Output() selectedChange = new EventEmitter(); 35 | 36 | get selected() { 37 | return this._selected; 38 | } 39 | 40 | set selected(selected) { 41 | this._selected = selected; 42 | this.selectedChange.emit(selected); 43 | } 44 | 45 | // other: 46 | get displayIndex() { 47 | if (this.dataTable.pagination) { 48 | return this.dataTable.displayParams.offset + this.index + 1; 49 | } else { 50 | return this.index + 1; 51 | } 52 | } 53 | 54 | getTooltip() { 55 | if (this.dataTable.rowTooltip) { 56 | return this.dataTable.rowTooltip(this.item, this, this.index); 57 | } 58 | return ''; 59 | } 60 | 61 | constructor(@Inject(forwardRef(() => DataTableComponent)) public dataTable: DataTableComponent, 62 | private renderer: Renderer2, private elementRef: ElementRef) {} 63 | 64 | ngOnInit() { 65 | if (this.dataTable.rowClick.observers.length > 0) { 66 | this._listeners.push( 67 | this.renderer.listen(this.elementRef.nativeElement, 'click', 68 | (event) => this.dataTable.rowClicked(this, event)) 69 | ); 70 | } 71 | if (this.dataTable.rowDoubleClick.observers.length > 0) { 72 | this._listeners.push( 73 | this.renderer.listen(this.elementRef.nativeElement, 'dblclick', 74 | (event) => this.dataTable.rowDoubleClicked(this, event)) 75 | ); 76 | } 77 | } 78 | 79 | ngOnDestroy() { 80 | this.selected = false; 81 | this._listeners.forEach(fn => fn()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /libs/datatable/src/components/pagination/pagination.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ElementRef, 4 | forwardRef, 5 | Inject, 6 | Input, 7 | ViewChild 8 | } from '@angular/core'; 9 | import { DataTableComponent} from '../table/table.component'; 10 | 11 | let nextId = 0; 12 | 13 | @Component({ 14 | selector: 'data-table-pagination', 15 | templateUrl: './pagination.component.html', 16 | styleUrls: ['./pagination.component.css'] 17 | }) 18 | export class DataTablePaginationComponent { 19 | 20 | id = `pagination-${nextId++}`; 21 | 22 | @ViewChild('pageInput') pageInput: ElementRef; 23 | 24 | Math: any; 25 | 26 | @Input() limits: number[]; 27 | 28 | constructor(@Inject(forwardRef(() => DataTableComponent)) public dataTable: DataTableComponent) { 29 | this.Math = Math; 30 | } 31 | 32 | pageBack() { 33 | this.dataTable.offset -= Math.min(this.dataTable.limit, this.dataTable.offset); 34 | if (this.dataTable.offset <= 0) { 35 | this.pageInput.nativeElement.focus(); 36 | } 37 | } 38 | pageForward() { 39 | this.dataTable.offset += this.dataTable.limit; 40 | if ((this.dataTable.offset + this.dataTable.limit) >= this.dataTable.itemCount) { 41 | this.pageInput.nativeElement.focus(); 42 | } 43 | } 44 | 45 | pageFirst() { 46 | this.dataTable.offset = 0; 47 | this.pageInput.nativeElement.focus(); 48 | } 49 | 50 | pageLast() { 51 | this.dataTable.offset = (this.maxPage - 1) * this.dataTable.limit; 52 | if ((this.dataTable.offset + this.dataTable.limit) >= this.dataTable.itemCount) { 53 | this.pageInput.nativeElement.focus(); 54 | } 55 | } 56 | 57 | get maxPage() { 58 | return Math.ceil(this.dataTable.itemCount / this.dataTable.limit); 59 | } 60 | 61 | get limit() { 62 | return this.dataTable.limit; 63 | } 64 | 65 | set limit(value) { 66 | this.dataTable.limit = +value; 67 | // returning back to the first page. 68 | this.page = 1; 69 | } 70 | 71 | get page() { 72 | return this.dataTable.page; 73 | } 74 | 75 | set page(value) { 76 | this.dataTable.page = +value; 77 | } 78 | 79 | validate(event) { 80 | const newValue = +event.target.value; 81 | if (newValue !== this.page) { 82 | this.page = (event.target.value > this.maxPage) ? this.maxPage : (newValue < 1 ) ? 1 : newValue; 83 | event.target.value = this.page; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular5-datable-demo", 3 | "version": "0.1.1", 4 | "license": "MIT", 5 | "private": false, 6 | "scripts": { 7 | "ng": "ng", 8 | "start": "ng serve", 9 | "build": "ng build --prod", 10 | "test": "ng test -sm=false", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e", 13 | "build:docs": "compodoc ./libs -p src/tsconfig.packages.json -s", 14 | "build:app": "cpr dist/angular5-data-table src/angualar5-datatable --overwrite && ng build --app packages && rimraf src/angular5-data-table", 15 | "build:app:prod": "cpr dist/angular5-data-table src/angular5-data-table --overwrite && ng build --prod --named-chunks=true --app packages && rimraf src/angular5-data-table", 16 | "build:libs:datatable": "ng-packagr -p libs/datatable/package.json", 17 | "publish:libs:datatable": "npm publish dist/angular5-data-table" 18 | }, 19 | "dependencies": { 20 | "@angular/animations": "^5.2.0", 21 | "@angular/common": "^5.2.0", 22 | "@angular/compiler": "^5.2.0", 23 | "@angular/core": "^5.2.0", 24 | "@angular/forms": "^5.2.0", 25 | "@angular/http": "^5.2.0", 26 | "@angular/platform-browser": "^5.2.0", 27 | "@angular/platform-browser-dynamic": "^5.2.0", 28 | "@angular/router": "^5.2.0", 29 | "bootstrap": "^4.0.0", 30 | "core-js": "^2.4.1", 31 | "font-awesome": "^4.7.0", 32 | "highlight.js": "^9.12.0", 33 | "lodash-es": "^4.17.4", 34 | "ngx-md": "^3.1.1", 35 | "popper.js": "^1.14.3", 36 | "rxjs": "^5.5.6", 37 | "tslint-angular": "^1.1.1", 38 | "zone.js": "^0.8.19" 39 | }, 40 | "devDependencies": { 41 | "@angular-devkit/core": "0.3.2", 42 | "@angular-devkit/schematics": "0.3.2", 43 | "@angular/cli": "1.7.1", 44 | "@angular/compiler-cli": "^5.2.0", 45 | "@angular/language-service": "^5.2.0", 46 | "@compodoc/compodoc": "^1.0.5", 47 | "@types/jasmine": "~2.8.3", 48 | "@types/jasminewd2": "~2.0.2", 49 | "@types/node": "^8.0.47", 50 | "codelyzer": "^4.2.0", 51 | "cpr": "^3.0.0", 52 | "jasmine-core": "~2.8.0", 53 | "jasmine-spec-reporter": "~4.2.1", 54 | "karma": "~2.0.0", 55 | "karma-chrome-launcher": "~2.2.0", 56 | "karma-coverage-istanbul-reporter": "^1.2.1", 57 | "karma-jasmine": "~1.1.0", 58 | "karma-jasmine-html-reporter": "^0.2.2", 59 | "ng-packagr": "^2.1.0", 60 | "node-sass": "4.7.2", 61 | "protractor": "~5.1.2", 62 | "rimraf": "^2.6.1", 63 | "ts-node": "~4.1.0", 64 | "tsickle": "^0.26.0", 65 | "tslint": "~5.9.1", 66 | "typescript": "2.6.1" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | import 'core-js/es6/symbol'; 23 | import 'core-js/es6/object'; 24 | import 'core-js/es6/function'; 25 | import 'core-js/es6/parse-int'; 26 | import 'core-js/es6/parse-float'; 27 | import 'core-js/es6/number'; 28 | import 'core-js/es6/math'; 29 | import 'core-js/es6/string'; 30 | import 'core-js/es6/date'; 31 | import 'core-js/es6/array'; 32 | import 'core-js/es6/regexp'; 33 | import 'core-js/es6/map'; 34 | import 'core-js/es6/weak-map'; 35 | import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Required to support Web Animations `@angular/platform-browser/animations`. 51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 52 | **/ 53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 54 | 55 | 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | import 'zone.js/dist/zone'; // Included with Angular CLI. 61 | 62 | 63 | 64 | /*************************************************************************************************** 65 | * APPLICATION IMPORTS 66 | */ 67 | -------------------------------------------------------------------------------- /src/app/components/code-viewer/code-viewer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, ViewChild } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { forkJoin } from 'rxjs/observable/forkJoin'; 5 | import 'rxjs/add/observable/of'; 6 | 7 | import * as Highlight from 'highlight.js'; 8 | import { catchError } from 'rxjs/operators'; 9 | 10 | @Component({ 11 | selector: 'code-viewer', 12 | templateUrl: './code-viewer.component.html', 13 | styleUrls: ['./code-viewer.component.css'] 14 | }) 15 | export class CodeViewerComponent { 16 | 17 | @ViewChild('tsViewerNode') tsNode: ElementRef; 18 | @ViewChild('htmlViewerNode') htmlNode: ElementRef; 19 | @ViewChild('cssViewerNode') cssNode: ElementRef; 20 | 21 | showTsNode = true; 22 | showHtmlNode = true; 23 | showCssNode = true; 24 | 25 | tsContent = ''; 26 | htmlContent = ''; 27 | cssContent = ''; 28 | 29 | visibility = false; 30 | baseUrl = 'assets/src'; 31 | path = ''; 32 | 33 | constructor(private http: HttpClient) { 34 | Highlight.configure({ useBR: false, languages: ['css', 'html', 'ts'] }); 35 | Highlight.initHighlighting(); 36 | } 37 | 38 | public toggle(): void { 39 | if (!this.visibility && this.path.length > 0) { 40 | const ts = this.http.get(`${this.baseUrl}/${this.path}.ts`, {responseType: 'text'}) 41 | .pipe(catchError(() => Observable.of(''))); 42 | const html = this.http.get(`${this.baseUrl}/${this.path}.html`, {responseType: 'text'}) 43 | .pipe(catchError(() => Observable.of(''))); 44 | const css = this.http.get(`${this.baseUrl}/${this.path}.css`, {responseType: 'text'}) 45 | .pipe(catchError(() => Observable.of(''))); 46 | forkJoin([ts, html, css]).subscribe((results: string[]) => { 47 | this.tsContent = results[0]; 48 | this.htmlContent = results[1]; 49 | this.cssContent = results[2]; 50 | this.showCodeBlock(); 51 | setTimeout(() => this.highlight(), 250); 52 | }, error => { 53 | console.debug(error); /* tslint:disable-line:no-console */ 54 | }); 55 | } else { 56 | this.hideCodeBlock(); 57 | } 58 | } 59 | 60 | public setPath(path: string): void { 61 | this.path = path; 62 | } 63 | 64 | hideCodeBlock(): void { 65 | this.visibility = false; 66 | } 67 | 68 | showCodeBlock(): void { 69 | this.visibility = true; 70 | } 71 | 72 | highlight() { 73 | if (this.showTsNode) { Highlight.highlightBlock(this.tsNode.nativeElement); } 74 | if (this.showHtmlNode) { Highlight.highlightBlock(this.htmlNode.nativeElement); } 75 | if (this.showCssNode) { Highlight.highlightBlock(this.cssNode.nativeElement); } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /libs/datatable/src/components/table/table.component.css: -------------------------------------------------------------------------------- 1 | /* bootstrap override: */ 2 | 3 | :host /deep/ .data-table.table > tbody + tbody { 4 | border-top: none; 5 | } 6 | 7 | :host /deep/ .data-table.table td { 8 | vertical-align: middle; 9 | } 10 | 11 | :host /deep/ .data-table > thead > tr > th, 12 | :host /deep/ .data-table > tbody > tr > td { 13 | overflow: hidden; 14 | } 15 | 16 | :host /deep/ .data-table > thead > tr > td { 17 | border-bottom: 2px solid #dee2e6; 18 | } 19 | 20 | /* I can't use the bootstrap striped table, because of the expandable rows */ 21 | :host /deep/ .row-odd { 22 | background-color: #F6F6F6; 23 | } 24 | 25 | :host /deep/ .row-even { 26 | } 27 | 28 | .data-table .substitute-rows > tr:hover, 29 | :host /deep/ .data-table .data-table-row:hover { 30 | background-color: #ECECEC; 31 | } 32 | 33 | /* table itself: */ 34 | 35 | .data-table { 36 | box-shadow: 0 0 15px rgb(236, 236, 236); 37 | table-layout: fixed; 38 | } 39 | 40 | /* header cells: */ 41 | 42 | .column-header { 43 | position: relative; 44 | } 45 | 46 | .expand-column-header { 47 | width: 50px; 48 | } 49 | 50 | .select-column-header { 51 | width: 50px; 52 | text-align: center; 53 | } 54 | 55 | .index-column-header { 56 | width: 40px; 57 | } 58 | 59 | .column-header.sortable button { 60 | -webkit-box-sizing: content-box; 61 | -moz-box-sizing: content-box; 62 | box-sizing: content-box; 63 | background: none; 64 | border: 0; 65 | color: inherit; 66 | cursor: pointer; 67 | font: inherit; 68 | line-height: normal; 69 | overflow: visible; 70 | padding: 0; 71 | -webkit-appearance: button; /* for input */ 72 | -webkit-user-select: none; /* for button */ 73 | -moz-user-select: none; 74 | -ms-user-select: none; 75 | text-align: left; 76 | } 77 | 78 | .column-header .column-sort-icon { 79 | margin-left: 8px; 80 | } 81 | 82 | .column-header.resizable .column-sort-icon { 83 | margin-right: 8px; 84 | } 85 | 86 | .column-header .column-sort-icon .column-sortable-icon { 87 | color: lightgray; 88 | } 89 | 90 | .column-header .column-resize-handle { 91 | position: absolute; 92 | top: 0; 93 | right: 0; 94 | margin: 0; 95 | padding: 0; 96 | width: 8px; 97 | height: 100%; 98 | cursor: col-resize; 99 | } 100 | 101 | /* cover: */ 102 | .data-table-box { 103 | position: relative; 104 | } 105 | 106 | .busy { 107 | position: absolute; 108 | z-index: 1; 109 | top: 0; 110 | bottom: 0; 111 | left: 0; 112 | right: 0; 113 | background-color: rgba(0, 0, 0, 0.25); 114 | } 115 | 116 | .busy > i { 117 | position: absolute; 118 | left: 50%; 119 | top: 50%; 120 | transform: translate(-50%, -50%); 121 | } 122 | -------------------------------------------------------------------------------- /libs/datatable/src/components/row/row.component.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 26 |
27 |
29 | 30 | 33 |
34 |
36 | 37 |
38 | 39 | 40 | 41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo3/data-table-demo3.html: -------------------------------------------------------------------------------- 1 |
2 | 18 | 19 |
20 |
21 | 25 | 26 | 30 | 31 | 36 | 37 | 40 | 41 |
42 |
43 | Selected: 44 | No item selected 45 | 46 |
47 |
48 |
49 |
50 |

Notes for testers and developers

51 |
    52 |
  • 53 | The table demo is initialized with selectOnRowClick=true. This means that a row selection can be triggered clicking on the row. However, this functionality is not accessible, unless the user provides enough information regarding the triggering action consequences and implements an <input> (like a <button> or <a>) as part of the row that will allow the selection via keyboard too. 54 |
  • 55 |
  • 56 | The Rating column defines the column background color based on the rating value, via the cellColors function. The resulting color could be not compliant with high contrast requirements and it must be taken care by the developer for the appropriate fix. 57 |
  • 58 |
  • 59 | Selected region is not part of the datatable component. Any accessibility issue related to it must be fixed by developer. 60 |
  • 61 |
62 | 63 | -------------------------------------------------------------------------------- /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "angular5-data-table" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "name": "app", 10 | "outDir": "dist/app", 11 | "assets": [ 12 | "assets", 13 | "favicon.ico", 14 | {"glob": "**/*","input":"./app/components/datatable/","output":"./assets/src/datatable"}, 15 | {"glob": "**/*.md","input":"../libs/datatable/","output":"./assets/datatable"} 16 | ], 17 | "index": "index.html", 18 | "main": "main.ts", 19 | "polyfills": "polyfills.ts", 20 | "test": "test.ts", 21 | "tsconfig": "tsconfig.app.json", 22 | "testTsconfig": "tsconfig.spec.json", 23 | "prefix": "app", 24 | "styles": [ 25 | "../node_modules/bootstrap/dist/css/bootstrap.min.css", 26 | "../node_modules/font-awesome/css/font-awesome.css", 27 | "styles.css" 28 | ], 29 | "scripts": [], 30 | "environmentSource": "environments/environment.ts", 31 | "environments": { 32 | "dev": "environments/environment.ts", 33 | "prod": "environments/environment.prod.ts" 34 | } 35 | }, 36 | { 37 | "root": "src", 38 | "name": "packages", 39 | "outDir": "dist/app", 40 | "assets": [ 41 | "assets", 42 | "favicon.ico" 43 | ], 44 | "index": "index.html", 45 | "main": "main.ts", 46 | "polyfills": "polyfills.ts", 47 | "test": "test.ts", 48 | "tsconfig": "tsconfig.packages.json", 49 | "testTsconfig": "tsconfig.spec.json", 50 | "prefix": "app", 51 | "styles": [ 52 | "../node_modules/bootstrap/dist/css/bootstrap.min.css", 53 | "../node_modules/font-awesome/css/font-awesome.css", 54 | "styles.css" 55 | ], 56 | "scripts": [], 57 | "environmentSource": "environments/environment.ts", 58 | "environments": { 59 | "dev": "environments/environment.ts", 60 | "prod": "environments/environment.prod.ts" 61 | } 62 | } 63 | ], 64 | "e2e": { 65 | "protractor": { 66 | "config": "./protractor.conf.js" 67 | } 68 | }, 69 | "lint": [ 70 | { 71 | "project": "src/tsconfig.app.json", 72 | "exclude": "**/node_modules/**" 73 | }, 74 | { 75 | "project": "src/tsconfig.packages.json", 76 | "exclude": "**/node_modules/**" 77 | }, 78 | { 79 | "project": "src/tsconfig.spec.json", 80 | "exclude": "**/node_modules/**" 81 | }, 82 | { 83 | "project": "e2e/tsconfig.e2e.json", 84 | "exclude": "**/node_modules/**" 85 | } 86 | ], 87 | "test": { 88 | "karma": { 89 | "config": "./karma.conf.js" 90 | } 91 | }, 92 | "defaults": { 93 | "styleExt": "css", 94 | "component": {} 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /libs/datatable/src/components/header/header.component.html: -------------------------------------------------------------------------------- 1 |
2 |

3 |
4 | 9 | 16 |
17 |
18 |
    19 |
  • 20 | 24 |
  • 25 |
  • 26 | 30 |
  • 31 |
  • 32 | 36 |
  • 37 | 38 |
  • 40 | 44 |
  • 45 |
    46 |
47 |
48 |
49 |
50 |
51 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo2/data-table-demo2.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 17 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 32 | 33 | 36 | 37 | 39 | 40 | Actions 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 |
49 | Selected: 50 | 51 |
52 |
53 | 54 |
55 |
56 |
57 |
58 |
59 |

Notes for testers and developers

60 |
    61 |
  • 62 | The first column (Year) in this demo makes use of custom content through dataTableCell feature. in order to display custom content. As stated in datatable documentation, this is out from the control of the datatable component. Hence if the developers use it, it's developer's responsibility to check and fix any accessibility issue. Specifically, in this case Year cell content is a <input> element with not text associated. The developer must provide an accessible name for it. 63 |
  • 64 |
  • 65 | The last column (Actions) in this demo makes use of custom content through dataTableCell feature. in order to display custom content. As stated in datatable documentation, this is out from the control of the datatable component. Hence if the developers use it, it's developer's responsibility to check and fix any accessibility issue. Specifically, in this case "Year" cell content is a <input> element with not text associated. The developer must provide an accessible name for it. 66 | The table demo defines a rowClick($event) function. This is a function that is executed when the user clicks on the row. However, this functionality is not accessible, unless the user provides an <input> (like a <button> or <a>) as part of the row that will allow to trigger the click via keyboard too. 67 |
  • 68 |
  • 69 | Highlight after input and Selected region are not part of the datatable component. Any accessibility issue related to them must be fixed by developer. 70 |
  • 71 |
72 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo1/data-table-demo1.html: -------------------------------------------------------------------------------- 1 |
2 | 11 | 16 | 17 | 21 | 22 | {{ item.date | date:'yyyy-MM-dd' }} 23 | 24 | 25 | 29 | 30 | 34 | 35 | 40 | 41 | Active 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 |
53 |
54 |

Notes for testers and developers

55 |
    56 |
  • 57 | The last column in the example makes use of dataTableHeader and dataTableCell in order to display custom content. As stated in datatable documentation, this is out from the control of the datatable component. Hence if the developers use any of them, it's developer's responsibility to check and fix any accessibility issue. Specifically, in this case "Active" text lacks sufficient contrast with the current background color and the "Status" icons in the Active column do not have an accessible name. See the below snippet for a fix sample. 58 |
    
    59 |           <span class="fa fa-check" aria-hidden="true"></span>
    60 |           <span class="sr-only">Yes</span>
    61 |         
    62 |
  • 63 |
  • 64 | The table demo defines a rowClick($event) function. This is a function that is executed when the user clicks on the row. However, this functionality is not accessible, unless the user provides enough information regarding the triggering action consequences and implements an <input> (like a <button> or <a>) as part of the row that will allow to trigger the click via keyboard too. 65 |
  • 66 |
  • 67 | The table demo defines a rowDoubleClick($event) function. This is a function that is executed when the user double-clicks on the row. However, this functionality is not accessible, unless the user provides enough information regarding the triggering action consequences and implements an <input> (like a <button> or <a>) as part of the row that will allow to trigger the click via keyboard too. 68 |
  • 69 |
70 | -------------------------------------------------------------------------------- /libs/datatable/src/components/pagination/pagination.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 7 |
8 |
9 |
10 |
11 |
12 | 13 |
14 | 17 |
18 |
19 |
20 | 27 | 34 |
35 |
36 |
37 | 40 |
41 | 52 |
53 | 54 | {{ dataTable.labels.paginationTotalPages }} {{ dataTable.lastPage }} 55 | 56 |
57 |
58 |
59 | 66 | 73 |
74 |
75 |
76 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo3/data-table-demo3-data.ts: -------------------------------------------------------------------------------- 1 | export const films = [ 2 | { 3 | title: 'The Shawshank Redemption', 4 | year: 1994, 5 | rating: 9.2, 6 | director: 'Frank Darabont', 7 | description: 'Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.' 8 | }, 9 | { 10 | title: 'The Godfather', 11 | year: 1972, 12 | rating: 9.2, 13 | director: 'Francis Ford Coppola', 14 | description: 'The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.' 15 | }, 16 | { 17 | title: 'The Godfather: Part II', 18 | year: 1974, 19 | rating: 9.0, 20 | director: 'Francis Ford Coppola', 21 | description: 'The early life and career of Vito Corleone in 1920s New York is portrayed while his son, Michael, expands and tightens ' + 22 | 'his grip on his crime syndicate stretching from Lake Tahoe, Nevada to pre-revolution 1958 Cuba.' 23 | }, 24 | { 25 | title: 'The Dark Knight', 26 | year: 2008, 27 | rating: 8.9, 28 | director: 'Christopher Nolan', 29 | description: 'When the menace known as the Joker wreaks havoc and chaos on the people of Gotham, the caped crusader must come to ' + 30 | 'terms with one of the greatest psychological tests of his ability to fight injustice.' 31 | }, 32 | { 33 | title: 'Pulp Fiction', 34 | year: 1994, 35 | rating: 8.9, 36 | director: 'Quentin Tarantino', 37 | description: 'The lives of two mob hit men, a boxer, a gangster\'s wife, and a pair of diner bandits intertwine in four tales of ' + 38 | 'violence and redemption.' 39 | }, 40 | { 41 | title: 'Schindler\'s List', 42 | year: 1993, 43 | rating: 8.9, 44 | director: 'Stephen Spielberg', 45 | description: 'In Poland during World War II, Oskar Schindler gradually becomes concerned for his Jewish workforce after witnessing ' + 46 | 'their persecution by the Nazis.' 47 | }, 48 | { 49 | title: '12 Angry Men', 50 | year: 1957, 51 | rating: 8.9, 52 | director: 'Sidney Lumet', 53 | description: 'A dissenting juror in a murder trial slowly manages to convince the others that the case is not as obviously clear ' + 54 | 'as it seemed in court.' 55 | }, 56 | { 57 | title: 'The Good, the Bad and the Ugly', 58 | year: 1966, 59 | rating: 8.9, 60 | director: 'Sergio Leone', 61 | description: 'A bounty hunting scam joins two men in an uneasy alliance against a third in a race to find a fortune in gold buried ' + 62 | 'in a remote cemetery.' 63 | }, 64 | { 65 | title: 'The Lord of the Rings: The Return of the King', 66 | year: 2003, 67 | rating: 8.9, 68 | director: 'Peter Jackson', 69 | description: 'Gandalf and Aragorn lead the World of Men against Sauron\'s army to draw his gaze from Frodo and Sam as they approach' + 70 | ' Mount Doom with the One Ring.' 71 | }, 72 | { 73 | title: 'Fight Club', 74 | year: 1999, 75 | rating: 8.8, 76 | director: 'David Fincher', 77 | description: 'An insomniac office worker looking for a way to change his life crosses paths with a devil-may-care soap maker and ' + 78 | 'they form an underground fight club that evolves into something much, much more...' 79 | }, 80 | { 81 | title: 'The Lord of the Rings: The Fellowship of the Ring', 82 | year: 2001, 83 | rating: 8.8, 84 | director: 'Peter Jackson', 85 | description: 'A meek hobbit of the Shire and eight companions set out on a journey to Mount Doom to destroy the One Ring and the' + 86 | ' dark lord Sauron.' 87 | }, 88 | { 89 | title: 'Star Wars: Episode V - The Empire Strikes Back', 90 | year: 1980, 91 | rating: 8.7, 92 | director: 'Irvin Kershner', 93 | description: 'After the rebels have been brutally overpowered by the Empire on their newly established base, Luke Skywalker takes ' + 94 | 'advanced Jedi training with Master Yoda, while his friends are pursued by Darth Vader as part of his plan to capture Luke.' 95 | }, 96 | { 97 | title: 'Forest Gump', 98 | year: 1994, 99 | rating: 8.7, 100 | director: 'Stephen Spielberg', 101 | description: 'Forrest Gump, while not intelligent, has accidentally been present at many historic moments, but his true love, ' + 102 | 'Jenny Curran, eludes him.' 103 | }, 104 | { 105 | title: 'Inception', 106 | year: 2010, 107 | rating: 8.7, 108 | director: 'Christopher Nolan', 109 | description: 'A thief who steals corporate secrets through use of dream-sharing technology is given the inverse task of planting an ' + 110 | 'idea into the mind of a CEO.' 111 | } 112 | ]; 113 | -------------------------------------------------------------------------------- /libs/datatable/src/components/table/table.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 18 | 21 | 29 | 69 | 70 | 71 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 83 | 84 | 85 | 86 | 88 | 89 |
17 | 19 | 20 | 22 | 28 | 36 | 54 | 55 | 57 | 59 | 60 | 62 | 64 | 65 | 67 | 68 |
{{ noDataMessage }}
  87 |
90 |
91 | 92 |
93 |
94 | 95 | 96 |
97 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo4/data-table-demo4-data.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | 'name': 'Aaron 2Moore', 4 | 'phoneNumber': '611-898-6201' 5 | }, 6 | { 7 | 'name': 'Yvonne Conroy Mrs.', 8 | 'phoneNumber': '115-850-0969' 9 | }, 10 | { 11 | 'name': 'Laron Padberg', 12 | 'phoneNumber': '632-654-3034' 13 | }, 14 | { 15 | 'name': 'Dr. Maryam Spinka', 16 | 'phoneNumber': '547-345-0067' 17 | }, 18 | { 19 | 'name': 'Kiley Baumbach', 20 | 'phoneNumber': '958-524-5164' 21 | }, 22 | { 23 | 'name': 'Hollis MacGyver', 24 | 'phoneNumber': '603-607-3241' 25 | }, 26 | { 27 | 'name': 'Axel McLaughlin', 28 | 'phoneNumber': '983-639-0705' 29 | }, 30 | { 31 | 'name': 'Ricardo Botsford', 32 | 'phoneNumber': '408-082-9480' 33 | }, 34 | { 35 | 'name': 'Corbin Funk Mrs.', 36 | 'phoneNumber': '386-937-8683' 37 | }, 38 | { 39 | 'name': 'Rosalind Paucek', 40 | 'phoneNumber': '977-661-7403' 41 | }, 42 | { 43 | 'name': 'Henderson Moore', 44 | 'phoneNumber': '078-101-6377' 45 | }, 46 | { 47 | 'name': 'Kelli Schoen', 48 | 'phoneNumber': '654-591-6561' 49 | }, 50 | { 51 | 'name': 'Kenna Fritsch', 52 | 'phoneNumber': '790-480-2859' 53 | }, 54 | { 55 | 'name': 'Judge Marquardt', 56 | 'phoneNumber': '100-494-1787' 57 | }, 58 | { 59 | 'name': 'Kurtis Hane', 60 | 'phoneNumber': '008-800-2959' 61 | }, 62 | { 63 | 'name': 'Nicolette Lind', 64 | 'phoneNumber': '007-908-2460' 65 | }, 66 | { 67 | 'name': 'Idella Green', 68 | 'phoneNumber': '147-865-1578' 69 | }, 70 | { 71 | 'name': 'Mackenzie Bartell', 72 | 'phoneNumber': '235-649-0980' 73 | }, 74 | { 75 | 'name': 'Mose Kohler', 76 | 'phoneNumber': '614-886-4868' 77 | }, 78 | { 79 | 'name': 'Cielo Kuphal', 80 | 'phoneNumber': '590-976-7492' 81 | }, 82 | { 83 | 'name': 'Haleigh Stokes', 84 | 'phoneNumber': '418-255-9365' 85 | }, 86 | { 87 | 'name': 'Tyrese Walter', 88 | 'phoneNumber': '041-555-9831' 89 | }, 90 | { 91 | 'name': 'Barney Shields', 92 | 'phoneNumber': '379-438-0217' 93 | }, 94 | { 95 | 'name': 'Favian Abbott Miss', 96 | 'phoneNumber': '642-808-5400' 97 | }, 98 | { 99 | 'name': 'Carissa Kunze', 100 | 'phoneNumber': '949-983-0342' 101 | }, 102 | { 103 | 'name': 'Aaron 2Moore', 104 | 'phoneNumber': '611-898-6201' 105 | }, 106 | { 107 | 'name': 'Yvonne Conroy Mrs.', 108 | 'phoneNumber': '115-850-0969' 109 | }, 110 | { 111 | 'name': 'Laron Padberg', 112 | 'phoneNumber': '632-654-3034' 113 | }, 114 | { 115 | 'name': 'Dr. Maryam Spinka', 116 | 'phoneNumber': '547-345-0067' 117 | }, 118 | { 119 | 'name': 'Kiley Baumbach', 120 | 'phoneNumber': '958-524-5164' 121 | }, 122 | { 123 | 'name': 'Hollis MacGyver', 124 | 'phoneNumber': '603-607-3241' 125 | }, 126 | { 127 | 'name': 'Axel McLaughlin', 128 | 'phoneNumber': '983-639-0705' 129 | }, 130 | { 131 | 'name': 'Ricardo Botsford', 132 | 'phoneNumber': '408-082-9480' 133 | }, 134 | { 135 | 'name': 'Corbin Funk Mrs.', 136 | 'phoneNumber': '386-937-8683' 137 | }, 138 | { 139 | 'name': 'Rosalind Paucek', 140 | 'phoneNumber': '977-661-7403' 141 | }, 142 | { 143 | 'name': 'Henderson Moore', 144 | 'phoneNumber': '078-101-6377' 145 | }, 146 | { 147 | 'name': 'Kelli Schoen', 148 | 'phoneNumber': '654-591-6561' 149 | }, 150 | { 151 | 'name': 'Kenna Fritsch', 152 | 'phoneNumber': '790-480-2859' 153 | }, 154 | { 155 | 'name': 'Judge Marquardt', 156 | 'phoneNumber': '100-494-1787' 157 | }, 158 | { 159 | 'name': 'Kurtis Hane', 160 | 'phoneNumber': '008-800-2959' 161 | }, 162 | { 163 | 'name': 'Nicolette Lind', 164 | 'phoneNumber': '007-908-2460' 165 | }, 166 | { 167 | 'name': 'Idella Green', 168 | 'phoneNumber': '147-865-1578' 169 | }, 170 | { 171 | 'name': 'Mackenzie Bartell', 172 | 'phoneNumber': '235-649-0980' 173 | }, 174 | { 175 | 'name': 'Mose Kohler', 176 | 'phoneNumber': '614-886-4868' 177 | }, 178 | { 179 | 'name': 'Cielo Kuphal', 180 | 'phoneNumber': '590-976-7492' 181 | }, 182 | { 183 | 'name': 'Haleigh Stokes', 184 | 'phoneNumber': '418-255-9365' 185 | }, 186 | { 187 | 'name': 'Tyrese Walter', 188 | 'phoneNumber': '041-555-9831' 189 | }, 190 | { 191 | 'name': 'Barney Shields', 192 | 'phoneNumber': '379-438-0217' 193 | }, 194 | { 195 | 'name': 'Favian Abbott Miss', 196 | 'phoneNumber': '642-808-5400' 197 | }, 198 | { 199 | 'name': 'Carissa Kunze', 200 | 'phoneNumber': '949-983-0342' 201 | }, 202 | { 203 | 'name': 'Aaron 2Moore', 204 | 'phoneNumber': '611-898-6201' 205 | }, 206 | { 207 | 'name': 'Yvonne Conroy Mrs.', 208 | 'phoneNumber': '115-850-0969' 209 | }, 210 | { 211 | 'name': 'Laron Padberg', 212 | 'phoneNumber': '632-654-3034' 213 | }, 214 | { 215 | 'name': 'Dr. Maryam Spinka', 216 | 'phoneNumber': '547-345-0067' 217 | }, 218 | { 219 | 'name': 'Kiley Baumbach', 220 | 'phoneNumber': '958-524-5164' 221 | }, 222 | { 223 | 'name': 'Hollis MacGyver', 224 | 'phoneNumber': '603-607-3241' 225 | }, 226 | { 227 | 'name': 'Axel McLaughlin', 228 | 'phoneNumber': '983-639-0705' 229 | }, 230 | { 231 | 'name': 'Ricardo Botsford', 232 | 'phoneNumber': '408-082-9480' 233 | }, 234 | { 235 | 'name': 'Corbin Funk Mrs.', 236 | 'phoneNumber': '386-937-8683' 237 | }, 238 | { 239 | 'name': 'Rosalind Paucek', 240 | 'phoneNumber': '977-661-7403' 241 | }, 242 | { 243 | 'name': 'Henderson Moore', 244 | 'phoneNumber': '078-101-6377' 245 | }, 246 | { 247 | 'name': 'Kelli Schoen', 248 | 'phoneNumber': '654-591-6561' 249 | }, 250 | { 251 | 'name': 'Kenna Fritsch', 252 | 'phoneNumber': '790-480-2859' 253 | }, 254 | { 255 | 'name': 'Judge Marquardt', 256 | 'phoneNumber': '100-494-1787' 257 | }, 258 | { 259 | 'name': 'Kurtis Hane', 260 | 'phoneNumber': '008-800-2959' 261 | }, 262 | { 263 | 'name': 'Nicolette Lind', 264 | 'phoneNumber': '007-908-2460' 265 | }, 266 | { 267 | 'name': 'Idella Green', 268 | 'phoneNumber': '147-865-1578' 269 | }, 270 | { 271 | 'name': 'Mackenzie Bartell', 272 | 'phoneNumber': '235-649-0980' 273 | }, 274 | { 275 | 'name': 'Mose Kohler', 276 | 'phoneNumber': '614-886-4868' 277 | }, 278 | { 279 | 'name': 'Cielo Kuphal', 280 | 'phoneNumber': '590-976-7492' 281 | }, 282 | { 283 | 'name': 'Haleigh Stokes', 284 | 'phoneNumber': '418-255-9365' 285 | }, 286 | { 287 | 'name': 'Tyrese Walter', 288 | 'phoneNumber': '041-555-9831' 289 | }, 290 | { 291 | 'name': 'Barney Shields', 292 | 'phoneNumber': '379-438-0217' 293 | }, 294 | { 295 | 'name': 'Favian Abbott Miss', 296 | 'phoneNumber': '642-808-5400' 297 | }, 298 | { 299 | 'name': 'Carissa Kunze', 300 | 'phoneNumber': '949-983-0342' 301 | }, 302 | { 303 | 'name': 'Favian Abbott Miss', 304 | 'phoneNumber': '642-808-5400' 305 | }, 306 | { 307 | 'name': 'Carissa Kunze', 308 | 'phoneNumber': '949-983-0342' 309 | }, 310 | { 311 | 'name': 'Favian Abbott Miss', 312 | 'phoneNumber': '642-808-5400' 313 | }, 314 | { 315 | 'name': 'Carissa Kunze', 316 | 'phoneNumber': '949-983-0342' 317 | } 318 | ]; 319 | -------------------------------------------------------------------------------- /libs/datatable/README.md: -------------------------------------------------------------------------------- 1 | # @angular5/datatable 2 | This library contains a datatable component with built-in solutions for features including: 3 | - pagination 4 | - sorting 5 | - row selection (single/multi) 6 | - expandable rows 7 | - column resizing 8 | - selecting visible columns 9 | - accessibility support 10 | --- 11 | # Dependencies 12 | Furthermore the component is based on Bootstrap v4.0 (*CSS-only*) and Font-Awesome v4.7, hence be sure to include them into your project. 13 | Most likely you need to install them as dependencies... 14 | 15 | ```bash 16 | npm install bootstrap@4.0.0 font-awesome@4.7.0 17 | ``` 18 | 19 | ... then you need to include the CSS bundles into .angular-cli.json file as show below. 20 | ```json 21 | { 22 | "apps": [ 23 | { 24 | "root": "src", 25 | "styles": [ 26 | "../node_modules/bootstrap/dist/css/bootstrap.min.css", 27 | "../node_modules/font-awesome/css/font-awesome.css", 28 | "styles.css" 29 | ] 30 | } 31 | ] 32 | } 33 | ``` 34 | 35 | --- 36 | # Installation 37 | ```bash 38 | npm install angular5-data-table 39 | ``` 40 | --- 41 | # Usage 42 | #### 1.Import Datatable module 43 | ```ts 44 | import { BrowserModule } from '@angular/platform-browser'; 45 | import { NgModule } from '@angular/core'; 46 | import { DataTableModule } from 'angular5-data-table'; 47 | 48 | @NgModule({ 49 | imports: [ 50 | ... 51 | DataTableModule.forRoot() 52 | ... 53 | ], 54 | bootstrap: [AppComponent] 55 | }) 56 | export class AppModule { } 57 | ``` 58 | 59 | 60 | #### 2. Include `` and `` into your component's template. 61 | ```HTML 62 | 63 |
64 | ... 65 | 72 | 76 | 77 | 80 | 81 | ... 82 |
83 | ``` 84 | --- 85 | # API 86 | The component is highly configurable and customizable through many *Input*s and *Event*s. 87 | 88 | ## data-table 89 | * `title` (`string` | default: `''`) table's name - it's highly recommend it's set for accessibility reasons as this will provide a better experience when interacting with the component, especially through a SR. 90 | * `showTitle` (`boolean` | default: `true`): if `false`, the title is not shown into the component. Useful when want the header component visible (with its Reload and Coulumn Selector buttons), but not the title. 91 | * `items` (`JsonObject[]` | default: `[]`) table data to show. 92 | * `itemCount` (`number` | default: `0`) items's count. 93 | * `header` (`boolean` | default: `true`) show/hide the table header sub-component - this holds the table name and two buttons (_reload table_ and _column selector_). 94 | * `pagination` (`boolean` | default: ) enable pagination. If `true`, pagination controls are shown at the bottom of the table. 95 | * `indexColumn` (`boolean` | default: `true`) when `true` the table shows a 0-indexed column. 96 | * `indexColumnHeader` (`string` | default: `''`) text shown as column header for the index column. 97 | * `selectColumn` (`boolean` | default: `false`) when `true` the table shows a checkbox column for selecting specific row. 98 | * `multiSelect` (`boolean` | default: `false`) allows multi-row selection, showing a checkbox at select's column header. 99 | * `labels` (`DataTableTranslations` | default: `defaultTranslations`) interface holding all needed labels. You can pass a subset of the labels. The missing labels will be defaulted. 100 | * `expandableRow` (`boolean` | default: `false`) when `true` each row will have a collapsible content. 101 | * `selectOnRowClick` (`boolean` | default: `false`) when `true` each row is selectable via a single-click. 102 | * `reload` (`function(): void` | default: `null`) function that is invoked when the table needs to re-render its data. Note: most of the times this is the place where the developer connects to a server in order to pull down the item set. 103 | * `autoReload` (`boolean` | default: `false`) when `true`, the `reload` function gets invoked and init time (`ngOnInit`). 104 | * `rowColors` (`function(): 'color` | default: `null`) custom function that must return a _CSS color_ that will be applied to the entire row. 105 | * `rowTooltip` (`function` | default: `null`) custom function to show a title tooltip when hovering the row. 106 | * `showReloading` (`boolean` | default: `true`) when `true` an overlay with a gear icon is shown on top of the table while it's reloading. 107 | * `noDataMessage` (`string` | default: `''`) message displayed when no item are displayed. If it's empty nothing is shown. 108 | * `pageLimits` (`number[]` | default: `[10, 25, 50, 100, 250]`) items per page selector options. 109 | * `primaryColumn` (`string` | default: first data column) it identifies which columns has be marked as primary. This is an important aspect from an accessibility and SR perspective. If not given, the first column will be the primary column. 110 | * `page` (`number` | default: `0`) page to load, valid only if pagination is enabled. 111 | * `limit` (`number` | default: `10`) number of items per page, valid only pagination is enabled. If `limit` value is not a valid (not contained into `pageLimits` array) it will be defaulted to `pageLimits`'s first value. 112 | * `sortBy` (`string` | default: `''`) column table is sorted by. 113 | * `sortAsc` (`boolean` | default: `true`) valid only if `sortBy` is not defaulted. Defines the sorting order. If `true` sort is ascending, descending otherwise. 114 | 115 | ## data-table-column 116 | * `property` (`string` | default: _no default_) item's `JSONObject` key used to retrieve the row cell content. 117 | * `header` (`string` | default: _no default_) column header text. 118 | * `sortable` (`boolean` | default: `false`) marks the columns as sortable. 119 | * `resizable` (`boolean` | default: `false`) marks the columns as resizable. 120 | * `visible` (`boolean` | default: `true`) marks the columns visible. 121 | * `width` (`number | string` | default: `''`) defines the column width. It can be a string like `2rem` or a number. If it's a number, it will be considered as pixels. 122 | 123 | ### Custom column templates 124 | data-column's content and header are not restricted to be text only. They can hold more complex components. In order to do that developers can use two references: `#dataTableHeader` and `#dataTableCell`. 125 | 126 | #### Usage and sample 127 | ```html 128 |
129 | ... 130 | 132 | 133 | Actions 134 | 135 | 136 | 137 | 138 | 139 | ... 140 | 141 | ``` 142 | As it can be seen from the above snippet, the `dataTableHeader` and `dataTableCell` are targeting two ``s nodes which will be used respectively as column header and cell content. In both cases `item` refers to the whole row item, so developers can use whatever they may need. 143 | 144 | --- 145 | # Accessibility 146 | The library is fully tested with NVDA and Mozilla Firefox. Other browsers or screen readers combination may be supported too. 147 | If developers use `dataTableHeader` and `dataTableCell` for getting displayed custom text, accessibility and SR support go out from the datatable component support range. That means that, although this component is *fully* accessible, in scenarios where column custom templates are used some accessibility checks may fail. It's hence developer's responsibility to fix any potential accessibility issue. See the demo's example for more info. 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular 5 DataTable 2 | 3 | *Note*: Originally this was fork of this [package](https://github.com/MIt9/angular-4-data-table). 4 | 5 | A simple Angular 5 data table, with built-in solutions for features including: 6 | * pagination 7 | * sorting 8 | * row selection (single/multi) 9 | * expandable rows 10 | * column resizing 11 | * selecting visible columns 12 | * accessibility 13 | 14 | The component can be used not just with local data, but remote resources too, ie. when sorting and/or pagination are implemented server side. 15 | 16 | The library is packaged with [`ng-packagr`](https://github.com/dherges/ng-packagr). 17 | 18 | ## Dependencies 19 | Furthermore the component is based on Bootstrap v4.0 (*CSS-only*) and Font-Awesome v4.7, hence be sure to include them into your project. 20 | Most likely you need to install them as dependencies... 21 | 22 | ```bash 23 | npm install bootstrap@4.0.0 font-awesome@4.7.0 24 | ``` 25 | 26 | ... then you need to include the CSS bundles into .angular-cli.json file as show below. 27 | ```json 28 | { 29 | "apps": [ 30 | { 31 | "root": "src", 32 | "styles": [ 33 | "../node_modules/bootstrap/dist/css/bootstrap.min.css", 34 | "../node_modules/font-awesome/css/font-awesome.css", 35 | "styles.css" 36 | ] 37 | } 38 | ] 39 | } 40 | ``` 41 | 42 | ## Installation 43 | 44 | ```bash 45 | npm install angular5-data-table 46 | ``` 47 | 48 | ## Usage 49 | 50 | ##### 1.Import Datatable module 51 | ```ts 52 | import { BrowserModule } from '@angular/platform-browser'; 53 | import { NgModule } from '@angular/core'; 54 | import { DataTableModule } from 'angular5-data-table'; 55 | 56 | @NgModule({ 57 | imports: [ 58 | ... 59 | DataTableModule.forRoot() 60 | ... 61 | ], 62 | bootstrap: [AppComponent] 63 | }) 64 | export class AppModule { } 65 | ``` 66 | 67 | ##### 2. Include `` and `` into your component's template. 68 | ```HTML 69 | 70 |
71 | ... 72 | 79 | 83 | 84 | 87 | 88 | ... 89 |
90 | ``` 91 | 92 | ## API 93 | 94 | ### data-table 95 | * `title` (`string` | default: `''`) table's name - it's highly recommend it's set for accessibility reasons as this will provide a better experience when interacting with the component, especially through a SR. 96 | * `showTitle` (`boolean` | default: `true`): if `false`, the title is not shown into the component. Useful when want the header component visible (with its Reload and Coulumn Selector buttons), but not the title. 97 | * `items` (`JsonObject[]` | default: `[]`) table data to show. 98 | * `itemCount` (`number` | default: `0`) items's count. 99 | * `header` (`boolean` | default: `true`) show/hide the table header sub-component - this holds the table name and two buttons (_reload table_ and _column selector_). 100 | * `pagination` (`boolean` | default: ) enable pagination. If `true`, pagination controls are shown at the bottom of the table. 101 | * `indexColumn` (`boolean` | default: `true`) when `true` the table shows a 0-indexed column. 102 | * `indexColumnHeader` (`string` | default: `''`) text shown as column header for the index column. 103 | * `selectColumn` (`boolean` | default: `false`) when `true` the table shows a checkbox column for selecting specific row. 104 | * `multiSelect` (`boolean` | default: `false`) allows multi-row selection, showing a checkbox at select's column header. 105 | * `labels` (`DataTableTranslations` | default: `defaultTranslations`) interface holding all needed labels. You can pass a subset of the labels. The missing labels will be defaulted. 106 | * `expandableRow` (`boolean` | default: `false`) when `true` each row will have a collapsible content. 107 | * `selectOnRowClick` (`boolean` | default: `false`) when `true` each row is selectable via a single-click. 108 | * `reload` (`function(): void` | default: `null`) function that is invoked when the table needs to re-render its data. Note: most of the times this is the place where the developer connects to a server in order to pull down the item set. 109 | * `autoReload` (`boolean` | default: `false`) when `true`, the `reload` function gets invoked and init time (`ngOnInit`). 110 | * `rowColors` (`function(): 'color` | default: `null`) custom function that must return a _CSS color_ that will be applied to the entire row. 111 | * `rowTooltip` (`function` | default: `null`) custom function to show a title tooltip when hovering the row. 112 | * `showReloading` (`boolean` | default: `true`) when `true` an overlay with a gear icon is shown on top of the table while it's reloading. 113 | * `noDataMessage` (`string` | default: `''`) message displayed when no item are displayed. If it's empty nothing is shown. 114 | * `pageLimits` (`number[]` | default: `[10, 25, 50, 100, 250]`) items per page selector options. 115 | * `primaryColumn` (`string` | default: first data column) it identifies which columns has be marked as primary. This is an important aspect from an accessibility and SR perspective. If not given, the first column will be the primary column. 116 | * `page` (`number` | default: `0`) page to load, valid only if pagination is enabled. 117 | * `limit` (`number` | default: `10`) number of items per page, valid only pagination is enabled. If `limit` value is not a valid (not contained into `pageLimits` array) it will be defaulted to `pageLimits`'s first value. 118 | * `sortBy` (`string` | default: `''`) column table is sorted by. 119 | * `sortAsc` (`boolean` | default: `true`) valid only if `sortBy` is not defaulted. Defines the sorting order. If `true` sort is ascending, descending otherwise. 120 | 121 | ### data-table-column 122 | * `property` (`string` | default: _no default_) item's `JSONObject` key used to retrieve the row cell content. 123 | * `header` (`string` | default: _no default_) column header text. 124 | * `sortable` (`boolean` | default: `false`) marks the columns as sortable. 125 | * `resizable` (`boolean` | default: `false`) marks the columns as resizable. 126 | * `visible` (`boolean` | default: `true`) marks the columns visible. 127 | * `width` (`number | string` | default: `''`) defines the column width. It can be a string like `2rem` or a number. If it's a number, it will be considered as pixels. 128 | 129 | ### Custom column templates 130 | data-column's content and header are not restricted to be text only - they can hold complex content too. In order to do that developers can use two references: `#dataTableHeader` and `#dataTableCell`. 131 | 132 | # Usage and sample 133 | ```html 134 |
135 | ... 136 | 138 | 139 | Actions 140 | 141 | 142 | 143 | 144 | 145 | ... 146 | 147 | ``` 148 | As it can be seen from the above snippet, the `dataTableHeader` and `dataTableCell` are targeting two ``s nodes which will be used respectively as column header and cell content. In both cases `item` refers to the whole row item, so developers can use whatever they may need. 149 | 150 | ## Demo app 151 | Clone this repository, run `npm install` and `ng serve`, then navigate to `http:localhost:4200` where you can access to the demo application sporting a few demos with code viewer and docs. 152 | 153 | ##Examples 154 | ![Demo1](https://user-images.githubusercontent.com/4410485/39678176-ce90ecc6-517f-11e8-82fd-ec85162ccf80.png) 155 | ![Demo2](https://user-images.githubusercontent.com/4410485/39678189-fcd6a080-517f-11e8-8cca-6dd5395703b0.png) 156 | ![Demo3](https://user-images.githubusercontent.com/4410485/39678197-1513fed6-5180-11e8-8ad6-64c08c9689fa.png) 157 | ![Demo4](https://user-images.githubusercontent.com/4410485/39678201-29df8740-5180-11e8-80f5-68249311aa20.png) 158 | ![Demo5](https://user-images.githubusercontent.com/4410485/39678204-425001f6-5180-11e8-8b7d-1decacd406a6.png) 159 | 160 | ## License 161 | MIT License 162 | -------------------------------------------------------------------------------- /libs/datatable/src/components/table/table.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AfterContentInit, 3 | Component, 4 | ContentChild, 5 | ContentChildren, 6 | EventEmitter, 7 | Input, 8 | OnDestroy, 9 | OnInit, 10 | Output, 11 | QueryList, 12 | TemplateRef, 13 | ViewChildren 14 | } from '@angular/core'; 15 | import { DataTableColumnDirective } from '../../directives/column/column.directive'; 16 | import { DataTableRowComponent } from '../row/row.component'; 17 | import { DataTableParams } from '../../types/data-table-params.type'; 18 | import { RowCallback } from '../../types/row-callback.type'; 19 | import { DataTableTranslations } from '../../types/data-table-translations.type'; 20 | import { defaultTranslations } from '../../types/default-translations.type'; 21 | import { drag } from '../../utils/drag'; 22 | 23 | import { Subject } from 'rxjs/Subject'; 24 | import { Subscription } from 'rxjs/Subscription'; 25 | import 'rxjs/add/operator/debounceTime'; 26 | import 'rxjs/add/operator/do'; 27 | 28 | let nextId = 0; 29 | 30 | @Component({ 31 | selector: 'data-table', 32 | templateUrl: './table.component.html', 33 | styleUrls: ['./table.component.css'] 34 | }) 35 | export class DataTableComponent implements DataTableParams, OnInit, AfterContentInit, OnDestroy { 36 | 37 | private _items: any[] = []; 38 | private _itemCount; 39 | 40 | @Input() 41 | get items(): any[] { 42 | return this._items; 43 | } 44 | 45 | set items(items: any[]) { 46 | this._items = items; 47 | // no need to call notifier.next() because _onReloadFinished() 48 | // will change reloaded value causing notifier.next() to be called implicitly. 49 | this._onReloadFinished(); 50 | } 51 | 52 | 53 | @Input() 54 | get itemCount(): number { 55 | return this._itemCount; 56 | } 57 | 58 | set itemCount(count: number) { 59 | this._itemCount = count; 60 | this.notifier.next(); 61 | } 62 | 63 | // UI components: 64 | @ContentChildren(DataTableColumnDirective) columns: QueryList; 65 | @ViewChildren(DataTableRowComponent) rows: QueryList; 66 | @ContentChild('dataTableExpand') expandTemplate: TemplateRef; 67 | 68 | // One-time optional bindings with default values: 69 | @Input() title = ''; 70 | @Input() showTitle = true; 71 | @Input() header = true; 72 | @Input() pagination = true; 73 | @Input() indexColumn = true; 74 | @Input() indexColumnHeader = ''; 75 | @Input() rowColors: RowCallback; 76 | @Input() rowTooltip: RowCallback; 77 | @Input() selectColumn = false; 78 | @Input() multiSelect = true; 79 | @Input() substituteRows = true; 80 | @Input() expandableRows = false; 81 | @Input() labels: DataTableTranslations; 82 | @Input() selectOnRowClick = false; 83 | @Input() autoReload = true; 84 | @Input() showReloading = false; 85 | @Input() noDataMessage: string; 86 | @Input() pageLimits: number[] = [10, 25, 50, 100, 250]; 87 | @Input() primaryColumn = ''; 88 | 89 | // reload emitter 90 | @Output() reload = new EventEmitter(); 91 | 92 | // event handlers: 93 | @Output() rowClick = new EventEmitter(); 94 | @Output() rowDoubleClick = new EventEmitter(); 95 | @Output() headerClick = new EventEmitter(); 96 | @Output() cellClick = new EventEmitter(); 97 | // UI state without input: 98 | indexColumnVisible: boolean; 99 | selectColumnVisible: boolean; 100 | expandColumnVisible: boolean; 101 | 102 | // ada notifications. 103 | reloadNotification: string; 104 | paginationNotification: string; 105 | sortNotification: string; 106 | columnSelectorNotification: string; 107 | 108 | _displayParams = {}; // params of the last finished reload 109 | 110 | subject = new Subject(); 111 | subject$: Subscription; 112 | 113 | notifier = new Subject(); 114 | notifier$: Subscription; 115 | 116 | // selection: 117 | selectedRow: DataTableRowComponent; 118 | selectedRows: DataTableRowComponent[] = []; 119 | 120 | Math: any; 121 | id = `datatable-${nextId++}`; 122 | 123 | // select all checkbox flag 124 | private _selectAllCheckbox = false; 125 | 126 | // column resizing: 127 | private _resizeInProgress = false; 128 | 129 | resizeLimit = 30; 130 | 131 | // Reloading: 132 | _reloading = false; 133 | 134 | get reloading() { 135 | return this._reloading; 136 | } 137 | 138 | set reloading(val: boolean) { 139 | this._reloading = val; 140 | this.notifier.next(); 141 | } 142 | 143 | // UI state: visible get/set for the outside with @Input for one-time initial values 144 | private _sortBy: string; 145 | 146 | @Input() 147 | get sortBy(): string { 148 | return this._sortBy; 149 | } 150 | 151 | set sortBy(value: string) { 152 | this._sortBy = value; 153 | this.subject.next(); 154 | } 155 | 156 | private _sortAsc = true; 157 | 158 | @Input() 159 | get sortAsc(): boolean { 160 | return this._sortAsc; 161 | } 162 | 163 | set sortAsc(value: boolean) { 164 | this._sortAsc = value; 165 | this.subject.next(); 166 | } 167 | 168 | private _offset = 0; 169 | 170 | @Input() 171 | get offset(): number { 172 | return this._offset; 173 | } 174 | 175 | set offset(value: number) { 176 | this._offset = value; 177 | this.subject.next(); 178 | } 179 | 180 | private _limit = 10; 181 | 182 | @Input() 183 | get limit(): number { 184 | return this._limit; 185 | } 186 | 187 | set limit(value: number) { 188 | this._limit = value; 189 | this.subject.next(); 190 | } 191 | 192 | // calculated property: 193 | @Input() 194 | get page() { 195 | return this.itemCount !== 0 ? Math.floor(this.offset / this.limit) + 1 : 0; 196 | } 197 | 198 | set page(value) { 199 | this.offset = (value - 1) * this.limit; 200 | } 201 | 202 | get lastPage() { 203 | return Math.ceil(this.itemCount / this.limit); 204 | } 205 | 206 | // setting multiple observable properties simultaneously 207 | sort(sortBy: string, asc: boolean) { 208 | this.sortBy = sortBy; 209 | this.sortAsc = asc; 210 | } 211 | 212 | // init 213 | ngOnInit() { 214 | this._initDefaultValues(); 215 | this._initDefaultClickEvents(); 216 | this._updateDisplayParams(); 217 | 218 | if (this.pageLimits.indexOf(this.limit) < 0) { 219 | this.limit = this.pageLimits[0]; 220 | } 221 | 222 | this.labels = {...defaultTranslations, ...this.labels}; 223 | 224 | if (this.autoReload) { 225 | this.reloadItems(); 226 | } 227 | 228 | this.notifier$ = this.notifier.subscribe(() => this._notify()); 229 | this.subject$ = this.subject.debounceTime(100).subscribe(() => this.reloadItems()); 230 | 231 | } 232 | 233 | private _initDefaultValues() { 234 | this.indexColumnVisible = this.indexColumn; 235 | this.selectColumnVisible = this.selectColumn; 236 | this.expandColumnVisible = this.expandableRows; 237 | } 238 | 239 | private _initDefaultClickEvents() { 240 | this.headerClick.subscribe( 241 | (tableEvent: { column: DataTableColumnDirective, event: Event }) => this.sortColumn(tableEvent.column)); 242 | if (this.selectOnRowClick) { 243 | this.rowClick.subscribe( 244 | (tableEvent: { row: DataTableRowComponent, event: Event }) => tableEvent.row.selected = !tableEvent.row.selected); 245 | } 246 | } 247 | 248 | reloadItems() { 249 | this.reloading = true; 250 | this.reload.emit(this._getRemoteParameters()); 251 | } 252 | 253 | private _onReloadFinished() { 254 | if (this.reloading) { 255 | this._updateDisplayParams(); 256 | this._selectAllCheckbox = false; 257 | this.reloading = false; 258 | } 259 | } 260 | get displayParams() { 261 | return this._displayParams; 262 | } 263 | 264 | _updateDisplayParams() { 265 | this._displayParams = { 266 | sortBy: this.sortBy, 267 | sortAsc: this.sortAsc, 268 | offset: this.offset, 269 | limit: this.limit 270 | }; 271 | } 272 | 273 | constructor() { } 274 | 275 | public rowClicked(row: DataTableRowComponent, event: Event) { 276 | this.rowClick.emit({row, event}); 277 | } 278 | 279 | public rowDoubleClicked(row: DataTableRowComponent, event: Event) { 280 | this.rowDoubleClick.emit({row, event}); 281 | } 282 | 283 | public headerClicked(column: DataTableColumnDirective, event: Event) { 284 | if (!this._resizeInProgress) { 285 | event.preventDefault(); 286 | event.stopPropagation(); 287 | this.headerClick.emit({column, event}); 288 | } else { 289 | this._resizeInProgress = false; // this is because I can't prevent click from mousup of the drag end 290 | } 291 | } 292 | 293 | private cellClicked(column: DataTableColumnDirective, row: DataTableRowComponent, event: MouseEvent) { 294 | this.cellClick.emit({row, column, event}); 295 | } 296 | 297 | // functions: 298 | private _getRemoteParameters(): DataTableParams { 299 | const params = {}; 300 | 301 | if (this.sortBy) { 302 | params.sortBy = this.sortBy; 303 | params.sortAsc = this.sortAsc; 304 | } 305 | if (this.pagination) { 306 | params.offset = this.offset; 307 | params.limit = this.limit; 308 | } 309 | return params; 310 | } 311 | 312 | private sortColumn(column: DataTableColumnDirective) { 313 | if (column.sortable) { 314 | const ascending = this.sortBy === column.property ? !this.sortAsc : true; 315 | this.sort(column.property, ascending); 316 | } 317 | } 318 | 319 | get columnCount() { 320 | let count = 0; 321 | count += this.indexColumnVisible ? 1 : 0; 322 | count += this.selectColumnVisible ? 1 : 0; 323 | count += this.expandColumnVisible ? 1 : 0; 324 | this.columns.toArray().forEach(column => { 325 | count += column.visible ? 1 : 0; 326 | }); 327 | return count; 328 | } 329 | 330 | public getRowColor(item: any, index: number, row: DataTableRowComponent) { 331 | if (this.rowColors !== undefined) { 332 | return (this.rowColors)(item, row, index); 333 | } 334 | } 335 | 336 | get selectAllCheckbox() { 337 | return this._selectAllCheckbox; 338 | } 339 | 340 | set selectAllCheckbox(value) { 341 | this._selectAllCheckbox = value; 342 | this._onSelectAllChanged(value); 343 | } 344 | 345 | private _onSelectAllChanged(value: boolean) { 346 | this.rows.toArray().forEach(row => row.selected = value); 347 | } 348 | 349 | onRowSelectChanged(row: DataTableRowComponent) { 350 | 351 | // maintain the selectedRow(s) view 352 | if (this.multiSelect) { 353 | const index = this.selectedRows.indexOf(row); 354 | if (row.selected && index < 0) { 355 | this.selectedRows.push(row); 356 | } else if (!row.selected && index >= 0) { 357 | this.selectedRows.splice(index, 1); 358 | } 359 | } else { 360 | if (row.selected) { 361 | this.selectedRow = row; 362 | } else if (this.selectedRow === row) { 363 | delete this.selectedRow; 364 | } 365 | } 366 | 367 | // unselect all other rows: 368 | if (row.selected && !this.multiSelect) { 369 | this.rows.toArray().filter(row_ => row_.selected).forEach(row_ => { 370 | if (row_ !== row) { // avoid endless loop 371 | row_.selected = false; 372 | } 373 | }); 374 | } 375 | } 376 | 377 | // other: 378 | 379 | get substituteItems() { 380 | return Array.from({length: this.displayParams.limit - this.items.length}); 381 | } 382 | 383 | private resizeColumnStart(event: MouseEvent, column: DataTableColumnDirective, columnElement: HTMLElement) { 384 | this._resizeInProgress = true; 385 | let startOffset = columnElement.offsetWidth - event.pageX; 386 | drag(event, { 387 | move: (moveEvent: MouseEvent, dx: number) => { 388 | if (this._isResizeInLimit(columnElement, dx)) { 389 | column.width = startOffset + moveEvent.pageX + dx; 390 | } 391 | }, 392 | }); 393 | } 394 | 395 | private _isResizeInLimit(columnElement: HTMLElement, dx: number) { 396 | /* This is needed because CSS min-width didn't work on table-layout: fixed. 397 | Without the limits, resizing can make the next column disappear completely, 398 | and even increase the table width. The current implementation suffers from the fact, 399 | that offsetWidth sometimes contains out-of-date values. */ 400 | if ((dx < 0 && (columnElement.offsetWidth + dx) <= this.resizeLimit) || 401 | !columnElement.nextElementSibling || // resizing doesn't make sense for the last visible column 402 | (dx >= 0 && (( columnElement.nextElementSibling).offsetWidth + dx) <= this.resizeLimit)) { 403 | return false; 404 | } 405 | return true; 406 | } 407 | 408 | ngAfterContentInit(): void { 409 | if (this.primaryColumn === '') { 410 | this.primaryColumn = (this.columns.first as DataTableColumnDirective).property; 411 | } 412 | } 413 | 414 | _notify(): void { 415 | const loading = this.reloading; 416 | 417 | this.reloadNotification = loading ? 418 | this.labels.loadingText.replace('{title}', this.title) : 419 | this.labels.loadedText.replace('{title}', this.title); 420 | 421 | if (!loading) { 422 | if (this.pagination) { 423 | this.paginationNotification = this.labels.paginationText 424 | .replace('{from}', '' + (Math.ceil(this.itemCount / this.limit) !== 0 ? this.offset + 1 : '0')) 425 | .replace('{to}', '' + (Math.min(this.offset + this.limit, this.itemCount))) 426 | .replace('{total}', '' + this.itemCount); 427 | } else { 428 | this.paginationNotification = ''; 429 | } 430 | if (this.columns !== undefined && this.sortBy !== undefined) { 431 | const col = this.columns.toArray().find(column => column.property === this.sortBy) as DataTableColumnDirective; 432 | this.sortNotification = (this.sortAsc ? this.labels.sortedAscending : this.labels.sortedDescending) 433 | .replace('{title}', this.title) 434 | .replace('{header}', col.header); 435 | } else { 436 | this.sortNotification = ''; 437 | } 438 | } 439 | } 440 | 441 | ngOnDestroy() { 442 | this.subject$.unsubscribe(); 443 | this.notifier$.unsubscribe(); 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /src/app/components/datatable/demo1/data-table-demo1-data.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | 'name': 'Aaron 2Moore', 'email': 'Heath44@hotmail.com', 'jobTitle': 'Regional Configuration Producer', 4 | 'active': true, 'phoneNumber': '611-898-6201', 'date': '2015-11-06T07:21:25.510Z' 5 | }, 6 | { 7 | 'name': 'Yvonne Conroy Mrs.', 'email': 'Gideon9@yahoo.com', 'jobTitle': 'Global Mobility Orchestrator', 8 | 'active': false, 'phoneNumber': '115-850-0969', 'date': '2014-12-20T00:48:40.276Z' 9 | }, 10 | { 11 | 'name': 'Laron Padberg', 'email': 'Laney_Huels@hotmail.com', 'jobTitle': 'Senior Directives Supervisor', 12 | 'active': false, 'phoneNumber': '632-654-3034', 'date': '2015-09-29T04:33:38.544Z' 13 | }, 14 | { 15 | 'name': 'Dr. Maryam Spinka', 'email': 'Aletha.Labadie@hotmail.com', 'jobTitle': 'Dynamic Mobility Associate', 16 | 'active': true, 'phoneNumber': '547-345-0067', 'date': '2015-09-23T01:13:39.320Z' 17 | }, 18 | { 19 | 'name': 'Kiley Baumbach', 'email': 'Rogelio24@hotmail.com', 'jobTitle': 'Principal Metrics Orchestrator', 20 | 'active': true, 'phoneNumber': '958-524-5164', 'date': '2014-12-05T23:39:27.340Z' 21 | }, 22 | { 23 | 'name': 'Hollis MacGyver', 'email': 'Yazmin.Heidenreich97@gmail.com', 'jobTitle': 'Direct Markets Assistant', 24 | 'active': true, 'phoneNumber': '603-607-3241', 'date': '2015-02-12T10:40:52.977Z' 25 | }, 26 | { 27 | 'name': 'Axel McLaughlin', 'email': 'Deon_Heaney@gmail.com', 'jobTitle': 'Forward Mobility Architect', 28 | 'active': false, 'phoneNumber': '983-639-0705', 'date': '2015-03-01T02:28:26.030Z' 29 | }, 30 | { 31 | 'name': 'Ricardo Botsford', 'email': 'Melisa73@yahoo.com', 'jobTitle': 'Direct Quality Consultant', 32 | 'active': true, 'phoneNumber': '408-082-9480', 'date': '2015-01-31T03:41:54.611Z' 33 | }, 34 | { 35 | 'name': 'Corbin Funk Mrs.', 'email': 'Marjory.Morissette51@gmail.com', 'jobTitle': 'Human Configuration Manager', 36 | 'active': true, 'phoneNumber': '386-937-8683', 'date': '2014-12-05T15:07:36.843Z' 37 | }, 38 | { 39 | 'name': 'Rosalind Paucek', 'email': 'Ivy_Stanton@gmail.com', 'jobTitle': 'Future Creative Supervisor', 40 | 'active': true, 'phoneNumber': '977-661-7403', 'date': '2015-06-10T17:42:38.644Z' 41 | }, 42 | { 43 | 'name': 'Henderson Moore', 'email': 'Randi_Corkery@hotmail.com', 'jobTitle': 'Internal Accountability Director', 44 | 'active': true, 'phoneNumber': '078-101-6377', 'date': '2015-09-26T05:14:34.913Z' 45 | }, 46 | { 47 | 'name': 'Kelli Schoen', 'email': 'Reva.Kiehn54@yahoo.com', 'jobTitle': 'National Accountability Architect', 48 | 'active': false, 'phoneNumber': '654-591-6561', 'date': '2015-05-04T06:50:37.482Z' 49 | }, 50 | { 51 | 'name': 'Kenna Fritsch', 'email': 'Wilburn2@gmail.com', 'jobTitle': 'Legacy Response Administrator', 52 | 'active': true, 'phoneNumber': '790-480-2859', 'date': '2015-10-10T23:37:05.867Z' 53 | }, 54 | { 55 | 'name': 'Judge Marquardt', 'email': 'Letha_Champlin69@hotmail.com', 'jobTitle': 'Human Program Specialist', 56 | 'active': true, 'phoneNumber': '100-494-1787', 'date': '2015-04-04T23:29:48.588Z' 57 | }, 58 | { 59 | 'name': 'Kurtis Hane', 'email': 'Mona.Gaylord47@yahoo.com', 'jobTitle': 'International Optimization Director', 60 | 'active': false, 'phoneNumber': '008-800-2959', 'date': '2014-12-04T21:09:50.722Z' 61 | }, 62 | { 63 | 'name': 'Nicolette Lind', 'email': 'Thurman30@yahoo.com', 'jobTitle': 'Legacy Marketing Facilitator', 64 | 'active': true, 'phoneNumber': '007-908-2460', 'date': '2015-06-22T08:11:57.381Z' 65 | }, 66 | { 67 | 'name': 'Idella Green', 'email': 'Fernando_Ward@yahoo.com', 'jobTitle': 'Dynamic Division Orchestrator', 68 | 'active': false, 'phoneNumber': '147-865-1578', 'date': '2015-02-12T23:00:31.283Z' 69 | }, 70 | { 71 | 'name': 'Mackenzie Bartell', 'email': 'Price25@yahoo.com', 'jobTitle': 'National Directives Associate', 72 | 'active': false, 'phoneNumber': '235-649-0980', 'date': '2015-06-24T20:21:51.356Z' 73 | }, 74 | { 75 | 'name': 'Mose Kohler', 'email': 'Malika56@hotmail.com', 'jobTitle': 'Lead Implementation Executive', 76 | 'active': true, 'phoneNumber': '614-886-4868', 'date': '2015-03-04T13:05:23.698Z' 77 | }, 78 | { 79 | 'name': 'Cielo Kuphal', 'email': 'Jude_Terry24@gmail.com', 'jobTitle': 'Dynamic Division Analyst', 80 | 'active': false, 'phoneNumber': '590-976-7492', 'date': '2015-06-02T20:52:32.664Z' 81 | }, 82 | { 83 | 'name': 'Haleigh Stokes', 'email': 'Belle_Herman64@yahoo.com', 'jobTitle': 'Global Intranet Executive', 84 | 'active': false, 'phoneNumber': '418-255-9365', 'date': '2015-04-10T00:32:10.283Z' 85 | }, 86 | { 87 | 'name': 'Tyrese Walter', 'email': 'Garland.Veum52@hotmail.com', 'jobTitle': 'Senior Web Liason', 88 | 'active': false, 'phoneNumber': '041-555-9831', 'date': '2015-08-18T20:05:08.839Z' 89 | }, 90 | { 91 | 'name': 'Barney Shields', 'email': 'Anika27@gmail.com', 'jobTitle': 'District Web Administrator', 92 | 'active': true, 'phoneNumber': '379-438-0217', 'date': '2015-06-01T09:28:46.778Z' 93 | }, 94 | { 95 | 'name': 'Favian Abbott Miss', 'email': 'Palma_Little@hotmail.com', 'jobTitle': 'Lead Implementation Facilitator', 96 | 'active': false, 'phoneNumber': '642-808-5400', 'date': '2015-08-09T07:38:06.588Z' 97 | }, 98 | { 99 | 'name': 'Carissa Kunze', 'email': 'Merl_Frami@yahoo.com', 'jobTitle': 'Regional Division Technician', 100 | 'active': true, 'phoneNumber': '949-983-0342', 'date': '2015-11-05T08:09:09.463Z' 101 | }, 102 | { 103 | 'name': 'Aaron 2Moore', 'email': 'Heath44@hotmail.com', 'jobTitle': 'Regional Configuration Producer', 104 | 'active': true, 'phoneNumber': '611-898-6201', 'date': '2015-11-06T07:21:25.510Z' 105 | }, 106 | { 107 | 'name': 'Yvonne Conroy Mrs.', 'email': 'Gideon9@yahoo.com', 'jobTitle': 'Global Mobility Orchestrator', 108 | 'active': false, 'phoneNumber': '115-850-0969', 'date': '2014-12-20T00:48:40.276Z' 109 | }, 110 | { 111 | 'name': 'Laron Padberg', 'email': 'Laney_Huels@hotmail.com', 'jobTitle': 'Senior Directives Supervisor', 112 | 'active': false, 'phoneNumber': '632-654-3034', 'date': '2015-09-29T04:33:38.544Z' 113 | }, 114 | { 115 | 'name': 'Dr. Maryam Spinka', 'email': 'Aletha.Labadie@hotmail.com', 'jobTitle': 'Dynamic Mobility Associate', 116 | 'active': true, 'phoneNumber': '547-345-0067', 'date': '2015-09-23T01:13:39.320Z' 117 | }, 118 | { 119 | 'name': 'Kiley Baumbach', 'email': 'Rogelio24@hotmail.com', 'jobTitle': 'Principal Metrics Orchestrator', 120 | 'active': true, 'phoneNumber': '958-524-5164', 'date': '2014-12-05T23:39:27.340Z' 121 | }, 122 | { 123 | 'name': 'Hollis MacGyver', 'email': 'Yazmin.Heidenreich97@gmail.com', 'jobTitle': 'Direct Markets Assistant', 124 | 'active': true, 'phoneNumber': '603-607-3241', 'date': '2015-02-12T10:40:52.977Z' 125 | }, 126 | { 127 | 'name': 'Axel McLaughlin', 'email': 'Deon_Heaney@gmail.com', 'jobTitle': 'Forward Mobility Architect', 128 | 'active': false, 'phoneNumber': '983-639-0705', 'date': '2015-03-01T02:28:26.030Z' 129 | }, 130 | { 131 | 'name': 'Ricardo Botsford', 'email': 'Melisa73@yahoo.com', 'jobTitle': 'Direct Quality Consultant', 132 | 'active': true, 'phoneNumber': '408-082-9480', 'date': '2015-01-31T03:41:54.611Z' 133 | }, 134 | { 135 | 'name': 'Corbin Funk Mrs.', 'email': 'Marjory.Morissette51@gmail.com', 'jobTitle': 'Human Configuration Manager', 136 | 'active': true, 'phoneNumber': '386-937-8683', 'date': '2014-12-05T15:07:36.843Z' 137 | }, 138 | { 139 | 'name': 'Rosalind Paucek', 'email': 'Ivy_Stanton@gmail.com', 'jobTitle': 'Future Creative Supervisor', 140 | 'active': true, 'phoneNumber': '977-661-7403', 'date': '2015-06-10T17:42:38.644Z' 141 | }, 142 | { 143 | 'name': 'Henderson Moore', 'email': 'Randi_Corkery@hotmail.com', 'jobTitle': 'Internal Accountability Director', 144 | 'active': true, 'phoneNumber': '078-101-6377', 'date': '2015-09-26T05:14:34.913Z' 145 | }, 146 | { 147 | 'name': 'Kelli Schoen', 'email': 'Reva.Kiehn54@yahoo.com', 'jobTitle': 'National Accountability Architect', 148 | 'active': false, 'phoneNumber': '654-591-6561', 'date': '2015-05-04T06:50:37.482Z' 149 | }, 150 | { 151 | 'name': 'Kenna Fritsch', 'email': 'Wilburn2@gmail.com', 'jobTitle': 'Legacy Response Administrator', 152 | 'active': true, 'phoneNumber': '790-480-2859', 'date': '2015-10-10T23:37:05.867Z' 153 | }, 154 | { 155 | 'name': 'Judge Marquardt', 'email': 'Letha_Champlin69@hotmail.com', 'jobTitle': 'Human Program Specialist', 156 | 'active': true, 'phoneNumber': '100-494-1787', 'date': '2015-04-04T23:29:48.588Z' 157 | }, 158 | { 159 | 'name': 'Kurtis Hane', 'email': 'Mona.Gaylord47@yahoo.com', 'jobTitle': 'International Optimization Director', 160 | 'active': false, 'phoneNumber': '008-800-2959', 'date': '2014-12-04T21:09:50.722Z' 161 | }, 162 | { 163 | 'name': 'Nicolette Lind', 'email': 'Thurman30@yahoo.com', 'jobTitle': 'Legacy Marketing Facilitator', 164 | 'active': true, 'phoneNumber': '007-908-2460', 'date': '2015-06-22T08:11:57.381Z' 165 | }, 166 | { 167 | 'name': 'Idella Green', 'email': 'Fernando_Ward@yahoo.com', 'jobTitle': 'Dynamic Division Orchestrator', 168 | 'active': false, 'phoneNumber': '147-865-1578', 'date': '2015-02-12T23:00:31.283Z' 169 | }, 170 | { 171 | 'name': 'Mackenzie Bartell', 'email': 'Price25@yahoo.com', 'jobTitle': 'National Directives Associate', 172 | 'active': false, 'phoneNumber': '235-649-0980', 'date': '2015-06-24T20:21:51.356Z' 173 | }, 174 | { 175 | 'name': 'Mose Kohler', 'email': 'Malika56@hotmail.com', 'jobTitle': 'Lead Implementation Executive', 176 | 'active': true, 'phoneNumber': '614-886-4868', 'date': '2015-03-04T13:05:23.698Z' 177 | }, 178 | { 179 | 'name': 'Cielo Kuphal', 'email': 'Jude_Terry24@gmail.com', 'jobTitle': 'Dynamic Division Analyst', 180 | 'active': false, 'phoneNumber': '590-976-7492', 'date': '2015-06-02T20:52:32.664Z' 181 | }, 182 | { 183 | 'name': 'Haleigh Stokes', 'email': 'Belle_Herman64@yahoo.com', 'jobTitle': 'Global Intranet Executive', 184 | 'active': false, 'phoneNumber': '418-255-9365', 'date': '2015-04-10T00:32:10.283Z' 185 | }, 186 | { 187 | 'name': 'Tyrese Walter', 'email': 'Garland.Veum52@hotmail.com', 'jobTitle': 'Senior Web Liason', 188 | 'active': false, 'phoneNumber': '041-555-9831', 'date': '2015-08-18T20:05:08.839Z' 189 | }, 190 | { 191 | 'name': 'Barney Shields', 'email': 'Anika27@gmail.com', 'jobTitle': 'District Web Administrator', 192 | 'active': true, 'phoneNumber': '379-438-0217', 'date': '2015-06-01T09:28:46.778Z' 193 | }, 194 | { 195 | 'name': 'Favian Abbott Miss', 'email': 'Palma_Little@hotmail.com', 'jobTitle': 'Lead Implementation Facilitator', 196 | 'active': false, 'phoneNumber': '642-808-5400', 'date': '2015-08-09T07:38:06.588Z' 197 | }, 198 | { 199 | 'name': 'Carissa Kunze', 'email': 'Merl_Frami@yahoo.com', 'jobTitle': 'Regional Division Technician', 200 | 'active': true, 'phoneNumber': '949-983-0342', 'date': '2015-11-05T08:09:09.463Z' 201 | }, 202 | { 203 | 'name': 'Aaron 2Moore', 'email': 'Heath44@hotmail.com', 'jobTitle': 'Regional Configuration Producer', 204 | 'active': true, 'phoneNumber': '611-898-6201', 'date': '2015-11-06T07:21:25.510Z' 205 | }, 206 | { 207 | 'name': 'Yvonne Conroy Mrs.', 'email': 'Gideon9@yahoo.com', 'jobTitle': 'Global Mobility Orchestrator', 208 | 'active': false, 'phoneNumber': '115-850-0969', 'date': '2014-12-20T00:48:40.276Z' 209 | }, 210 | { 211 | 'name': 'Laron Padberg', 'email': 'Laney_Huels@hotmail.com', 'jobTitle': 'Senior Directives Supervisor', 212 | 'active': false, 'phoneNumber': '632-654-3034', 'date': '2015-09-29T04:33:38.544Z' 213 | }, 214 | { 215 | 'name': 'Dr. Maryam Spinka', 'email': 'Aletha.Labadie@hotmail.com', 'jobTitle': 'Dynamic Mobility Associate', 216 | 'active': true, 'phoneNumber': '547-345-0067', 'date': '2015-09-23T01:13:39.320Z' 217 | }, 218 | { 219 | 'name': 'Kiley Baumbach', 'email': 'Rogelio24@hotmail.com', 'jobTitle': 'Principal Metrics Orchestrator', 220 | 'active': true, 'phoneNumber': '958-524-5164', 'date': '2014-12-05T23:39:27.340Z' 221 | }, 222 | { 223 | 'name': 'Hollis MacGyver', 'email': 'Yazmin.Heidenreich97@gmail.com', 'jobTitle': 'Direct Markets Assistant', 224 | 'active': true, 'phoneNumber': '603-607-3241', 'date': '2015-02-12T10:40:52.977Z' 225 | }, 226 | { 227 | 'name': 'Axel McLaughlin', 'email': 'Deon_Heaney@gmail.com', 'jobTitle': 'Forward Mobility Architect', 228 | 'active': false, 'phoneNumber': '983-639-0705', 'date': '2015-03-01T02:28:26.030Z' 229 | }, 230 | { 231 | 'name': 'Ricardo Botsford', 'email': 'Melisa73@yahoo.com', 'jobTitle': 'Direct Quality Consultant', 232 | 'active': true, 'phoneNumber': '408-082-9480', 'date': '2015-01-31T03:41:54.611Z' 233 | }, 234 | { 235 | 'name': 'Corbin Funk Mrs.', 'email': 'Marjory.Morissette51@gmail.com', 'jobTitle': 'Human Configuration Manager', 236 | 'active': true, 'phoneNumber': '386-937-8683', 'date': '2014-12-05T15:07:36.843Z' 237 | }, 238 | { 239 | 'name': 'Rosalind Paucek', 'email': 'Ivy_Stanton@gmail.com', 'jobTitle': 'Future Creative Supervisor', 240 | 'active': true, 'phoneNumber': '977-661-7403', 'date': '2015-06-10T17:42:38.644Z' 241 | }, 242 | { 243 | 'name': 'Henderson Moore', 'email': 'Randi_Corkery@hotmail.com', 'jobTitle': 'Internal Accountability Director', 244 | 'active': true, 'phoneNumber': '078-101-6377', 'date': '2015-09-26T05:14:34.913Z' 245 | }, 246 | { 247 | 'name': 'Kelli Schoen', 'email': 'Reva.Kiehn54@yahoo.com', 'jobTitle': 'National Accountability Architect', 248 | 'active': false, 'phoneNumber': '654-591-6561', 'date': '2015-05-04T06:50:37.482Z' 249 | }, 250 | { 251 | 'name': 'Kenna Fritsch', 'email': 'Wilburn2@gmail.com', 'jobTitle': 'Legacy Response Administrator', 252 | 'active': true, 'phoneNumber': '790-480-2859', 'date': '2015-10-10T23:37:05.867Z' 253 | }, 254 | { 255 | 'name': 'Judge Marquardt', 'email': 'Letha_Champlin69@hotmail.com', 'jobTitle': 'Human Program Specialist', 256 | 'active': true, 'phoneNumber': '100-494-1787', 'date': '2015-04-04T23:29:48.588Z' 257 | }, 258 | { 259 | 'name': 'Kurtis Hane', 'email': 'Mona.Gaylord47@yahoo.com', 'jobTitle': 'International Optimization Director', 260 | 'active': false, 'phoneNumber': '008-800-2959', 'date': '2014-12-04T21:09:50.722Z' 261 | }, 262 | { 263 | 'name': 'Nicolette Lind', 'email': 'Thurman30@yahoo.com', 'jobTitle': 'Legacy Marketing Facilitator', 264 | 'active': true, 'phoneNumber': '007-908-2460', 'date': '2015-06-22T08:11:57.381Z' 265 | }, 266 | { 267 | 'name': 'Idella Green', 'email': 'Fernando_Ward@yahoo.com', 'jobTitle': 'Dynamic Division Orchestrator', 268 | 'active': false, 'phoneNumber': '147-865-1578', 'date': '2015-02-12T23:00:31.283Z' 269 | }, 270 | { 271 | 'name': 'Mackenzie Bartell', 'email': 'Price25@yahoo.com', 'jobTitle': 'National Directives Associate', 272 | 'active': false, 'phoneNumber': '235-649-0980', 'date': '2015-06-24T20:21:51.356Z' 273 | }, 274 | { 275 | 'name': 'Mose Kohler', 'email': 'Malika56@hotmail.com', 'jobTitle': 'Lead Implementation Executive', 276 | 'active': true, 'phoneNumber': '614-886-4868', 'date': '2015-03-04T13:05:23.698Z' 277 | }, 278 | { 279 | 'name': 'Cielo Kuphal', 'email': 'Jude_Terry24@gmail.com', 'jobTitle': 'Dynamic Division Analyst', 280 | 'active': false, 'phoneNumber': '590-976-7492', 'date': '2015-06-02T20:52:32.664Z' 281 | }, 282 | { 283 | 'name': 'Haleigh Stokes', 'email': 'Belle_Herman64@yahoo.com', 'jobTitle': 'Global Intranet Executive', 284 | 'active': false, 'phoneNumber': '418-255-9365', 'date': '2015-04-10T00:32:10.283Z' 285 | }, 286 | { 287 | 'name': 'Tyrese Walter', 'email': 'Garland.Veum52@hotmail.com', 'jobTitle': 'Senior Web Liason', 288 | 'active': false, 'phoneNumber': '041-555-9831', 'date': '2015-08-18T20:05:08.839Z' 289 | }, 290 | { 291 | 'name': 'Barney Shields', 'email': 'Anika27@gmail.com', 'jobTitle': 'District Web Administrator', 292 | 'active': true, 'phoneNumber': '379-438-0217', 'date': '2015-06-01T09:28:46.778Z' 293 | }, 294 | { 295 | 'name': 'Favian Abbott Miss', 'email': 'Palma_Little@hotmail.com', 'jobTitle': 'Lead Implementation Facilitator', 296 | 'active': false, 'phoneNumber': '642-808-5400', 'date': '2015-08-09T07:38:06.588Z' 297 | }, 298 | { 299 | 'name': 'Carissa Kunze', 'email': 'Merl_Frami@yahoo.com', 'jobTitle': 'Regional Division Technician', 300 | 'active': true, 'phoneNumber': '949-983-0342', 'date': '2015-11-05T08:09:09.463Z' 301 | }, 302 | { 303 | 'name': 'Favian Abbott Miss', 'email': 'Palma_Little@hotmail.com', 'jobTitle': 'Lead Implementation Facilitator', 304 | 'active': false, 'phoneNumber': '642-808-5400', 'date': '2015-08-09T07:38:06.588Z' 305 | }, 306 | { 307 | 'name': 'Carissa Kunze', 'email': 'Merl_Frami@yahoo.com', 'jobTitle': 'Regional Division Technician', 308 | 'active': true, 'phoneNumber': '949-983-0342', 'date': '2015-11-05T08:09:09.463Z' 309 | }, 310 | { 311 | 'name': 'Favian Abbott Miss', 'email': 'Palma_Little@hotmail.com', 'jobTitle': 'Lead Implementation Facilitator', 312 | 'active': false, 'phoneNumber': '642-808-5400', 'date': '2015-08-09T07:38:06.588Z' 313 | }, 314 | { 315 | 'name': 'Carissa Kunze', 'email': 'Merl_Frami@yahoo.com', 'jobTitle': 'Regional Division Technician', 316 | 'active': true, 'phoneNumber': '949-983-0342', 'date': '2015-11-05T08:09:09.463Z' 317 | } 318 | ]; 319 | --------------------------------------------------------------------------------