├── projects ├── demo │ ├── src │ │ ├── assets │ │ │ ├── .gitkeep │ │ │ └── demo.gif │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── pages │ │ │ │ ├── demo │ │ │ │ │ ├── snippets │ │ │ │ │ │ ├── install.md │ │ │ │ │ │ ├── require.md │ │ │ │ │ │ ├── template.md │ │ │ │ │ │ ├── data-template.md │ │ │ │ │ │ ├── directive.md │ │ │ │ │ │ ├── settings.md │ │ │ │ │ │ ├── array.md │ │ │ │ │ │ └── basic-full.md │ │ │ │ │ ├── demo.routes.ts │ │ │ │ │ ├── demo.module.ts │ │ │ │ │ ├── demo.component.ts │ │ │ │ │ └── demo.component.html │ │ │ │ ├── documentation │ │ │ │ │ ├── documentation.component.scss │ │ │ │ │ ├── documentation.routes.ts │ │ │ │ │ ├── documentation.component.ts │ │ │ │ │ └── documentation.module.ts │ │ │ │ ├── examples │ │ │ │ │ ├── snippets │ │ │ │ │ │ ├── source-require.md │ │ │ │ │ │ ├── source-template.md │ │ │ │ │ │ ├── create-source.md │ │ │ │ │ │ ├── custom-editor-module.md │ │ │ │ │ │ ├── search.md │ │ │ │ │ │ ├── hide-filters.md │ │ │ │ │ │ ├── search-table.md │ │ │ │ │ │ └── source-full.md │ │ │ │ │ ├── server │ │ │ │ │ │ ├── server-examples.component.ts │ │ │ │ │ │ ├── advanced-example-server.component.ts │ │ │ │ │ │ ├── basic-example-load.service.ts │ │ │ │ │ │ ├── basic-example-load.component.ts │ │ │ │ │ │ ├── server-examples.component.html │ │ │ │ │ │ └── serve.data-source.ts │ │ │ │ │ ├── examples.component.ts │ │ │ │ │ ├── various │ │ │ │ │ │ ├── various-examples.component.ts │ │ │ │ │ │ ├── various-examples.component.html │ │ │ │ │ │ ├── basic-example-multi-select.component.ts │ │ │ │ │ │ └── advanced-example-confirm.component.ts │ │ │ │ │ ├── custom-edit-view │ │ │ │ │ │ ├── custom-edit-view-examples.component.ts │ │ │ │ │ │ ├── custom-render.component.ts │ │ │ │ │ │ ├── custom-filter.component.ts │ │ │ │ │ │ ├── advanced-example-custom-editor.component.ts │ │ │ │ │ │ ├── basic-example-custom-actions.component.ts │ │ │ │ │ │ ├── custom-editor.component.ts │ │ │ │ │ │ ├── basic-example-button-view.component.ts │ │ │ │ │ │ ├── custom-edit-view-examples.component.html │ │ │ │ │ │ └── advanced-example-types.component.ts │ │ │ │ │ ├── examples.component.html │ │ │ │ │ ├── filter │ │ │ │ │ │ ├── filter-examples.component.ts │ │ │ │ │ │ ├── advanced-example-filters.component.ts │ │ │ │ │ │ ├── basic-example-source.component.ts │ │ │ │ │ │ └── filter-examples.component.html │ │ │ │ │ ├── examples.routes.ts │ │ │ │ │ ├── examples.component.scss │ │ │ │ │ └── examples.module.ts │ │ │ │ ├── home │ │ │ │ │ ├── home.routes.ts │ │ │ │ │ ├── home.component.ts │ │ │ │ │ ├── home.module.ts │ │ │ │ │ └── home.component.html │ │ │ │ ├── pages.routes.ts │ │ │ │ └── pages.module.ts │ │ │ ├── app.component.html │ │ │ ├── theme │ │ │ │ ├── theme.scss │ │ │ │ ├── directives │ │ │ │ │ └── scrollPosition.directive.ts │ │ │ │ └── sass │ │ │ │ │ └── _light.scss │ │ │ ├── app.component.ts │ │ │ ├── app.routes.ts │ │ │ ├── shared │ │ │ │ ├── components │ │ │ │ │ ├── header │ │ │ │ │ │ ├── header.component.ts │ │ │ │ │ │ └── header.component.html │ │ │ │ │ └── basic-example │ │ │ │ │ │ ├── basic-example.component.ts │ │ │ │ │ │ └── basic-example-data.component.ts │ │ │ │ ├── directives │ │ │ │ │ └── highlight.directive.ts │ │ │ │ └── shared.module.ts │ │ │ ├── app.module.ts │ │ │ └── app.component.spec.ts │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── main.ts │ │ ├── index.html │ │ ├── test.ts │ │ └── polyfills.ts │ ├── e2e │ │ ├── tsconfig.json │ │ ├── src │ │ │ ├── app.po.ts │ │ │ └── app.e2e-spec.ts │ │ └── protractor.conf.js │ ├── tslint.json │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ ├── .browserslistrc │ └── karma.conf.js └── ng2-smart-table │ ├── src │ ├── lib │ │ ├── index.ts │ │ ├── components │ │ │ ├── cell │ │ │ │ ├── cell-view-mode │ │ │ │ │ ├── view-cell.ts │ │ │ │ │ ├── view-cell.component.ts │ │ │ │ │ └── custom-view.component.ts │ │ │ │ ├── cell-editors │ │ │ │ │ ├── editor.component.scss │ │ │ │ │ ├── default-editor.ts │ │ │ │ │ ├── input-editor.component.ts │ │ │ │ │ ├── textarea-editor.component.ts │ │ │ │ │ ├── select-editor.component.ts │ │ │ │ │ ├── checkbox-editor.component.ts │ │ │ │ │ └── completer-editor.component.ts │ │ │ │ ├── cell-edit-mode │ │ │ │ │ ├── default-edit.component.ts │ │ │ │ │ ├── edit-cell-default.ts │ │ │ │ │ ├── edit-cell.component.ts │ │ │ │ │ ├── default-edit.component.html │ │ │ │ │ └── custom-edit.component.ts │ │ │ │ ├── cell.component.ts │ │ │ │ └── cell.module.ts │ │ │ ├── tbody │ │ │ │ ├── tbody.component.scss │ │ │ │ ├── tbody.module.ts │ │ │ │ ├── cells │ │ │ │ │ ├── custom.component.ts │ │ │ │ │ ├── create-cancel.component.ts │ │ │ │ │ └── edit-delete.component.ts │ │ │ │ ├── tbody.component.ts │ │ │ │ └── tbody.component.html │ │ │ ├── pager │ │ │ │ ├── pager.module.ts │ │ │ │ └── pager.component.scss │ │ │ ├── filter │ │ │ │ ├── filter.component.scss │ │ │ │ ├── filter-default.ts │ │ │ │ ├── filter-types │ │ │ │ │ ├── default-filter.ts │ │ │ │ │ ├── select-filter.component.ts │ │ │ │ │ ├── input-filter.component.ts │ │ │ │ │ ├── checkbox-filter.component.ts │ │ │ │ │ └── completer-filter.component.ts │ │ │ │ ├── default-filter.component.ts │ │ │ │ ├── filter.module.ts │ │ │ │ ├── custom-filter.component.ts │ │ │ │ └── filter.component.ts │ │ │ └── thead │ │ │ │ ├── cells │ │ │ │ ├── checkbox-select-all.component.ts │ │ │ │ ├── title │ │ │ │ │ ├── title.component.scss │ │ │ │ │ └── title.component.ts │ │ │ │ ├── column-title.component.ts │ │ │ │ ├── actions-title.component.ts │ │ │ │ ├── actions.component.ts │ │ │ │ └── add-button.component.ts │ │ │ │ ├── thead.component.html │ │ │ │ ├── thead.component.ts │ │ │ │ ├── thead.module.ts │ │ │ │ └── rows │ │ │ │ ├── thead-titles-row.component.ts │ │ │ │ ├── thead-filters-row.component.ts │ │ │ │ └── thead-form-row.component.ts │ │ ├── lib │ │ │ ├── data-source │ │ │ │ ├── local │ │ │ │ │ ├── local.pager.ts │ │ │ │ │ ├── local.filter.ts │ │ │ │ │ └── local.sorter.ts │ │ │ │ ├── server │ │ │ │ │ ├── server-source.conf.ts │ │ │ │ │ └── server.data-source.ts │ │ │ │ └── data-source.ts │ │ │ ├── data-set │ │ │ │ ├── cell.ts │ │ │ │ ├── row.ts │ │ │ │ ├── column.ts │ │ │ │ └── data-set.ts │ │ │ └── helpers.ts │ │ ├── ng2-smart-table.module.ts │ │ ├── ng2-smart-table.component.scss │ │ └── ng2-smart-table.component.html │ ├── public-api.ts │ └── test.ts │ ├── tsconfig.lib.prod.json │ ├── ng-package.json │ ├── tslint.json │ ├── tsconfig.spec.json │ ├── package.json │ ├── tsconfig.lib.json │ └── karma.conf.js ├── .editorconfig ├── .travis.yml ├── tsconfig.json ├── .gitignore ├── DEV_DOCS.md ├── LICENSE.txt ├── package.json ├── tslint.json └── angular.json /projects/demo/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/demo/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/demo/snippets/install.md: -------------------------------------------------------------------------------- 1 | npm install --save ng2-smart-table -------------------------------------------------------------------------------- /projects/demo/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/demo/snippets/require.md: -------------------------------------------------------------------------------- 1 | import { Ng2SmartTableModule } from 'ng2-smart-table'; -------------------------------------------------------------------------------- /projects/demo/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/documentation/documentation.component.scss: -------------------------------------------------------------------------------- 1 | .nested-obj { 2 | color: #bdbdbd; 3 | } 4 | -------------------------------------------------------------------------------- /projects/demo/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akveo/ng2-smart-table/HEAD/projects/demo/src/favicon.ico -------------------------------------------------------------------------------- /projects/demo/src/app/theme/theme.scss: -------------------------------------------------------------------------------- 1 | @import "sass/normalize"; 2 | @import "sass/main"; 3 | @import "sass/light"; 4 | -------------------------------------------------------------------------------- /projects/demo/src/assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akveo/ng2-smart-table/HEAD/projects/demo/src/assets/demo.gif -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/snippets/source-require.md: -------------------------------------------------------------------------------- 1 | import { Ng2SmartTableModule, LocalDataSource } from 'ng2-smart-table'; -------------------------------------------------------------------------------- /projects/ng2-smart-table/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "angularCompilerOptions": { 4 | "enableIvy": false 5 | } 6 | } -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-view-mode/view-cell.ts: -------------------------------------------------------------------------------- 1 | export interface ViewCell { 2 | value: string | number; 3 | rowData: any; 4 | } 5 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/demo/snippets/template.md: -------------------------------------------------------------------------------- 1 | // ... 2 | 3 | @Component({ 4 | template: ` 5 | 6 | ` 7 | }) 8 | // ... -------------------------------------------------------------------------------- /projects/demo/src/app/pages/demo/snippets/data-template.md: -------------------------------------------------------------------------------- 1 | // ... 2 | 3 | @Component({ 4 | template: ` 5 | 6 | ` 7 | }) 8 | // ... -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/snippets/source-template.md: -------------------------------------------------------------------------------- 1 | // ... 2 | 3 | @Component({ 4 | template: ` 5 | 6 | ` 7 | }) 8 | // ... -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-editors/editor.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | input, 3 | textarea { 4 | width: 100%; 5 | line-height: normal; 6 | padding: .375em .75em; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/snippets/create-source.md: -------------------------------------------------------------------------------- 1 | source: LocalDataSource; // add a property to the component 2 | 3 | constructor() { 4 | this.source = new LocalDataSource(this.data); // create the source 5 | } -------------------------------------------------------------------------------- /projects/demo/src/app/pages/demo/snippets/directive.md: -------------------------------------------------------------------------------- 1 | // ... 2 | 3 | @NgModule({ 4 | imports: [ 5 | // ... 6 | 7 | Ng2SmartTableModule, 8 | 9 | // ... 10 | ], 11 | declarations: [ ... ] 12 | }) 13 | // ... -------------------------------------------------------------------------------- /projects/demo/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | template: ` 6 | 7 | `, 8 | }) 9 | export class AppComponent { 10 | } 11 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/lib/data-source/local/local.pager.ts: -------------------------------------------------------------------------------- 1 | export class LocalPager { 2 | 3 | static paginate(data: Array, page: number, perPage: number): Array { 4 | return data.slice(perPage * (page - 1), perPage * page); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/demo/demo.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | import { DemoComponent } from './demo.component'; 4 | 5 | export const routes: Routes = [ 6 | { 7 | path: '', 8 | component: DemoComponent, 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/home/home.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | export const routes: Routes = [ 6 | { 7 | path: '', 8 | component: HomeComponent, 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'home', 5 | templateUrl: './home.component.html', 6 | }) 7 | export class HomeComponent { 8 | 9 | constructor() { 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /projects/demo/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | export const routes: Routes = [ 4 | { 5 | path: '', 6 | redirectTo: 'pages', 7 | pathMatch: 'full', 8 | }, 9 | { 10 | path: '**', 11 | redirectTo: '', 12 | }, 13 | ]; 14 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/server/server-examples.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'server-examples', 5 | templateUrl: './server-examples.component.html', 6 | }) 7 | export class ServerExamplesComponent { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/examples.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'examples', 5 | styleUrls: ['./examples.component.scss'], 6 | templateUrl: 'examples.component.html', 7 | }) 8 | export class ExamplesComponent { 9 | } 10 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ng2-smart-table", 4 | "whitelistedNonPeerDependencies": [ 5 | "lodash" 6 | ], 7 | "lib": { 8 | "entryFile": "src/public-api.ts" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/snippets/custom-editor-module.md: -------------------------------------------------------------------------------- 1 | @NgModule({ 2 | imports: [ 3 | // ... 4 | ], 5 | entryComponents: [CustomEditorComponent, CustomRenderComponent], 6 | declarations: [ 7 | // ... 8 | CustomEditorComponent, 9 | CustomRenderComponent, 10 | ], 11 | }) -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/various/various-examples.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, AfterViewInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'various-examples', 5 | templateUrl: './various-examples.component.html', 6 | }) 7 | export class VariousExamplesComponent { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://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 | -------------------------------------------------------------------------------- /projects/demo/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es2018", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/demo/snippets/settings.md: -------------------------------------------------------------------------------- 1 | settings = { 2 | columns: { 3 | id: { 4 | title: 'ID' 5 | }, 6 | name: { 7 | title: 'Full Name' 8 | }, 9 | username: { 10 | title: 'User Name' 11 | }, 12 | email: { 13 | title: 'Email' 14 | } 15 | } 16 | }; -------------------------------------------------------------------------------- /projects/demo/src/app/pages/documentation/documentation.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | import { DocumentationComponent } from './documentation.component'; 4 | 5 | export const routes: Routes = [ 6 | { 7 | path: '', 8 | component: DocumentationComponent, 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/snippets/search.md: -------------------------------------------------------------------------------- 1 | // ... 2 | 3 | @Component({ 4 | template: ` 5 | 6 | 7 | ` 8 | }) 9 | // ... -------------------------------------------------------------------------------- /projects/demo/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/documentation/documentation.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'demo', 5 | styleUrls: ['./documentation.component.scss'], 6 | templateUrl: './documentation.component.html', 7 | }) 8 | export class DocumentationComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /projects/demo/src/app/shared/components/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'header-component', 5 | templateUrl: './header.component.html', 6 | }) 7 | export class HeaderComponent { 8 | 9 | @Input() tagline: string = ''; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /projects/demo/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "lib", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "lib", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /projects/demo/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/app", 5 | "types": [ 6 | "node" 7 | ] 8 | }, 9 | "files": [ 10 | "src/main.ts", 11 | "src/polyfills.ts" 12 | ], 13 | "include": [ 14 | "src/**/*.d.ts", 15 | "../ng2-smart-table/src/**/*.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /projects/demo/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/snippets/hide-filters.md: -------------------------------------------------------------------------------- 1 | settings = { 2 | columns: { 3 | id: { 4 | title: 'ID', 5 | filter: false 6 | }, 7 | name: { 8 | title: 'Full Name', 9 | filter: false 10 | }, 11 | username: { 12 | title: 'User Name', 13 | filter: false 14 | }, 15 | email: { 16 | title: 'Email', 17 | filter: false 18 | } 19 | } 20 | }; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - "10" 7 | 8 | -cache: 9 | directories: 10 | - node_modules 11 | 12 | addons: 13 | apt: 14 | sources: 15 | - ubuntu-toolchain-r-test 16 | packages: 17 | - g++-4.8 18 | 19 | before_install: 20 | - npm i -g npm@latest 21 | 22 | install: 23 | - npm i 24 | 25 | script: 26 | - npm run build:ci 27 | 28 | git: 29 | depth: 1 30 | -------------------------------------------------------------------------------- /projects/demo/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.error(err)); 13 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/custom-edit-view/custom-edit-view-examples.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'custom-edit-view-examples', 5 | templateUrl: './custom-edit-view-examples.component.html', 6 | }) 7 | export class CustomViewEditExamplesComponent { 8 | 9 | snippets = { 10 | customEditorModule: require('raw-loader!../snippets/custom-editor-module.md').default, 11 | }; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /projects/demo/src/app/shared/directives/highlight.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, AfterViewInit } from '@angular/core'; 2 | import * as hljs from 'highlight.js'; 3 | 4 | @Directive({ 5 | selector: 'code[highlight]', 6 | }) 7 | export class HighlightCodeDirective implements AfterViewInit { 8 | 9 | constructor(private elRef: ElementRef) { } 10 | 11 | ngAfterViewInit() { 12 | hljs.highlightBlock(this.elRef.nativeElement); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2-smart-table", 3 | "version": "1.7.2", 4 | "description": "Angular Smart Table", 5 | "author": "akveo", 6 | "license": "MIT", 7 | "dependencies": { 8 | "lodash": "^4.17.10", 9 | "tslib": "^2.0.0" 10 | }, 11 | "peerDependencies": { 12 | "ng2-completer": "^9.0.1", 13 | "@angular/common": "^10.0.0", 14 | "@angular/core": "^10.0.0", 15 | "@angular/forms": "^10.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ng2-smart-table - Angular data table library with sorting, filtering, pagination & add/edit/delete functions. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/tbody/tbody.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | .ng2-smart-row { 3 | &.selected { 4 | background: rgba(0, 0, 0, 0.05); 5 | } 6 | .ng2-smart-actions { 7 | &.ng2-smart-action-multiple-select { 8 | text-align: center; 9 | } 10 | } 11 | } 12 | ::ng-deep ng2-st-tbody-edit-delete, ::ng-deep ng2-st-tbody-create-cancel { 13 | a:first-child { 14 | margin-right: 0.25rem; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/demo/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /projects/demo/src/app/pages/demo/snippets/array.md: -------------------------------------------------------------------------------- 1 | data = [ 2 | { 3 | id: 1, 4 | name: "Leanne Graham", 5 | username: "Bret", 6 | email: "Sincere@april.biz" 7 | }, 8 | { 9 | id: 2, 10 | name: "Ervin Howell", 11 | username: "Antonette", 12 | email: "Shanna@melissa.tv" 13 | }, 14 | 15 | // ... list of items 16 | 17 | { 18 | id: 11, 19 | name: "Nicholas DuBuque", 20 | username: "Nicholas.Stanton", 21 | email: "Rey.Padberg@rosamond.biz" 22 | } 23 | ]; -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/pager/pager.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { PagerComponent } from './pager.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule, 10 | FormsModule, 11 | ], 12 | declarations: [ 13 | PagerComponent, 14 | ], 15 | exports: [ 16 | PagerComponent, 17 | ], 18 | }) 19 | export class PagerModule { } 20 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/filter/filter.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | .ng2-smart-filter { 3 | ::ng-deep { 4 | input, 5 | select { 6 | width: 100%; 7 | line-height: normal; 8 | padding: .375em .75em; 9 | font-weight: normal; 10 | } 11 | input[type="search"] { 12 | box-sizing: inherit; 13 | } 14 | .completer-dropdown-holder { 15 | font-weight: normal; 16 | } 17 | a { 18 | font-weight: normal; 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/custom-edit-view/custom-render.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | import { ViewCell } from 'ng2-smart-table'; 4 | 5 | @Component({ 6 | template: ` 7 | {{renderValue}} 8 | `, 9 | }) 10 | export class CustomRenderComponent implements ViewCell, OnInit { 11 | 12 | renderValue: string; 13 | 14 | @Input() value: string | number; 15 | @Input() rowData: any; 16 | 17 | ngOnInit() { 18 | this.renderValue = this.value.toString().toUpperCase(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/thead/cells/checkbox-select-all.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | import { Grid } from '../../../lib/grid'; 4 | import { DataSource } from '../../../lib/data-source/data-source'; 5 | 6 | @Component({ 7 | selector: '[ng2-st-checkbox-select-all]', 8 | template: ` 9 | 10 | `, 11 | }) 12 | export class CheckboxSelectAllComponent { 13 | 14 | @Input() grid: Grid; 15 | @Input() source: DataSource; 16 | @Input() isAllSelected: boolean; 17 | } 18 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | import { SharedModule } from '../../shared/shared.module'; 6 | 7 | import { routes } from './home.routes'; 8 | 9 | import { HomeComponent } from './home.component'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | RouterModule.forChild(routes), 15 | SharedModule, 16 | ], 17 | declarations: [ 18 | HomeComponent, 19 | ], 20 | }) 21 | export class HomeModule { } 22 | -------------------------------------------------------------------------------- /projects/demo/src/app/shared/components/header/header.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/public-api.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/ng2-smart-table.module'; 2 | export { ViewCell } from './lib/components/cell/cell-view-mode/view-cell'; 3 | export { DefaultEditor, Editor } from './lib/components/cell/cell-editors/default-editor'; 4 | export { DefaultFilter, Filter } from './lib/components/filter/filter-types/default-filter' 5 | export { Cell } from './lib/lib/data-set/cell'; 6 | export { LocalDataSource } from './lib/lib/data-source/local/local.data-source'; 7 | export { ServerDataSource } from './lib/lib/data-source/server/server.data-source'; 8 | export * from './lib/ng2-smart-table.component'; 9 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-edit-mode/default-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { EditCellDefault } from './edit-cell-default'; 4 | import { Cell } from '../../../lib/data-set/cell'; 5 | 6 | @Component({ 7 | selector: 'table-cell-default-editor', 8 | templateUrl: './default-edit.component.html', 9 | }) 10 | export class DefaultEditComponent extends EditCellDefault { 11 | 12 | constructor() { 13 | super(); 14 | } 15 | 16 | getEditorType(): string { 17 | return this.cell.getColumn().editor && this.cell.getColumn().editor.type; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": [ 10 | "dom", 11 | "es2018" 12 | ] 13 | }, 14 | "angularCompilerOptions": { 15 | "skipTemplateCodegen": true, 16 | "strictMetadataEmit": true, 17 | "fullTemplateTypeCheck": true, 18 | "strictInjectionParameters": true, 19 | "enableResourceInlining": true 20 | }, 21 | "exclude": [ 22 | "src/test.ts", 23 | "**/*.spec.ts" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/documentation/documentation.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | import { SharedModule } from '../../shared/shared.module'; 6 | 7 | import { routes } from './documentation.routes'; 8 | 9 | import { DocumentationComponent } from './documentation.component'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | RouterModule.forChild(routes), 15 | SharedModule, 16 | ], 17 | declarations: [ 18 | DocumentationComponent, 19 | ], 20 | }) 21 | export class DocumentationModule { } 22 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/lib/data-source/local/local.filter.ts: -------------------------------------------------------------------------------- 1 | export function filterValues(value: string, search: string) { 2 | return value.toString().toLowerCase().includes(search.toString().toLowerCase()); 3 | } 4 | 5 | export class LocalFilter { 6 | 7 | static filter(data: Array, field: string, search: string, customFilter?: Function): Array { 8 | const filter: Function = customFilter ? customFilter : filterValues; 9 | 10 | return data.filter((el) => { 11 | const value = typeof el[field] === 'undefined' || el[field] === null ? '' : el[field]; 12 | return filter.call(null, value, search); 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/demo/demo.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | import { SharedModule } from '../../shared/shared.module'; 6 | 7 | import { routes } from './demo.routes'; 8 | 9 | import { DemoComponent } from './demo.component'; 10 | 11 | const DEMO_COMPONENTS = [ 12 | DemoComponent, 13 | ]; 14 | 15 | @NgModule({ 16 | imports: [ 17 | CommonModule, 18 | RouterModule.forChild(routes), 19 | SharedModule, 20 | ], 21 | declarations: [ 22 | ...DEMO_COMPONENTS, 23 | ], 24 | }) 25 | export class DemoModule { } 26 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/pages.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | export const routes: Routes = [ 4 | { 5 | path: '', 6 | loadChildren: () => import('./home/home.module').then(m => m.HomeModule), 7 | }, 8 | { 9 | path: 'demo', 10 | loadChildren: () => import('./demo/demo.module').then(m => m.DemoModule), 11 | }, 12 | { 13 | path: 'documentation', 14 | loadChildren: () => import('./documentation/documentation.module').then(m => m.DocumentationModule), 15 | }, 16 | { 17 | path: 'examples', 18 | loadChildren: () => import('./examples/examples.module').then(m => m.ExamplesModule), 19 | }, 20 | ]; 21 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/thead/cells/title/title.component.scss: -------------------------------------------------------------------------------- 1 | a.sort { 2 | 3 | &.asc, &.desc { 4 | font-weight: bold; 5 | 6 | &::after { 7 | content: ''; 8 | display: inline-block; 9 | width: 0; 10 | height: 0; 11 | border-bottom: 4px solid rgba(0, 0, 0, 0.3); 12 | border-top: 4px solid transparent; 13 | border-left: 4px solid transparent; 14 | border-right: 4px solid transparent; 15 | margin-bottom: 2px; 16 | } 17 | } 18 | 19 | &.desc::after { 20 | -webkit-transform: rotate(-180deg); 21 | transform: rotate(-180deg); 22 | margin-bottom: -2px; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2020", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ], 21 | "paths": { 22 | "ng2-smart-table": [ 23 | "projects/ng2-smart-table/src/public-api.ts" 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/pages.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | import { RouterModule } from '@angular/router'; 5 | import { Ng2SmartTableModule } from 'ng2-smart-table'; 6 | 7 | import { routes } from './pages.routes'; 8 | import { SharedModule } from '../shared/shared.module'; 9 | 10 | @NgModule({ 11 | imports: [ 12 | FormsModule, 13 | HttpClientModule, 14 | ReactiveFormsModule, 15 | RouterModule.forChild(routes), 16 | Ng2SmartTableModule, 17 | SharedModule, 18 | ], 19 | }) 20 | export class PagesModule { 21 | } 22 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/lib/data-source/local/local.sorter.ts: -------------------------------------------------------------------------------- 1 | export function compareValues(direction: any, a: any, b: any) { 2 | if (a < b) { 3 | return -1 * direction; 4 | } 5 | if (a > b) { 6 | return direction; 7 | } 8 | return 0; 9 | } 10 | 11 | export class LocalSorter { 12 | 13 | static sort(data: Array, field: string, direction: string, customCompare?: Function): Array { 14 | 15 | const dir: number = (direction === 'asc') ? 1 : -1; 16 | const compare: Function = customCompare ? customCompare : compareValues; 17 | 18 | return data.sort((a, b) => { 19 | return compare.call(null, dir, a[field], b[field]); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /projects/demo/src/app/shared/components/basic-example/basic-example.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'basic-example', 5 | template: ` 6 | 7 | `, 8 | }) 9 | export class BasicExampleComponent { 10 | 11 | settings = { 12 | columns: { 13 | id: { 14 | title: 'ID', 15 | width: '100px', 16 | }, 17 | name: { 18 | title: 'Full Name', 19 | width: '40%', 20 | }, 21 | username: { 22 | title: 'User Name', 23 | }, 24 | email: { 25 | title: 'Email', 26 | }, 27 | }, 28 | }; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/snippets/search-table.md: -------------------------------------------------------------------------------- 1 | onSearch(query: string = '') { 2 | this.source.setFilter([ 3 | // fields we want to include in the search 4 | { 5 | field: 'id', 6 | search: query 7 | }, 8 | { 9 | field: 'name', 10 | search: query 11 | }, 12 | { 13 | field: 'username', 14 | search: query 15 | }, 16 | { 17 | field: 'email', 18 | search: query 19 | } 20 | ], false); 21 | // second parameter specifying whether to perform 'AND' or 'OR' search 22 | // (meaning all columns should contain search query or at least one) 23 | // 'AND' by default, so changing to 'OR' by setting false here 24 | } 25 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/thead/cells/column-title.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | import { Column } from '../../../lib/data-set/column'; 4 | import { DataSource } from '../../../lib/data-source/data-source'; 5 | 6 | @Component({ 7 | selector: 'ng2-st-column-title', 8 | template: ` 9 |
10 | 11 |
12 | `, 13 | }) 14 | export class ColumnTitleComponent { 15 | 16 | @Input() column: Column; 17 | @Input() source: DataSource; 18 | 19 | @Output() sort = new EventEmitter(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-edit-mode/edit-cell-default.ts: -------------------------------------------------------------------------------- 1 | import { Component, Output, EventEmitter, Input } from '@angular/core'; 2 | 3 | import { Cell } from '../../../lib/data-set/cell'; 4 | 5 | @Component({ 6 | template: '' 7 | }) 8 | export class EditCellDefault { 9 | 10 | @Input() cell: Cell; 11 | @Input() inputClass: string = ''; 12 | 13 | @Output() edited = new EventEmitter(); 14 | 15 | onEdited(event: any): boolean { 16 | this.edited.next(event); 17 | return false; 18 | } 19 | 20 | onStopEditing(): boolean { 21 | this.cell.getRow().isInEditing = false; 22 | return false; 23 | } 24 | 25 | onClick(event: any) { 26 | event.stopPropagation(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /projects/demo/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('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-editors/default-editor.ts: -------------------------------------------------------------------------------- 1 | import { Component, Output, EventEmitter, Input } from '@angular/core'; 2 | 3 | import { Cell } from '../../../lib/data-set/cell'; 4 | 5 | @Component({ 6 | template: '', 7 | }) 8 | export class DefaultEditor implements Editor { 9 | @Input() cell: Cell; 10 | @Input() inputClass: string; 11 | 12 | @Output() onStopEditing = new EventEmitter(); 13 | @Output() onEdited = new EventEmitter(); 14 | @Output() onClick = new EventEmitter(); 15 | } 16 | 17 | export interface Editor { 18 | cell: Cell; 19 | inputClass: string; 20 | onStopEditing: EventEmitter; 21 | onEdited: EventEmitter; 22 | onClick: EventEmitter; 23 | } 24 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-view-mode/view-cell.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | import { Cell } from '../../../lib/data-set/cell'; 4 | 5 | @Component({ 6 | selector: 'table-cell-view-mode', 7 | changeDetection: ChangeDetectionStrategy.OnPush, 8 | template: ` 9 |
10 | 11 |
12 |
{{ cell.getValue() }}
13 |
14 | `, 15 | }) 16 | export class ViewCellComponent { 17 | 18 | @Input() cell: Cell; 19 | } 20 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/filter/filter-default.ts: -------------------------------------------------------------------------------- 1 | import { Output, EventEmitter, Input, Component } from '@angular/core'; 2 | 3 | import { Column } from '../../lib/data-set/column'; 4 | import { DataSource } from '../../lib/data-source/data-source'; 5 | 6 | @Component({ 7 | template: '', 8 | }) 9 | export class FilterDefault { 10 | 11 | @Input() column: Column; 12 | @Input() source: DataSource; 13 | @Input() inputClass: string = ''; 14 | 15 | @Output() filter = new EventEmitter(); 16 | 17 | query: string = ''; 18 | 19 | onFilter(query: string) { 20 | this.source.addFilter({ 21 | field: this.column.id, 22 | search: query, 23 | filter: this.column.getFilterFunction(), 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /projects/demo/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to demo!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /projects/demo/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/pager/pager.component.scss: -------------------------------------------------------------------------------- 1 | .ng2-smart-pagination { 2 | display: inline-flex; 3 | font-size: .875em; 4 | padding: 0; 5 | 6 | .sr-only { 7 | position: absolute; 8 | width: 1px; 9 | height: 1px; 10 | padding: 0; 11 | margin: -1px; 12 | overflow: hidden; 13 | clip: rect(0,0,0,0); 14 | border: 0; 15 | } 16 | 17 | .ng2-smart-page-item { 18 | display: inline; 19 | } 20 | 21 | .page-link-next, .page-link-prev { 22 | font-size: 10px; 23 | } 24 | } 25 | 26 | :host { 27 | display: flex; 28 | justify-content: space-between; 29 | 30 | select { 31 | margin: 1rem 0 1rem 1rem; 32 | } 33 | label { 34 | margin: 1rem 0 1rem 1rem; 35 | line-height: 2.5rem; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/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'; 4 | import 'zone.js/dist/zone-testing'; 5 | import { getTestBed } from '@angular/core/testing'; 6 | import { 7 | BrowserDynamicTestingModule, 8 | platformBrowserDynamicTesting 9 | } from '@angular/platform-browser-dynamic/testing'; 10 | 11 | declare const require: any; 12 | 13 | // First, initialize the Angular testing environment. 14 | getTestBed().initTestEnvironment( 15 | BrowserDynamicTestingModule, 16 | platformBrowserDynamicTesting() 17 | ); 18 | // Then we find all the tests. 19 | const context = require.context('./', true, /\.spec\.ts$/); 20 | // And load the modules. 21 | context.keys().map(context); 22 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/examples.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 12 |
13 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/thead/cells/actions-title.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, AfterViewInit, ElementRef, OnChanges} from '@angular/core'; 2 | 3 | import { Grid } from '../../../lib/grid'; 4 | 5 | @Component({ 6 | selector: '[ng2-st-actions-title]', 7 | template: ` 8 |
{{ actionsColumnTitle }}
9 | `, 10 | }) 11 | export class ActionsTitleComponent implements AfterViewInit, OnChanges { 12 | 13 | @Input() grid: Grid; 14 | 15 | actionsColumnTitle: string; 16 | 17 | constructor(private ref: ElementRef) { 18 | } 19 | 20 | ngAfterViewInit() { 21 | this.ref.nativeElement.classList.add('ng2-smart-actions'); 22 | } 23 | 24 | ngOnChanges() { 25 | this.actionsColumnTitle = this.grid.getSetting('actions.columnTitle'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/demo/demo.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'demo', 5 | templateUrl: './demo.component.html', 6 | }) 7 | export class DemoComponent { 8 | 9 | snippets = { 10 | install: require('raw-loader!./snippets/install.md').default, 11 | require: require('raw-loader!./snippets/require.md').default, 12 | directive: require('raw-loader!./snippets/directive.md').default, 13 | settings: require('raw-loader!./snippets/settings.md').default, 14 | template: require('raw-loader!./snippets/template.md').default, 15 | array: require('raw-loader!./snippets/array.md').default, 16 | dataTemplate: require('raw-loader!./snippets/data-template.md').default, 17 | basicFull: require('raw-loader!./snippets/basic-full.md').default, 18 | }; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-editors/input-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { DefaultEditor } from './default-editor'; 4 | 5 | @Component({ 6 | selector: 'input-editor', 7 | styleUrls: ['./editor.component.scss'], 8 | template: ` 9 | 18 | `, 19 | }) 20 | export class InputEditorComponent extends DefaultEditor { 21 | 22 | constructor() { 23 | super(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /projects/demo/src/app/theme/directives/scrollPosition.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, Output, EventEmitter, HostListener, OnInit } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[scrollPosition]', 5 | }) 6 | export class ScrollPositionDirective implements OnInit { 7 | 8 | @Input() maxHeight: number; 9 | 10 | @Output() scrollChange = new EventEmitter(); 11 | 12 | private isScrolled: boolean; 13 | 14 | ngOnInit() { 15 | this.onWindowScroll(); 16 | } 17 | 18 | @HostListener('window:scroll') 19 | onWindowScroll() { 20 | const isScrolled = window.scrollY > this.maxHeight; 21 | if (isScrolled !== this.isScrolled) { 22 | this.isScrolled = isScrolled; 23 | this.scrollChange.emit({ 24 | scrolled: isScrolled, 25 | offset: window.scrollY, 26 | }); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/filter/filter-examples.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'filter-examples', 5 | templateUrl: './filter-examples.component.html', 6 | }) 7 | export class FilterExamplesComponent { 8 | 9 | snippets = { 10 | sourceRequire: require('raw-loader!../snippets/source-require.md').default, 11 | createSource: require('raw-loader!../snippets/create-source.md').default, 12 | sourceTemplate: require('raw-loader!../snippets/source-template.md').default, 13 | search: require('raw-loader!../snippets/search.md').default, 14 | searchTable: require('raw-loader!../snippets/search-table.md').default, 15 | sourceFull: require('raw-loader!../snippets/source-full.md').default, 16 | hideFilters: require('raw-loader!../snippets/hide-filters.md').default, 17 | }; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /DEV_DOCS.md: -------------------------------------------------------------------------------- 1 | - [Release](#release) 2 | 3 | # Release 4 | 5 | 0. For major version, search for `@breaking-change` to make sure all breaking changes are covered. 6 | 7 | To start a new release (publish the framework packages on NPM) you need: 8 | 9 | 1. create a new release branch called `release/{version}` where {version} is in `v1.6.0` format 10 | 2. `npm run build:lib` to make sure the lib is building 11 | 3. MANUALLY update a version in main ./package.json & in ./packages/ng2-smart-table/package.json 12 | 4. `npm run changelog` to update changelog. Make sure the previous version has a git tag and it is pushed into the origin 13 | 5. commit with `release: {version}` message 14 | 6. push, create PR, approve & merge 15 | 7. pull changes and release with `npm run publish` 16 | 8. update docs with `npm run docs:gh-pages` 17 | 9. create git tag `{version}` and push it with `git push --tags` 18 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-editors/textarea-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { DefaultEditor } from './default-editor'; 4 | 5 | @Component({ 6 | selector: 'textarea-editor', 7 | styleUrls: ['./editor.component.scss'], 8 | template: ` 9 | 19 | `, 20 | }) 21 | export class TextareaEditorComponent extends DefaultEditor { 22 | 23 | constructor() { 24 | super(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /projects/demo/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | 'browserName': 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/server/advanced-example-server.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { ServerDataSource } from 'ng2-smart-table'; 4 | 5 | @Component({ 6 | selector: 'advanced-example-server', 7 | template: ` 8 | 9 | `, 10 | }) 11 | export class AdvancedExampleServerComponent { 12 | 13 | settings = { 14 | columns: { 15 | id: { 16 | title: 'ID', 17 | }, 18 | albumId: { 19 | title: 'Album', 20 | }, 21 | title: { 22 | title: 'Title', 23 | }, 24 | url: { 25 | title: 'Url', 26 | }, 27 | }, 28 | }; 29 | 30 | source: ServerDataSource; 31 | 32 | constructor(http: HttpClient) { 33 | this.source = new ServerDataSource(http, { endPoint: 'https://jsonplaceholder.typicode.com/photos' }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/server/basic-example-load.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class BasicExampleLoadService { 5 | 6 | static DATA_SIZE = 500; 7 | 8 | // emulating request to the server 9 | getData(): Promise { 10 | return new Promise((resolve, reject) => { 11 | setTimeout(() => { 12 | resolve(this.generateData()); 13 | }, 2000); 14 | }); 15 | } 16 | 17 | getNewExampleObj(n?: number): any { 18 | n = typeof n !== 'undefined' ? n : Math.random() * 1000; 19 | return { 20 | id: n, 21 | name: `Jack London ${n}`, 22 | username: `jack_london_${n}`, 23 | email: `jack_london_${n}@example.com`, 24 | }; 25 | } 26 | 27 | protected generateData(): Array { 28 | const data = []; 29 | for (let i = 0; i < BasicExampleLoadService.DATA_SIZE; i++) { 30 | data.push(this.getNewExampleObj(i)); 31 | } 32 | return data; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-editors/select-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { DefaultEditor } from './default-editor'; 4 | 5 | @Component({ 6 | selector: 'select-editor', 7 | template: ` 8 | 21 | `, 22 | }) 23 | export class SelectEditorComponent extends DefaultEditor { 24 | 25 | constructor() { 26 | super(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /projects/demo/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpClientModule } from '@angular/common/http'; 5 | import { RouterModule } from '@angular/router'; 6 | import { Ng2SmartTableModule } from 'ng2-smart-table'; 7 | 8 | import { PagesModule } from './pages/pages.module'; 9 | import { AppComponent } from './app.component'; 10 | import { routes } from './app.routes'; 11 | import { ScrollPositionDirective } from './theme/directives/scrollPosition.directive'; 12 | @NgModule({ 13 | declarations: [ 14 | AppComponent, 15 | ScrollPositionDirective, 16 | ], 17 | imports: [ 18 | BrowserModule, 19 | FormsModule, 20 | HttpClientModule, 21 | RouterModule.forRoot(routes, { useHash: true }), 22 | Ng2SmartTableModule, 23 | PagesModule, 24 | ], 25 | providers: [], 26 | bootstrap: [AppComponent], 27 | }) 28 | export class AppModule { } 29 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/demo/snippets/basic-full.md: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'basic-example-data', 5 | styles: [], 6 | template: ` 7 | 8 | ` 9 | }) 10 | export class BasicExampleDataComponent { 11 | 12 | settings = { 13 | columns: { 14 | id: { 15 | title: 'ID' 16 | }, 17 | name: { 18 | title: 'Full Name' 19 | }, 20 | username: { 21 | title: 'User Name' 22 | }, 23 | email: { 24 | title: 'Email' 25 | } 26 | } 27 | }; 28 | 29 | data = [ 30 | { 31 | id: 1, 32 | name: "Leanne Graham", 33 | username: "Bret", 34 | email: "Sincere@april.biz" 35 | }, 36 | // ... other rows here 37 | { 38 | id: 11, 39 | name: "Nicholas DuBuque", 40 | username: "Nicholas.Stanton", 41 | email: "Rey.Padberg@rosamond.biz" 42 | } 43 | ]; 44 | } 45 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/tbody/tbody.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { CellModule } from '../cell/cell.module'; 6 | 7 | import { Ng2SmartTableTbodyComponent } from './tbody.component'; 8 | import { TbodyCreateCancelComponent } from './cells/create-cancel.component'; 9 | import { TbodyEditDeleteComponent } from './cells/edit-delete.component'; 10 | import { TbodyCustomComponent } from './cells/custom.component'; 11 | 12 | const TBODY_COMPONENTS = [ 13 | TbodyCreateCancelComponent, 14 | TbodyEditDeleteComponent, 15 | TbodyCustomComponent, 16 | Ng2SmartTableTbodyComponent 17 | ]; 18 | 19 | @NgModule({ 20 | imports: [ 21 | CommonModule, 22 | FormsModule, 23 | CellModule, 24 | ], 25 | declarations: [ 26 | ...TBODY_COMPONENTS, 27 | ], 28 | exports: [ 29 | ...TBODY_COMPONENTS, 30 | ], 31 | }) 32 | export class TBodyModule { } 33 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/thead/thead.component.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 16 | 17 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/filter/filter-types/default-filter.ts: -------------------------------------------------------------------------------- 1 | import { Input, Output, EventEmitter, OnDestroy, Component } from '@angular/core'; 2 | import { Subscription } from 'rxjs'; 3 | 4 | import { Column } from '../../../lib/data-set/column'; 5 | 6 | @Component({ 7 | template: '', 8 | }) 9 | export class DefaultFilter implements Filter, OnDestroy { 10 | 11 | delay: number = 300; 12 | changesSubscription: Subscription; 13 | @Input() query: string; 14 | @Input() inputClass: string; 15 | @Input() column: Column; 16 | @Output() filter = new EventEmitter(); 17 | 18 | ngOnDestroy() { 19 | if (this.changesSubscription) { 20 | this.changesSubscription.unsubscribe(); 21 | } 22 | } 23 | 24 | setFilter() { 25 | this.filter.emit(this.query); 26 | } 27 | } 28 | 29 | export interface Filter { 30 | 31 | delay?: number; 32 | changesSubscription?: Subscription; 33 | query: string; 34 | inputClass: string; 35 | column: Column; 36 | filter: EventEmitter; 37 | } 38 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/ng2-smart-table.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | 5 | import { CellModule } from './components/cell/cell.module'; 6 | import { FilterModule } from './components/filter/filter.module'; 7 | import { PagerModule } from './components/pager/pager.module'; 8 | import { TBodyModule } from './components/tbody/tbody.module'; 9 | import { THeadModule } from './components/thead/thead.module'; 10 | 11 | import { Ng2SmartTableComponent } from './ng2-smart-table.component'; 12 | 13 | @NgModule({ 14 | imports: [ 15 | CommonModule, 16 | FormsModule, 17 | ReactiveFormsModule, 18 | CellModule, 19 | FilterModule, 20 | PagerModule, 21 | TBodyModule, 22 | THeadModule, 23 | ], 24 | declarations: [ 25 | Ng2SmartTableComponent, 26 | ], 27 | exports: [ 28 | Ng2SmartTableComponent, 29 | ], 30 | }) 31 | export class Ng2SmartTableModule { 32 | } 33 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/thead/thead.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Output, EventEmitter, OnChanges} from '@angular/core'; 2 | 3 | import { Grid } from '../../lib/grid'; 4 | import { DataSource } from '../../lib/data-source/data-source'; 5 | 6 | @Component({ 7 | selector: '[ng2-st-thead]', 8 | templateUrl: './thead.component.html', 9 | }) 10 | export class Ng2SmartTableTheadComponent implements OnChanges { 11 | 12 | @Input() grid: Grid; 13 | @Input() source: DataSource; 14 | @Input() isAllSelected: boolean; 15 | @Input() createConfirm: EventEmitter; 16 | 17 | @Output() sort = new EventEmitter(); 18 | @Output() selectAllRows = new EventEmitter(); 19 | @Output() create = new EventEmitter(); 20 | @Output() filter = new EventEmitter(); 21 | 22 | isHideHeader: boolean; 23 | isHideSubHeader: boolean; 24 | 25 | ngOnChanges() { 26 | this.isHideHeader = this.grid.getSetting('hideHeader'); 27 | this.isHideSubHeader = this.grid.getSetting('hideSubHeader'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/thead/cells/actions.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Output, EventEmitter, OnChanges } from '@angular/core'; 2 | 3 | import { Grid } from '../../../lib/grid'; 4 | 5 | @Component({ 6 | selector: 'ng2-st-actions', 7 | template: ` 8 | 11 | 14 | `, 15 | }) 16 | export class ActionsComponent implements OnChanges { 17 | 18 | @Input() grid: Grid; 19 | @Output() create = new EventEmitter(); 20 | 21 | createButtonContent: string; 22 | cancelButtonContent: string; 23 | 24 | ngOnChanges() { 25 | this.createButtonContent = this.grid.getSetting('add.createButtonContent'); 26 | this.cancelButtonContent = this.grid.getSetting('add.cancelButtonContent'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /projects/demo/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'demo'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app.title).toEqual('demo'); 23 | }); 24 | 25 | it('should render title in a h1 tag', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.debugElement.nativeElement; 29 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to demo!'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-editors/checkbox-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { DefaultEditor } from './default-editor'; 4 | 5 | @Component({ 6 | selector: 'checkbox-editor', 7 | styleUrls: ['./editor.component.scss'], 8 | template: ` 9 | 17 | `, 18 | }) 19 | export class CheckboxEditorComponent extends DefaultEditor { 20 | 21 | constructor() { 22 | super(); 23 | } 24 | 25 | onChange(event: any) { 26 | const trueVal = (this.cell.getColumn().getConfig() && this.cell.getColumn().getConfig().true) || true; 27 | const falseVal = (this.cell.getColumn().getConfig() && this.cell.getColumn().getConfig().false) || false; 28 | this.cell.newValue = event.target.checked ? trueVal : falseVal; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 akveo.com 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 | 23 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/ng2-smart-table.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | font-size: 1rem; 3 | 4 | ::ng-deep { 5 | * { 6 | box-sizing: border-box; 7 | } 8 | 9 | button, 10 | input, 11 | optgroup, 12 | select, 13 | textarea { 14 | color: inherit; 15 | font: inherit; 16 | margin: 0; 17 | } 18 | 19 | table { 20 | line-height: 1.5em; 21 | border-collapse: collapse; 22 | border-spacing: 0; 23 | display: table; 24 | width: 100%; 25 | max-width: 100%; 26 | overflow: auto; 27 | word-break: normal; 28 | word-break: keep-all; 29 | 30 | tr { 31 | th { 32 | font-weight: bold; 33 | } 34 | section { 35 | font-size: .75em; 36 | font-weight: bold; 37 | } 38 | td, 39 | th { 40 | font-size: .875em; 41 | margin: 0; 42 | padding: 0.5em 1em; 43 | } 44 | } 45 | } 46 | 47 | a { 48 | color: #1e6bb8; 49 | text-decoration: none; 50 | &:hover { 51 | text-decoration: underline; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /projects/demo/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-devkit/build-angular'], 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-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage/demo'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/server/basic-example-load.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { LocalDataSource } from 'ng2-smart-table'; 4 | import { BasicExampleLoadService } from './basic-example-load.service'; 5 | 6 | @Component({ 7 | selector: 'basic-example-load', 8 | providers: [BasicExampleLoadService], 9 | template: ` 10 | 11 | `, 12 | }) 13 | export class BasicExampleLoadComponent { 14 | 15 | source: LocalDataSource; 16 | 17 | settings = { 18 | columns: { 19 | id: { 20 | title: 'ID', 21 | editable: false, 22 | addable: false, 23 | }, 24 | 25 | 26 | name: { 27 | title: 'Full Name', 28 | }, 29 | username: { 30 | title: 'User Name', 31 | }, 32 | email: { 33 | title: 'Email', 34 | }, 35 | }, 36 | }; 37 | 38 | constructor(protected service: BasicExampleLoadService) { 39 | this.source = new LocalDataSource(); 40 | 41 | this.service.getData().then((data) => { 42 | this.source.load(data); 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /projects/demo/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | import { Ng2SmartTableModule } from 'ng2-smart-table'; 6 | 7 | import { HeaderComponent } from './components/header/header.component'; 8 | import { BasicExampleDataComponent } from './components/basic-example/basic-example-data.component'; 9 | import { BasicExampleComponent } from './components/basic-example/basic-example.component'; 10 | 11 | import { HighlightCodeDirective } from './directives/highlight.directive'; 12 | 13 | const SHARED_COMPONENTS = [ 14 | HeaderComponent, 15 | BasicExampleComponent, 16 | BasicExampleDataComponent, 17 | ]; 18 | 19 | const SHARED_DIRECTIVES = [ 20 | HighlightCodeDirective, 21 | ]; 22 | 23 | @NgModule({ 24 | imports: [ 25 | RouterModule, 26 | CommonModule, 27 | Ng2SmartTableModule, 28 | ], 29 | declarations: [ 30 | ...SHARED_COMPONENTS, 31 | ...SHARED_DIRECTIVES, 32 | ], 33 | exports: [ 34 | ...SHARED_COMPONENTS, 35 | ...SHARED_DIRECTIVES, 36 | ], 37 | }) 38 | export class SharedModule { } 39 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/tbody/cells/custom.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { Row } from '../../../lib/data-set/row'; 3 | 4 | import { Grid } from '../../../lib/grid'; 5 | 6 | @Component({ 7 | selector: 'ng2-st-tbody-custom', 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | template: ` 10 | 14 | ` 15 | }) 16 | export class TbodyCustomComponent { 17 | 18 | @Input() grid: Grid; 19 | @Input() row: Row; 20 | @Input() source: any; 21 | @Output() custom = new EventEmitter(); 22 | 23 | onCustom(action: any, event: any) { 24 | event.preventDefault(); 25 | event.stopPropagation(); 26 | 27 | this.custom.emit({ 28 | action: action.name, 29 | data: this.row.getData(), 30 | source: this.source 31 | }); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/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-devkit/build-angular'], 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-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage/ng2-smart-table'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/various/various-examples.component.html: -------------------------------------------------------------------------------- 1 |

Various examples

2 |

Confirm Action Example

3 |

4 | An example on how to confirm an action before it's applied to the table: 5 |

6 |
7 | Demo Source 8 | 9 |
10 | 11 | 12 |

Multi select

13 |

An example on how to use multi select mode:

14 |
15 | Demo Source 16 | 17 |
18 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/server/server-examples.component.html: -------------------------------------------------------------------------------- 1 |

Get data from external source example

2 |

Loading From Server Example

3 |

4 | This example shows how to pass the data loaded from some API to the table DataSource. 5 |

6 |
7 | Demo Source 8 | 9 |
10 | 11 |

Server Data Source Example

12 |

13 | An example on how to load data user Server DataSource: 14 |

15 |
16 | Demo Source 17 | 18 |
19 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/examples.routes.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { ExamplesComponent } from './examples.component'; 5 | import { FilterExamplesComponent } from './filter/filter-examples.component'; 6 | import { ServerExamplesComponent } from './server/server-examples.component'; 7 | import { CustomViewEditExamplesComponent } from './custom-edit-view/custom-edit-view-examples.component'; 8 | import { VariousExamplesComponent } from './various/various-examples.component'; 9 | 10 | export const routes: Routes = [ 11 | { 12 | path: '', 13 | component: ExamplesComponent, 14 | children: [ 15 | { 16 | path: '', 17 | redirectTo: 'using-filters', 18 | }, 19 | { 20 | path: 'using-filters', 21 | component: FilterExamplesComponent, 22 | }, 23 | { 24 | path: 'populate-from-server', 25 | component: ServerExamplesComponent, 26 | }, 27 | { 28 | path: 'custom-editors-viewers', 29 | component: CustomViewEditExamplesComponent, 30 | }, 31 | { 32 | path: 'various', 33 | component: VariousExamplesComponent, 34 | }, 35 | ], 36 | }, 37 | ]; 38 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/examples.component.scss: -------------------------------------------------------------------------------- 1 | .with-sidebar { 2 | position: relative; 3 | 4 | .main-content-body { 5 | padding-left: 16rem; 6 | } 7 | 8 | .fixed-sidebar { 9 | display: block; 10 | padding: 0 1rem; 11 | margin-top: 2rem; 12 | position: fixed; 13 | top: 0; 14 | padding-top: 290px; 15 | width: 16rem; 16 | font-size: 0.875rem; 17 | 18 | .back-top { 19 | display: none; 20 | margin-bottom: 1rem; 21 | font-weight: bold; 22 | } 23 | 24 | ul { 25 | padding-left: 1rem; 26 | list-style: none; 27 | margin-bottom: 0.875rem; 28 | } 29 | 30 | &.scrolled { 31 | position: fixed; 32 | top: 0; 33 | 34 | .back-top { 35 | display: block; 36 | } 37 | } 38 | 39 | .examples-menu { 40 | a.active { 41 | font-weight: bold; 42 | } 43 | } 44 | } 45 | } 46 | 47 | @media screen and (max-width: 64em) { 48 | .with-sidebar { 49 | .fixed-sidebar { 50 | display: none; 51 | } 52 | } 53 | } 54 | 55 | @media screen and (min-width: 42em) and (max-width: 64em) { 56 | .with-sidebar { 57 | padding: 2rem 4rem; 58 | } 59 | } 60 | 61 | @media screen and (max-width: 42em) { 62 | .with-sidebar { 63 | padding: 2rem 1rem; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/filter/filter-types/select-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild } from '@angular/core'; 2 | import { NgControl } from '@angular/forms'; 3 | import { distinctUntilChanged, debounceTime, skip } from 'rxjs/operators'; 4 | 5 | import { DefaultFilter } from './default-filter'; 6 | 7 | @Component({ 8 | selector: 'select-filter', 9 | template: ` 10 | 20 | `, 21 | }) 22 | export class SelectFilterComponent extends DefaultFilter implements OnInit { 23 | 24 | @ViewChild('inputControl', { read: NgControl, static: true }) inputControl: NgControl; 25 | 26 | constructor() { 27 | super(); 28 | } 29 | 30 | ngOnInit() { 31 | this.inputControl.valueChanges 32 | .pipe( 33 | skip(1), 34 | distinctUntilChanged(), 35 | debounceTime(this.delay) 36 | ) 37 | .subscribe((value: string) => this.setFilter()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/custom-edit-view/custom-filter.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnChanges, OnInit, SimpleChanges} from '@angular/core'; 2 | import { FormControl } from '@angular/forms'; 3 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; 4 | 5 | import { DefaultFilter } from 'ng2-smart-table'; 6 | 7 | @Component({ 8 | template: ` 9 | 16 | `, 17 | }) 18 | export class CustomFilterComponent extends DefaultFilter implements OnInit, OnChanges { 19 | inputControl = new FormControl(); 20 | 21 | constructor() { 22 | super(); 23 | } 24 | 25 | ngOnInit() { 26 | this.inputControl.valueChanges 27 | .pipe( 28 | distinctUntilChanged(), 29 | debounceTime(this.delay), 30 | ) 31 | .subscribe((value: number) => { 32 | this.query = value !== null ? this.inputControl.value.toString() : ''; 33 | this.setFilter(); 34 | }); 35 | } 36 | 37 | ngOnChanges(changes: SimpleChanges) { 38 | if (changes.query) { 39 | this.query = changes.query.currentValue; 40 | this.inputControl.setValue(this.query); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | import { Grid } from '../../lib/grid'; 4 | import { Cell } from '../../lib/data-set/cell'; 5 | import { Row } from '../../lib/data-set/row'; 6 | 7 | @Component({ 8 | selector: 'ng2-smart-table-cell', 9 | template: ` 10 | 11 | 14 | 15 | `, 16 | }) 17 | export class CellComponent { 18 | 19 | @Input() grid: Grid; 20 | @Input() row: Row; 21 | @Input() editConfirm: EventEmitter; 22 | @Input() createConfirm: EventEmitter; 23 | @Input() isNew: boolean; 24 | @Input() cell: Cell; 25 | @Input() inputClass: string = ''; 26 | @Input() mode: string = 'inline'; 27 | @Input() isInEditing: boolean = false; 28 | 29 | @Output() edited = new EventEmitter(); 30 | 31 | onEdited(event: any) { 32 | if (this.isNew) { 33 | this.grid.create(this.grid.getNewRow(), this.createConfirm); 34 | } else { 35 | this.grid.save(this.row, this.editConfirm); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-edit-mode/edit-cell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | import { Cell } from '../../../lib/data-set/cell'; 4 | 5 | @Component({ 6 | selector: 'table-cell-edit-mode', 7 | template: ` 8 |
9 | 13 | 14 | 18 | 19 |
20 | `, 21 | }) 22 | export class EditCellComponent { 23 | 24 | @Input() cell: Cell; 25 | @Input() inputClass: string = ''; 26 | 27 | @Output() edited = new EventEmitter(); 28 | 29 | onEdited(event: any): boolean { 30 | this.edited.next(event); 31 | return false; 32 | } 33 | 34 | getEditorType(): string { 35 | return this.cell.getColumn().editor && this.cell.getColumn().editor.type; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/lib/data-set/cell.ts: -------------------------------------------------------------------------------- 1 | import { Column } from './column'; 2 | import { DataSet } from './data-set'; 3 | import { Row } from './row'; 4 | 5 | export function prepareValue (value: any) { return value } 6 | 7 | export class Cell { 8 | 9 | newValue: any = ''; 10 | protected static PREPARE = prepareValue; 11 | 12 | constructor(protected value: any, protected row: Row, protected column: any, protected dataSet: DataSet) { 13 | this.newValue = value; 14 | } 15 | 16 | getColumn(): Column { 17 | return this.column; 18 | } 19 | 20 | getRow(): Row { 21 | return this.row; 22 | } 23 | 24 | getValue(): any { 25 | const valid = this.column.getValuePrepareFunction() instanceof Function; 26 | const prepare = valid ? this.column.getValuePrepareFunction() : Cell.PREPARE; 27 | return prepare.call(null, this.value, this.row.getData(), this); 28 | } 29 | 30 | setValue(value: any): any { 31 | this.newValue = value; 32 | } 33 | 34 | getId(): string { 35 | return this.getColumn().id; 36 | } 37 | 38 | getTitle(): string { 39 | return this.getColumn().title; 40 | } 41 | 42 | isEditable(): boolean { 43 | if (this.getRow().index === -1) { 44 | return this.getColumn().isAddable; 45 | } 46 | else { 47 | return this.getColumn().isEditable; 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/filter/filter-types/input-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnChanges, OnInit, SimpleChanges } from '@angular/core'; 2 | import { FormControl } from '@angular/forms'; 3 | import { debounceTime, distinctUntilChanged, skip } from 'rxjs/operators'; 4 | 5 | import { DefaultFilter } from './default-filter'; 6 | 7 | @Component({ 8 | selector: 'input-filter', 9 | template: ` 10 | 16 | `, 17 | }) 18 | export class InputFilterComponent extends DefaultFilter implements OnInit, OnChanges { 19 | 20 | inputControl = new FormControl(); 21 | 22 | constructor() { 23 | super(); 24 | } 25 | 26 | ngOnInit() { 27 | if (this.query) { 28 | this.inputControl.setValue(this.query); 29 | } 30 | this.inputControl.valueChanges 31 | .pipe( 32 | distinctUntilChanged(), 33 | debounceTime(this.delay), 34 | ) 35 | .subscribe((value: string) => { 36 | this.query = this.inputControl.value; 37 | this.setFilter(); 38 | }); 39 | } 40 | 41 | ngOnChanges(changes: SimpleChanges) { 42 | if (changes.query) { 43 | this.inputControl.setValue(this.query); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/tbody/cells/create-cancel.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, EventEmitter, OnChanges } from '@angular/core'; 2 | 3 | import { Grid } from '../../../lib/grid'; 4 | import { Row } from '../../../lib/data-set/row'; 5 | 6 | @Component({ 7 | selector: 'ng2-st-tbody-create-cancel', 8 | template: ` 9 | 11 | 13 | `, 14 | }) 15 | export class TbodyCreateCancelComponent implements OnChanges { 16 | 17 | @Input() grid: Grid; 18 | @Input() row: Row; 19 | @Input() editConfirm: EventEmitter; 20 | 21 | cancelButtonContent: string; 22 | saveButtonContent: string; 23 | 24 | onSave(event: any) { 25 | event.preventDefault(); 26 | event.stopPropagation(); 27 | 28 | this.grid.save(this.row, this.editConfirm); 29 | } 30 | 31 | onCancelEdit(event: any) { 32 | event.preventDefault(); 33 | event.stopPropagation(); 34 | 35 | this.row.isInEditing = false; 36 | } 37 | 38 | ngOnChanges() { 39 | this.saveButtonContent = this.grid.getSetting('edit.saveButtonContent'); 40 | this.cancelButtonContent = this.grid.getSetting('edit.cancelButtonContent') 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-edit-mode/default-edit.component.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 9 | 10 | 16 | 17 | 18 | 22 | 23 | 24 | 26 | 27 | 28 | 34 | 35 |
-------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/filter/default-filter.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input} from '@angular/core'; 2 | 3 | import {FilterDefault} from "./filter-default"; 4 | 5 | @Component({ 6 | selector: 'default-table-filter', 7 | template: ` 8 | 9 | 14 | 15 | 20 | 21 | 26 | 27 | 32 | 33 | 34 | `, 35 | }) 36 | export class DefaultFilterComponent extends FilterDefault { 37 | @Input() query: string; 38 | } 39 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/thead/cells/add-button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter, AfterViewInit, ElementRef, OnChanges } from '@angular/core'; 2 | 3 | import { Grid } from '../../../lib/grid'; 4 | import { DataSource } from '../../../lib/data-source/data-source'; 5 | 6 | @Component({ 7 | selector: '[ng2-st-add-button]', 8 | template: ` 9 | 11 | `, 12 | }) 13 | export class AddButtonComponent implements AfterViewInit, OnChanges { 14 | 15 | @Input() grid: Grid; 16 | @Input() source: DataSource; 17 | @Output() create = new EventEmitter(); 18 | 19 | isActionAdd: boolean; 20 | addNewButtonContent: string; 21 | 22 | constructor(private ref: ElementRef) { 23 | } 24 | 25 | ngAfterViewInit() { 26 | this.ref.nativeElement.classList.add('ng2-smart-actions-title', 'ng2-smart-actions-title-add'); 27 | } 28 | 29 | ngOnChanges() { 30 | this.isActionAdd = this.grid.getSetting('actions.add'); 31 | this.addNewButtonContent = this.grid.getSetting('add.addButtonContent'); 32 | } 33 | 34 | onAdd(event: any) { 35 | event.preventDefault(); 36 | event.stopPropagation(); 37 | if (this.grid.getSetting('mode') === 'external') { 38 | this.create.emit({ 39 | source: this.source, 40 | }); 41 | } else { 42 | this.grid.createFormShown = true; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/filter/filter.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | import { Ng2CompleterModule } from 'ng2-completer'; 5 | 6 | import { FilterComponent } from './filter.component'; 7 | import { DefaultFilterComponent } from "./default-filter.component"; 8 | import { CustomFilterComponent } from "./custom-filter.component"; 9 | import { CheckboxFilterComponent } from './filter-types/checkbox-filter.component'; 10 | import { CompleterFilterComponent } from './filter-types/completer-filter.component'; 11 | import { InputFilterComponent } from './filter-types/input-filter.component'; 12 | import { SelectFilterComponent } from './filter-types/select-filter.component'; 13 | import { DefaultFilter } from './filter-types/default-filter'; 14 | import { FilterDefault } from './filter-default'; 15 | 16 | const FILTER_COMPONENTS = [ 17 | FilterDefault, 18 | DefaultFilter, 19 | FilterComponent, 20 | DefaultFilterComponent, 21 | CustomFilterComponent, 22 | CheckboxFilterComponent, 23 | CompleterFilterComponent, 24 | InputFilterComponent, 25 | SelectFilterComponent, 26 | ]; 27 | 28 | @NgModule({ 29 | imports: [ 30 | CommonModule, 31 | FormsModule, 32 | ReactiveFormsModule, 33 | Ng2CompleterModule, 34 | ], 35 | declarations: [ 36 | ...FILTER_COMPONENTS, 37 | ], 38 | exports: [ 39 | ...FILTER_COMPONENTS, 40 | ], 41 | }) 42 | export class FilterModule { } 43 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/server/serve.data-source.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { LocalDataSource } from 'ng2-smart-table'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | @Injectable() 7 | export class CustomServerDataSource extends LocalDataSource { 8 | 9 | lastRequestCount: number = 0; 10 | 11 | constructor(protected http: HttpClient) { 12 | super(); 13 | } 14 | 15 | count(): number { 16 | return this.lastRequestCount; 17 | } 18 | 19 | getElements(): Promise { 20 | let url = 'https://jsonplaceholder.typicode.com/photos?'; 21 | 22 | if (this.sortConf) { 23 | this.sortConf.forEach((fieldConf) => { 24 | url += `_sort=${fieldConf.field}&_order=${fieldConf.direction.toUpperCase()}&`; 25 | }); 26 | } 27 | 28 | if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) { 29 | url += `_page=${this.pagingConf['page']}&_limit=${this.pagingConf['perPage']}&`; 30 | } 31 | 32 | if (this.filterConf.filters) { 33 | this.filterConf.filters.forEach((fieldConf) => { 34 | if (fieldConf['search']) { 35 | url += `${fieldConf['field']}_like=${fieldConf['search']}&`; 36 | } 37 | }); 38 | } 39 | 40 | return this.http.get(url, { observe: 'response' }) 41 | .pipe( 42 | map(res => { 43 | this.lastRequestCount = +res.headers.get('x-total-count'); 44 | return res.body; 45 | }) 46 | ).toPromise(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/ng2-smart-table.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 26 | 27 | 28 |
29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/lib/data-set/row.ts: -------------------------------------------------------------------------------- 1 | import { Cell } from './cell'; 2 | import { Column } from './column'; 3 | import { DataSet } from './data-set'; 4 | 5 | export class Row { 6 | 7 | isSelected: boolean = false; 8 | isInEditing: boolean = false; 9 | cells: Array = []; 10 | 11 | 12 | constructor(public index: number, protected data: any, protected _dataSet: DataSet) { 13 | this.process(); 14 | } 15 | 16 | getCell(column: Column): Cell { 17 | return this.cells.find(el => el.getColumn() === column); 18 | } 19 | 20 | getCells() { 21 | return this.cells; 22 | } 23 | 24 | getData(): any { 25 | return this.data; 26 | } 27 | 28 | getIsSelected(): boolean { 29 | return this.isSelected; 30 | } 31 | 32 | getNewData(): any { 33 | const values = Object.assign({}, this.data); 34 | this.getCells().forEach((cell) => values[cell.getColumn().id] = cell.newValue); 35 | return values; 36 | } 37 | 38 | setData(data: any): any { 39 | this.data = data; 40 | this.process(); 41 | } 42 | 43 | process() { 44 | this.cells = []; 45 | this._dataSet.getColumns().forEach((column: Column) => { 46 | const cell = this.createCell(column); 47 | this.cells.push(cell); 48 | }); 49 | } 50 | 51 | createCell(column: Column): Cell { 52 | const defValue = (column as any).settings.defaultValue ? (column as any).settings.defaultValue : ''; 53 | const value = typeof this.data[column.id] === 'undefined' ? defValue : this.data[column.id]; 54 | return new Cell(value, this, column, this._dataSet); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/lib/data-source/server/server-source.conf.ts: -------------------------------------------------------------------------------- 1 | export class ServerSourceConf { 2 | 3 | protected static readonly SORT_FIELD_KEY = '_sort'; 4 | protected static readonly SORT_DIR_KEY = '_order'; 5 | protected static readonly PAGER_PAGE_KEY = '_page'; 6 | protected static readonly PAGER_LIMIT_KEY = '_limit'; 7 | protected static readonly FILTER_FIELD_KEY = '#field#_like'; 8 | protected static readonly TOTAL_KEY = 'x-total-count'; 9 | protected static readonly DATA_KEY = ''; 10 | 11 | endPoint: string; 12 | 13 | sortFieldKey: string; 14 | sortDirKey: string; 15 | pagerPageKey: string; 16 | pagerLimitKey: string; 17 | filterFieldKey: string; 18 | totalKey: string; 19 | dataKey: string; 20 | 21 | constructor( 22 | { endPoint = '', sortFieldKey = '', sortDirKey = '', 23 | pagerPageKey = '', pagerLimitKey = '', filterFieldKey = '', totalKey = '', dataKey = '' } = {}) { 24 | 25 | this.endPoint = endPoint ? endPoint : ''; 26 | 27 | this.sortFieldKey = sortFieldKey ? sortFieldKey : ServerSourceConf.SORT_FIELD_KEY; 28 | this.sortDirKey = sortDirKey ? sortDirKey : ServerSourceConf.SORT_DIR_KEY; 29 | this.pagerPageKey = pagerPageKey ? pagerPageKey : ServerSourceConf.PAGER_PAGE_KEY; 30 | this.pagerLimitKey = pagerLimitKey ? pagerLimitKey : ServerSourceConf.PAGER_LIMIT_KEY; 31 | this.filterFieldKey = filterFieldKey ? filterFieldKey : ServerSourceConf.FILTER_FIELD_KEY; 32 | this.totalKey = totalKey ? totalKey : ServerSourceConf.TOTAL_KEY; 33 | this.dataKey = dataKey ? dataKey : ServerSourceConf.DATA_KEY; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-editors/completer-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { CompleterService } from 'ng2-completer'; 3 | 4 | import { DefaultEditor } from './default-editor'; 5 | 6 | @Component({ 7 | selector: 'completer-editor', 8 | template: ` 9 | 15 | 16 | `, 17 | }) 18 | export class CompleterEditorComponent extends DefaultEditor implements OnInit { 19 | 20 | completerStr: string = ''; 21 | 22 | constructor(private completerService: CompleterService) { 23 | super(); 24 | } 25 | 26 | ngOnInit() { 27 | if (this.cell.getColumn().editor && this.cell.getColumn().editor.type === 'completer') { 28 | const config = this.cell.getColumn().getConfig().completer; 29 | config.dataService = this.completerService.local(config.data, config.searchFields, config.titleField); 30 | config.dataService.descriptionField(config.descriptionField); 31 | } 32 | } 33 | 34 | onEditedCompleter(event: { title: '' }): boolean { 35 | this.cell.newValue = event.title; 36 | return false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/filter/filter-types/checkbox-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl } from '@angular/forms'; 3 | 4 | import { DefaultFilter } from './default-filter'; 5 | import { debounceTime } from 'rxjs/operators'; 6 | 7 | @Component({ 8 | selector: 'checkbox-filter', 9 | template: ` 10 | 11 | {{column.getFilterConfig()?.resetText || 'reset'}} 13 | `, 14 | }) 15 | export class CheckboxFilterComponent extends DefaultFilter implements OnInit { 16 | 17 | filterActive: boolean = false; 18 | inputControl = new FormControl(); 19 | 20 | constructor() { 21 | super(); 22 | } 23 | 24 | ngOnInit() { 25 | this.changesSubscription = this.inputControl.valueChanges 26 | .pipe(debounceTime(this.delay)) 27 | .subscribe((checked: boolean) => { 28 | this.filterActive = true; 29 | const trueVal = (this.column.getFilterConfig() && this.column.getFilterConfig().true) || true; 30 | const falseVal = (this.column.getFilterConfig() && this.column.getFilterConfig().false) || false; 31 | this.query = checked ? trueVal : falseVal; 32 | this.setFilter(); 33 | }); 34 | } 35 | 36 | resetFilter(event: any) { 37 | event.preventDefault(); 38 | this.query = ''; 39 | this.inputControl.setValue(false, { emitEvent: false }); 40 | this.filterActive = false; 41 | this.setFilter(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/thead/thead.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { FilterModule } from '../filter/filter.module'; 6 | import { CellModule } from '../cell/cell.module'; 7 | 8 | import { Ng2SmartTableTheadComponent } from './thead.component'; 9 | import { ActionsComponent } from './cells/actions.component'; 10 | import { ActionsTitleComponent } from './cells/actions-title.component'; 11 | import { AddButtonComponent } from './cells/add-button.component'; 12 | import { CheckboxSelectAllComponent } from './cells/checkbox-select-all.component'; 13 | import { ColumnTitleComponent } from './cells/column-title.component'; 14 | import { TitleComponent } from './cells/title/title.component'; 15 | import { TheadFitlersRowComponent } from './rows/thead-filters-row.component'; 16 | import { TheadFormRowComponent } from './rows/thead-form-row.component'; 17 | import { TheadTitlesRowComponent } from './rows/thead-titles-row.component'; 18 | 19 | const THEAD_COMPONENTS = [ 20 | ActionsComponent, 21 | ActionsTitleComponent, 22 | AddButtonComponent, 23 | CheckboxSelectAllComponent, 24 | ColumnTitleComponent, 25 | TitleComponent, 26 | TheadFitlersRowComponent, 27 | TheadFormRowComponent, 28 | TheadTitlesRowComponent, 29 | Ng2SmartTableTheadComponent, 30 | ]; 31 | 32 | @NgModule({ 33 | imports: [ 34 | CommonModule, 35 | FormsModule, 36 | FilterModule, 37 | CellModule, 38 | ], 39 | declarations: [ 40 | ...THEAD_COMPONENTS, 41 | ], 42 | exports: [ 43 | ...THEAD_COMPONENTS, 44 | ], 45 | }) 46 | export class THeadModule { } 47 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-edit-mode/custom-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ComponentFactoryResolver, 4 | ViewChild, 5 | ViewContainerRef, 6 | SimpleChanges, 7 | OnChanges, 8 | OnDestroy, 9 | } from '@angular/core'; 10 | 11 | import { EditCellDefault } from './edit-cell-default'; 12 | 13 | @Component({ 14 | selector: 'table-cell-custom-editor', 15 | template: ` 16 | 17 | `, 18 | }) 19 | export class CustomEditComponent extends EditCellDefault implements OnChanges, OnDestroy { 20 | 21 | customComponent: any; 22 | @ViewChild('dynamicTarget', { read: ViewContainerRef, static: true }) dynamicTarget: any; 23 | 24 | constructor(private resolver: ComponentFactoryResolver) { 25 | super(); 26 | } 27 | 28 | ngOnChanges(changes: SimpleChanges) { 29 | if (this.cell && !this.customComponent) { 30 | const componentFactory = this.resolver.resolveComponentFactory(this.cell.getColumn().editor.component); 31 | this.customComponent = this.dynamicTarget.createComponent(componentFactory); 32 | 33 | // set @Inputs and @Outputs of custom component 34 | this.customComponent.instance.cell = this.cell; 35 | this.customComponent.instance.inputClass = this.inputClass; 36 | this.customComponent.instance.onStopEditing.subscribe(() => this.onStopEditing()); 37 | this.customComponent.instance.onEdited.subscribe((event: any) => this.onEdited(event)); 38 | this.customComponent.instance.onClick.subscribe((event: any) => this.onClick(event)); 39 | } 40 | } 41 | 42 | ngOnDestroy() { 43 | if (this.customComponent) { 44 | this.customComponent.destroy(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/filter/custom-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ComponentFactoryResolver, Input, 4 | OnChanges, 5 | OnDestroy, 6 | SimpleChanges, 7 | ViewChild, 8 | ViewContainerRef 9 | } from '@angular/core'; 10 | 11 | import { FilterDefault } from './filter-default'; 12 | 13 | @Component({ 14 | selector: 'custom-table-filter', 15 | template: ``, 16 | }) 17 | export class CustomFilterComponent extends FilterDefault implements OnChanges, OnDestroy { 18 | @Input() query: string; 19 | customComponent: any; 20 | @ViewChild('dynamicTarget', { read: ViewContainerRef, static: true }) dynamicTarget: any; 21 | 22 | constructor(private resolver: ComponentFactoryResolver) { 23 | super(); 24 | } 25 | 26 | ngOnChanges(changes: SimpleChanges) { 27 | if (this.column && !this.customComponent) { 28 | const componentFactory = this.resolver.resolveComponentFactory(this.column.filter.component); 29 | this.customComponent = this.dynamicTarget.createComponent(componentFactory); 30 | 31 | // set @Inputs and @Outputs of custom component 32 | this.customComponent.instance.query = this.query; 33 | this.customComponent.instance.column = this.column; 34 | this.customComponent.instance.source = this.source; 35 | this.customComponent.instance.inputClass = this.inputClass; 36 | this.customComponent.instance.filter.subscribe((event: any) => this.onFilter(event)); 37 | } 38 | if (this.customComponent) { 39 | this.customComponent.instance.ngOnChanges(changes); 40 | } 41 | } 42 | 43 | ngOnDestroy() { 44 | if (this.customComponent) { 45 | this.customComponent.destroy(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/snippets/source-full.md: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'basic-example-source', 5 | styles: [], 6 | template: ` 7 | 8 | 9 | ` 10 | }) 11 | export class BasicExampleSourceComponent { 12 | 13 | settings = { 14 | columns: { 15 | id: { 16 | title: 'ID', 17 | filter: false 18 | }, 19 | name: { 20 | title: 'Full Name', 21 | filter: false 22 | }, 23 | username: { 24 | title: 'User Name', 25 | filter: false 26 | }, 27 | email: { 28 | title: 'Email', 29 | filter: false 30 | } 31 | } 32 | }; 33 | 34 | data = [ 35 | // ... our data here 36 | ]; 37 | 38 | source: LocalDataSource; 39 | 40 | constructor() { 41 | this.source = new LocalDataSource(this.data); 42 | } 43 | 44 | onSearch(query: string = '') { 45 | this.source.setFilter([ 46 | // fields we want to include in the search 47 | { 48 | field: 'id', 49 | search: query 50 | }, 51 | { 52 | field: 'name', 53 | search: query 54 | }, 55 | { 56 | field: 'username', 57 | search: query 58 | }, 59 | { 60 | field: 'email', 61 | search: query 62 | } 63 | ], false); 64 | // second parameter specifying whether to perform 'AND' or 'OR' search 65 | // (meaning all columns should contain search query or at least one) 66 | // 'AND' by default, so changing to 'OR' by setting false here 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell-view-mode/custom-view.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | Input, 4 | ComponentFactoryResolver, 5 | ViewChild, 6 | ViewContainerRef, 7 | OnInit, 8 | OnDestroy, 9 | } from '@angular/core'; 10 | 11 | import { Cell } from '../../../lib/data-set/cell'; 12 | import { ViewCell } from './view-cell'; 13 | 14 | @Component({ 15 | selector: 'custom-view-component', 16 | template: ` 17 | 18 | `, 19 | }) 20 | export class CustomViewComponent implements OnInit, OnDestroy { 21 | 22 | customComponent: any; 23 | @Input() cell: Cell; 24 | @ViewChild('dynamicTarget', { read: ViewContainerRef, static: true }) dynamicTarget: any; 25 | 26 | constructor(private resolver: ComponentFactoryResolver) { 27 | } 28 | 29 | ngOnInit() { 30 | if (this.cell && !this.customComponent) { 31 | this.createCustomComponent(); 32 | this.callOnComponentInit(); 33 | this.patchInstance(); 34 | } 35 | } 36 | 37 | ngOnDestroy() { 38 | if (this.customComponent) { 39 | this.customComponent.destroy(); 40 | } 41 | } 42 | 43 | protected createCustomComponent() { 44 | const componentFactory = this.resolver.resolveComponentFactory(this.cell.getColumn().renderComponent); 45 | this.customComponent = this.dynamicTarget.createComponent(componentFactory); 46 | } 47 | 48 | protected callOnComponentInit() { 49 | const onComponentInitFunction = this.cell.getColumn().getOnComponentInitFunction(); 50 | onComponentInitFunction && onComponentInitFunction(this.customComponent.instance); 51 | } 52 | 53 | protected patchInstance() { 54 | Object.assign(this.customComponent.instance, this.getPatch()); 55 | } 56 | 57 | protected getPatch(): ViewCell { 58 | return { 59 | value: this.cell.getValue(), 60 | rowData: this.cell.getRow().getData() 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/cell/cell.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { Ng2CompleterModule } from 'ng2-completer'; 5 | 6 | import { CellComponent } from './cell.component'; 7 | import { CustomEditComponent } from './cell-edit-mode/custom-edit.component'; 8 | import { DefaultEditComponent } from './cell-edit-mode/default-edit.component'; 9 | import { EditCellComponent } from './cell-edit-mode/edit-cell.component'; 10 | import { CheckboxEditorComponent } from './cell-editors/checkbox-editor.component'; 11 | import { CompleterEditorComponent } from './cell-editors/completer-editor.component'; 12 | import { InputEditorComponent } from './cell-editors/input-editor.component'; 13 | import { SelectEditorComponent } from './cell-editors/select-editor.component'; 14 | import { TextareaEditorComponent } from './cell-editors/textarea-editor.component'; 15 | import { CustomViewComponent } from './cell-view-mode/custom-view.component'; 16 | import { ViewCellComponent } from './cell-view-mode/view-cell.component'; 17 | import { EditCellDefault } from './cell-edit-mode/edit-cell-default'; 18 | import { DefaultEditor } from './cell-editors/default-editor'; 19 | 20 | const CELL_COMPONENTS = [ 21 | CellComponent, 22 | EditCellDefault, 23 | DefaultEditor, 24 | CustomEditComponent, 25 | DefaultEditComponent, 26 | EditCellComponent, 27 | CheckboxEditorComponent, 28 | CompleterEditorComponent, 29 | InputEditorComponent, 30 | SelectEditorComponent, 31 | TextareaEditorComponent, 32 | CustomViewComponent, 33 | ViewCellComponent, 34 | ]; 35 | 36 | @NgModule({ 37 | imports: [ 38 | CommonModule, 39 | FormsModule, 40 | Ng2CompleterModule, 41 | ], 42 | declarations: [ 43 | ...CELL_COMPONENTS, 44 | ], 45 | exports: [ 46 | ...CELL_COMPONENTS, 47 | ], 48 | }) 49 | export class CellModule { } 50 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/custom-edit-view/advanced-example-custom-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { CustomEditorComponent } from './custom-editor.component'; 4 | import { CustomRenderComponent } from './custom-render.component'; 5 | import { CustomFilterComponent } from './custom-filter.component'; 6 | 7 | @Component({ 8 | selector: 'advanced-example-custom-editor', 9 | template: ` 10 | 11 | `, 12 | }) 13 | export class AdvancedExamplesCustomEditorComponent { 14 | 15 | data = [ 16 | { 17 | id: 1, 18 | name: 'Leanne Graham', 19 | username: 'Bret', 20 | link: 'Google', 21 | }, 22 | { 23 | id: 2, 24 | name: 'Ervin Howell', 25 | username: 'Antonette', 26 | link: 'Ng2 Admin', 27 | }, 28 | { 29 | id: 3, 30 | name: 'Clementine Bauch', 31 | username: 'Samantha', 32 | link: 'Ng2 smart table', 33 | }, 34 | { 35 | id: 4, 36 | name: 'Patricia Lebsack', 37 | username: 'Karianne', 38 | link: 'Blur Admin', 39 | }, 40 | ]; 41 | 42 | settings = { 43 | columns: { 44 | id: { 45 | title: 'ID', 46 | filter: { 47 | type: 'custom', 48 | component: CustomFilterComponent 49 | } 50 | }, 51 | name: { 52 | title: 'Full Name', 53 | type: 'custom', 54 | renderComponent: CustomRenderComponent, 55 | }, 56 | username: { 57 | title: 'User Name', 58 | }, 59 | link: { 60 | title: 'Link', 61 | type: 'html', 62 | editor: { 63 | type: 'custom', 64 | component: CustomEditorComponent, 65 | }, 66 | }, 67 | }, 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/custom-edit-view/basic-example-custom-actions.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'basic-example-custom-actions', 5 | template: ` 6 | 7 | `, 8 | }) 9 | export class BasicExampleCustomActionsComponent { 10 | 11 | settings = { 12 | actions: { 13 | custom: [ 14 | { 15 | name: 'view', 16 | title: 'View ', 17 | }, 18 | { 19 | name: 'edit', 20 | title: 'Edit ', 21 | }, 22 | { 23 | name: 'delete', 24 | title: 'Delete ', 25 | }, 26 | { 27 | name: 'duplicate', 28 | title: 'Duplicate ', 29 | }, 30 | ], 31 | }, 32 | columns: { 33 | id: { 34 | title: 'ID', 35 | }, 36 | name: { 37 | title: 'Full Name', 38 | }, 39 | username: { 40 | title: 'User Name', 41 | }, 42 | email: { 43 | title: 'Email', 44 | } 45 | }, 46 | }; 47 | 48 | data = [ 49 | { 50 | id: 1, 51 | name: 'Leanne Graham', 52 | username: 'Bret', 53 | email: 'Sincere@april.biz', 54 | }, 55 | { 56 | id: 2, 57 | name: 'Ervin Howell', 58 | username: 'Antonette', 59 | email: 'Shanna@melissa.tv', 60 | }, 61 | { 62 | id: 3, 63 | name: 'Clementine Bauch', 64 | username: 'Samantha', 65 | email: 'Nathan@yesenia.net', 66 | }, 67 | { 68 | id: 4, 69 | name: 'Patricia Lebsack', 70 | username: 'Karianne', 71 | email: 'Julianne.OConner@kory.org', 72 | }, 73 | { 74 | id: 5, 75 | name: 'Chelsey Dietrich', 76 | username: 'Kamren', 77 | email: 'Lucio_Hettinger@annie.ca', 78 | }, 79 | ]; 80 | 81 | onCustom(event) { 82 | alert(`Custom event '${event.action}' fired on row №: ${event.data.id}`) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/thead/rows/thead-titles-row.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Output, EventEmitter, OnChanges} from '@angular/core'; 2 | 3 | import { Grid } from '../../../lib/grid'; 4 | import { DataSource } from '../../../lib/data-source/data-source'; 5 | import { Column } from "../../../lib/data-set/column"; 6 | 7 | @Component({ 8 | selector: '[ng2-st-thead-titles-row]', 9 | template: ` 10 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | `, 25 | }) 26 | export class TheadTitlesRowComponent implements OnChanges { 27 | 28 | @Input() grid: Grid; 29 | @Input() isAllSelected: boolean; 30 | @Input() source: DataSource; 31 | 32 | @Output() sort = new EventEmitter(); 33 | @Output() selectAllRows = new EventEmitter(); 34 | 35 | isMultiSelectVisible: boolean; 36 | showActionColumnLeft: boolean; 37 | showActionColumnRight: boolean; 38 | 39 | 40 | ngOnChanges() { 41 | this.isMultiSelectVisible = this.grid.isMultiSelectVisible(); 42 | this.showActionColumnLeft = this.grid.showActionColumn('left'); 43 | this.showActionColumnRight = this.grid.showActionColumn('right'); 44 | } 45 | 46 | getVisibleColumns(columns: Array): Array { 47 | return (columns || []).filter((column: Column) => !column.hide); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/custom-edit-view/custom-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core'; 2 | import { DefaultEditor } from 'ng2-smart-table'; 3 | 4 | @Component({ 5 | template: ` 6 | Name:
16 | Url: 26 |
27 | `, 28 | }) 29 | export class CustomEditorComponent extends DefaultEditor implements AfterViewInit { 30 | 31 | @ViewChild('name') name: ElementRef; 32 | @ViewChild('url') url: ElementRef; 33 | @ViewChild('htmlValue') htmlValue: ElementRef; 34 | 35 | constructor() { 36 | super(); 37 | } 38 | 39 | ngAfterViewInit() { 40 | if (this.cell.newValue !== '') { 41 | this.name.nativeElement.value = this.getUrlName(); 42 | this.url.nativeElement.value = this.getUrlHref(); 43 | } 44 | } 45 | 46 | updateValue() { 47 | const href = this.url.nativeElement.value; 48 | const name = this.name.nativeElement.value; 49 | this.cell.newValue = `${name}`; 50 | } 51 | 52 | getUrlName(): string { 53 | return this.htmlValue.nativeElement.innerText; 54 | } 55 | 56 | getUrlHref(): string { 57 | return this.htmlValue.nativeElement.querySelector('a').getAttribute('href'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/thead/rows/thead-filters-row.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Output, EventEmitter, OnChanges} from '@angular/core'; 2 | 3 | import { Grid } from '../../../lib/grid'; 4 | import { DataSource } from '../../../lib/data-source/data-source'; 5 | import { Column } from "../../../lib/data-set/column"; 6 | 7 | @Component({ 8 | selector: '[ng2-st-thead-filters-row]', 9 | template: ` 10 | 11 | 14 | 15 | 16 | 20 | 21 | 22 | 26 | 27 | `, 28 | }) 29 | export class TheadFitlersRowComponent implements OnChanges { 30 | 31 | @Input() grid: Grid; 32 | @Input() source: DataSource; 33 | 34 | @Output() create = new EventEmitter(); 35 | @Output() filter = new EventEmitter(); 36 | 37 | isMultiSelectVisible: boolean; 38 | showActionColumnLeft: boolean; 39 | showActionColumnRight: boolean; 40 | filterInputClass: string; 41 | 42 | ngOnChanges() { 43 | this.isMultiSelectVisible = this.grid.isMultiSelectVisible(); 44 | this.showActionColumnLeft = this.grid.showActionColumn('left'); 45 | this.showActionColumnRight = this.grid.showActionColumn('right'); 46 | this.filterInputClass = this.grid.getSetting('filter.inputClass'); 47 | } 48 | 49 | getVisibleColumns(columns: Array): Array { 50 | return (columns || []).filter((column: Column) => !column.hide); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |

Demo

6 | 7 |
8 | 9 |
10 | 11 |

Features

12 |
    13 |
  • Local data source (Server/API DataSource is on its way)
  • 14 |
  • Filtering
  • 15 |
  • Sorting
  • 16 |
  • Pagination
  • 17 |
  • Inline Add/Edit/Delete
  • 18 |
  • Flexible event model
  • 19 |
20 | 21 |

Is it free?

22 | 23 |

Yes, ng2-smart-table is absolutely free and MIT licensed. That basically means you can use it as you want to.

24 | 25 |

How can I support you guys?

26 | 27 |

If you have a second:

28 | 34 | 35 |

Can I hire you?

36 | 37 |

Yes! We are available for hire. Visit our homepage or simply leave us a note at contact@akveo.com. We will be happy to work with you!

38 | 39 |
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2-smart-table", 3 | "version": "1.7.2", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build:demo": "ng build demo --aot --prod", 8 | "build:lib": "ng build ng2-smart-table --prod", 9 | "build:ci": "npm run build:demo && npm run build:lib", 10 | "docs:gh-pages": "npm run build:demo -- --base-href '/ng2-smart-table/' && ngh --dir ./dist/demo", 11 | "test": "ng test", 12 | "release": "npm run build:lib && npm run publish", 13 | "publish": "npm publish --access=public dist/ng2-smart-table", 14 | "changelog": "standard-changelog -p angular", 15 | "lint": "ng lint", 16 | "e2e": "ng e2e" 17 | }, 18 | "private": true, 19 | "dependencies": { 20 | "@angular/animations": "~10.1.5", 21 | "@angular/common": "~10.1.5", 22 | "@angular/compiler": "~10.1.5", 23 | "@angular/core": "~10.1.5", 24 | "@angular/forms": "~10.1.5", 25 | "@angular/platform-browser": "~10.1.5", 26 | "@angular/platform-browser-dynamic": "~10.1.5", 27 | "@angular/router": "~10.1.5", 28 | "highlight.js": "^9.15.8", 29 | "lodash": "^4.17.10", 30 | "ng2-completer": "^9.0.1", 31 | "rxjs": "~6.5.4", 32 | "tslib": "^2.0.0", 33 | "zone.js": "~0.10.3" 34 | }, 35 | "devDependencies": { 36 | "@angular-devkit/build-angular": "~0.1001.5", 37 | "@angular-devkit/build-ng-packagr": "~0.1001.5", 38 | "@angular/cli": "~10.1.5", 39 | "@angular/compiler-cli": "~10.1.5", 40 | "@angular/language-service": "~10.1.5", 41 | "@types/jasmine": "~3.3.8", 42 | "@types/jasminewd2": "~2.0.3", 43 | "@types/node": "^12.11.1", 44 | "angular-cli-ghpages": "^0.5.3", 45 | "codelyzer": "^5.1.2", 46 | "jasmine-core": "~3.5.0", 47 | "jasmine-spec-reporter": "~5.0.0", 48 | "karma": "~5.0.0", 49 | "karma-chrome-launcher": "~3.1.0", 50 | "karma-coverage-istanbul-reporter": "~3.0.2", 51 | "karma-jasmine": "~4.0.0", 52 | "karma-jasmine-html-reporter": "^1.5.0", 53 | "ng-packagr": "^10.1.0", 54 | "protractor": "~7.0.0", 55 | "standard-changelog": "^2.0.11", 56 | "ts-node": "~7.0.0", 57 | "tslint": "~6.1.0", 58 | "typescript": "~4.0.3" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/filter/filter-types/completer-filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | import { CompleterService } from 'ng2-completer'; 4 | 5 | import { DefaultFilter } from './default-filter'; 6 | import { distinctUntilChanged, debounceTime, map } from 'rxjs/operators'; 7 | 8 | @Component({ 9 | selector: 'completer-filter', 10 | template: ` 11 | 18 | 19 | `, 20 | }) 21 | export class CompleterFilterComponent extends DefaultFilter implements OnInit { 22 | 23 | completerContent = new Subject(); 24 | 25 | constructor(private completerService: CompleterService) { 26 | super(); 27 | } 28 | 29 | ngOnInit() { 30 | const config = this.column.getFilterConfig().completer; 31 | config.dataService = this.completerService.local(config.data, config.searchFields, config.titleField); 32 | config.dataService.descriptionField(config.descriptionField); 33 | 34 | this.changesSubscription = this.completerContent 35 | .pipe( 36 | map((ev: any) => (ev && ev.title) || ev || ''), 37 | distinctUntilChanged(), 38 | debounceTime(this.delay) 39 | ) 40 | .subscribe((search: string) => { 41 | this.query = search; 42 | this.setFilter(); 43 | }); 44 | } 45 | 46 | inputTextChanged(event: string) { 47 | // workaround to trigger the search event when the home/end buttons are clicked 48 | // when this happens the [(ngModel)]="query" is set to "" but the (selected) method is not called 49 | // so here it gets called manually 50 | if (event === '') { 51 | this.completerContent.next(event); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/thead/rows/thead-form-row.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core'; 2 | 3 | import { Grid } from '../../../lib/grid'; 4 | import { Row } from '../../../lib/data-set/row'; 5 | import { Cell } from '../../../lib/data-set/cell'; 6 | 7 | @Component({ 8 | selector: '[ng2-st-thead-form-row]', 9 | template: ` 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | `, 28 | }) 29 | export class TheadFormRowComponent implements OnChanges { 30 | 31 | @Input() grid: Grid; 32 | @Input() row: Row; 33 | @Input() createConfirm: EventEmitter; 34 | 35 | @Output() create = new EventEmitter(); 36 | 37 | isMultiSelectVisible: boolean; 38 | showActionColumnLeft: boolean; 39 | showActionColumnRight: boolean; 40 | addInputClass: string; 41 | 42 | onCreate(event: any) { 43 | event.stopPropagation(); 44 | 45 | this.grid.create(this.grid.getNewRow(), this.createConfirm); 46 | } 47 | 48 | ngOnChanges(){ 49 | this.isMultiSelectVisible = this.grid.isMultiSelectVisible(); 50 | this.showActionColumnLeft = this.grid.showActionColumn('left'); 51 | this.showActionColumnRight = this.grid.showActionColumn('right'); 52 | this.addInputClass = this.grid.getSetting('add.inputClass'); 53 | } 54 | 55 | getVisibleCells(cells: Array): Array { 56 | return (cells || []).filter((cell: Cell) => !cell.getColumn().hide); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/filter/filter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnChanges, SimpleChanges } from '@angular/core'; 2 | import { FilterDefault } from './filter-default'; 3 | import { Subscription } from 'rxjs'; 4 | 5 | @Component({ 6 | selector: 'ng2-smart-table-filter', 7 | styleUrls: ['./filter.component.scss'], 8 | template: ` 9 |
10 | 16 | 17 | 23 | 24 |
25 | `, 26 | }) 27 | export class FilterComponent extends FilterDefault implements OnChanges { 28 | query: string = ''; 29 | protected dataChangedSub: Subscription; 30 | 31 | ngOnChanges(changes: SimpleChanges) { 32 | if (changes.source) { 33 | if (!changes.source.firstChange) { 34 | this.dataChangedSub.unsubscribe(); 35 | } 36 | this.dataChangedSub = this.source.onChanged().subscribe((dataChanges) => { 37 | const filterConf = this.source.getFilter(); 38 | if (filterConf && filterConf.filters && filterConf.filters.length === 0) { 39 | this.query = ''; 40 | 41 | // add a check for existing filters an set the query if one exists for this column 42 | // this covers instances where the filter is set by user code while maintaining existing functionality 43 | } else if (filterConf && filterConf.filters && filterConf.filters.length > 0) { 44 | filterConf.filters.forEach((k: any, v: any) => { 45 | if (k.field == this.column.id) { 46 | this.query = k.search; 47 | } 48 | }); 49 | } 50 | }); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /projects/demo/src/app/shared/components/basic-example/basic-example-data.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'basic-example-data', 5 | template: ` 6 | 7 | `, 8 | }) 9 | export class BasicExampleDataComponent { 10 | 11 | settings = { 12 | columns: { 13 | id: { 14 | title: 'ID', 15 | }, 16 | name: { 17 | title: 'Full Name', 18 | }, 19 | username: { 20 | title: 'User Name', 21 | }, 22 | email: { 23 | title: 'Email', 24 | }, 25 | }, 26 | }; 27 | 28 | data = [ 29 | { 30 | id: 1, 31 | name: 'Leanne Graham', 32 | username: 'Bret', 33 | email: 'Sincere@april.biz', 34 | }, 35 | { 36 | id: 2, 37 | name: 'Ervin Howell', 38 | username: 'Antonette', 39 | email: 'Shanna@melissa.tv', 40 | }, 41 | { 42 | id: 3, 43 | name: 'Clementine Bauch', 44 | username: 'Samantha', 45 | email: 'Nathan@yesenia.net', 46 | }, 47 | { 48 | id: 4, 49 | name: 'Patricia Lebsack', 50 | username: 'Karianne', 51 | email: 'Julianne.OConner@kory.org', 52 | }, 53 | { 54 | id: 5, 55 | name: 'Chelsey Dietrich', 56 | username: 'Kamren', 57 | email: 'Lucio_Hettinger@annie.ca', 58 | }, 59 | { 60 | id: 6, 61 | name: 'Mrs. Dennis Schulist', 62 | username: 'Leopoldo_Corkery', 63 | email: 'Karley_Dach@jasper.info', 64 | }, 65 | { 66 | id: 7, 67 | name: 'Kurtis Weissnat', 68 | username: 'Elwyn.Skiles', 69 | email: 'Telly.Hoeger@billy.biz', 70 | }, 71 | { 72 | id: 8, 73 | name: 'Nicholas Runolfsdottir V', 74 | username: 'Maxime_Nienow', 75 | email: 'Sherwood@rosamond.me', 76 | }, 77 | { 78 | id: 9, 79 | name: 'Glenna Reichert', 80 | username: 'Delphine', 81 | email: 'Chaim_McDermott@dana.io', 82 | }, 83 | { 84 | id: 10, 85 | name: 'Clementina DuBuque', 86 | username: 'Moriah.Stanton', 87 | email: 'Rey.Padberg@karina.biz', 88 | }, 89 | { 90 | id: 11, 91 | name: 'Nicholas DuBuque', 92 | username: 'Nicholas.Stanton', 93 | email: 'Rey.Padberg@rosamond.biz', 94 | }, 95 | ]; 96 | } 97 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/various/basic-example-multi-select.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'basic-example-multi-select', 5 | template: ` 6 | 7 | `, 8 | }) 9 | export class BasicExampleMultiSelectComponent { 10 | 11 | settings = { 12 | selectMode: 'multi', 13 | columns: { 14 | id: { 15 | title: 'ID', 16 | }, 17 | name: { 18 | title: 'Full Name', 19 | }, 20 | username: { 21 | title: 'User Name', 22 | }, 23 | email: { 24 | title: 'Email', 25 | }, 26 | }, 27 | }; 28 | 29 | data = [ 30 | { 31 | id: 1, 32 | name: 'Leanne Graham', 33 | username: 'Bret', 34 | email: 'Sincere@april.biz', 35 | }, 36 | { 37 | id: 2, 38 | name: 'Ervin Howell', 39 | username: 'Antonette', 40 | email: 'Shanna@melissa.tv', 41 | }, 42 | { 43 | id: 3, 44 | name: 'Clementine Bauch', 45 | username: 'Samantha', 46 | email: 'Nathan@yesenia.net', 47 | }, 48 | { 49 | id: 4, 50 | name: 'Patricia Lebsack', 51 | username: 'Karianne', 52 | email: 'Julianne.OConner@kory.org', 53 | }, 54 | { 55 | id: 5, 56 | name: 'Chelsey Dietrich', 57 | username: 'Kamren', 58 | email: 'Lucio_Hettinger@annie.ca', 59 | }, 60 | { 61 | id: 6, 62 | name: 'Mrs. Dennis Schulist', 63 | username: 'Leopoldo_Corkery', 64 | email: 'Karley_Dach@jasper.info', 65 | }, 66 | { 67 | id: 7, 68 | name: 'Kurtis Weissnat', 69 | username: 'Elwyn.Skiles', 70 | email: 'Telly.Hoeger@billy.biz', 71 | }, 72 | { 73 | id: 8, 74 | name: 'Nicholas Runolfsdottir V', 75 | username: 'Maxime_Nienow', 76 | email: 'Sherwood@rosamond.me', 77 | }, 78 | { 79 | id: 9, 80 | name: 'Glenna Reichert', 81 | username: 'Delphine', 82 | email: 'Chaim_McDermott@dana.io', 83 | }, 84 | { 85 | id: 10, 86 | name: 'Clementina DuBuque', 87 | username: 'Moriah.Stanton', 88 | email: 'Rey.Padberg@karina.biz', 89 | }, 90 | { 91 | id: 11, 92 | name: 'Nicholas DuBuque', 93 | username: 'Nicholas.Stanton', 94 | email: 'Rey.Padberg@rosamond.biz', 95 | }, 96 | ]; 97 | } 98 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/thead/cells/title/title.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; 2 | import { Subscription } from 'rxjs'; 3 | 4 | import { DataSource } from '../../../../lib/data-source/data-source'; 5 | import { Column } from '../../../../lib/data-set/column'; 6 | 7 | @Component({ 8 | selector: 'ng2-smart-table-title', 9 | styleUrls: ['./title.component.scss'], 10 | template: ` 11 | 15 | {{ column.title }} 16 | 17 | {{ column.title }} 18 | `, 19 | }) 20 | export class TitleComponent implements OnChanges { 21 | 22 | currentDirection = ''; 23 | @Input() column: Column; 24 | @Input() source: DataSource; 25 | @Output() sort = new EventEmitter(); 26 | 27 | protected dataChangedSub: Subscription; 28 | 29 | ngOnChanges(changes: SimpleChanges) { 30 | if (changes.source) { 31 | if (!changes.source.firstChange) { 32 | this.dataChangedSub.unsubscribe(); 33 | } 34 | this.dataChangedSub = this.source.onChanged().subscribe((dataChanges) => { 35 | const sortConf = this.source.getSort(); 36 | 37 | if (sortConf.length > 0 && sortConf[0]['field'] === this.column.id) { 38 | this.currentDirection = sortConf[0]['direction']; 39 | } else { 40 | this.currentDirection = ''; 41 | } 42 | 43 | sortConf.forEach((fieldConf: any) => { 44 | 45 | }); 46 | }); 47 | } 48 | } 49 | 50 | _sort(event: any) { 51 | event.preventDefault(); 52 | this.changeSortDirection(); 53 | this.source.setSort([ 54 | { 55 | field: this.column.id, 56 | direction: this.currentDirection, 57 | compare: this.column.getCompareFunction(), 58 | }, 59 | ]); 60 | this.sort.emit(null); 61 | } 62 | 63 | changeSortDirection(): string { 64 | if (this.currentDirection) { 65 | const newDirection = this.currentDirection === 'asc' ? 'desc' : 'asc'; 66 | this.currentDirection = newDirection; 67 | } else { 68 | this.currentDirection = this.column.sortDirection; 69 | } 70 | return this.currentDirection; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/tbody/tbody.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Output, EventEmitter, } from '@angular/core'; 2 | 3 | import { Grid } from '../../lib/grid'; 4 | import { DataSource } from '../../lib/data-source/data-source'; 5 | import { Cell } from '../../lib/data-set/cell'; 6 | 7 | @Component({ 8 | selector: '[ng2-st-tbody]', 9 | styleUrls: ['./tbody.component.scss'], 10 | templateUrl: './tbody.component.html', 11 | }) 12 | export class Ng2SmartTableTbodyComponent { 13 | 14 | @Input() grid: Grid; 15 | @Input() source: DataSource; 16 | @Input() deleteConfirm: EventEmitter; 17 | @Input() editConfirm: EventEmitter; 18 | @Input() rowClassFunction: Function; 19 | 20 | @Output() save = new EventEmitter(); 21 | @Output() cancel = new EventEmitter(); 22 | @Output() edit = new EventEmitter(); 23 | @Output() delete = new EventEmitter(); 24 | @Output() custom = new EventEmitter(); 25 | @Output() edited = new EventEmitter(); 26 | @Output() userSelectRow = new EventEmitter(); 27 | @Output() editRowSelect = new EventEmitter(); 28 | @Output() multipleSelectRow = new EventEmitter(); 29 | @Output() rowHover = new EventEmitter(); 30 | 31 | isMultiSelectVisible: boolean; 32 | showActionColumnLeft: boolean; 33 | showActionColumnRight: boolean; 34 | mode: string; 35 | editInputClass: string; 36 | isActionAdd: boolean; 37 | isActionEdit: boolean; 38 | isActionDelete: boolean; 39 | noDataMessage: boolean; 40 | 41 | get tableColumnsCount() { 42 | const actionColumns = this.isActionAdd || this.isActionEdit || this.isActionDelete ? 1 : 0; 43 | return this.grid.getColumns().length + actionColumns; 44 | } 45 | 46 | ngOnChanges() { 47 | this.isMultiSelectVisible = this.grid.isMultiSelectVisible(); 48 | this.showActionColumnLeft = this.grid.showActionColumn('left'); 49 | this.mode = this.grid.getSetting('mode'); 50 | this.editInputClass = this.grid.getSetting('edit.inputClass'); 51 | this.showActionColumnRight = this.grid.showActionColumn('right'); 52 | this.isActionAdd = this.grid.getSetting('actions.add'); 53 | this.isActionEdit = this.grid.getSetting('actions.edit'); 54 | this.isActionDelete = this.grid.getSetting('actions.delete'); 55 | this.noDataMessage = this.grid.getSetting('noDataMessage'); 56 | } 57 | 58 | getVisibleCells(cells: Array): Array { 59 | return (cells || []).filter((cell: Cell) => !cell.getColumn().hide); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/tbody/cells/edit-delete.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, Input, Output, EventEmitter, OnChanges, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | import { Grid } from '../../../lib/grid'; 4 | import { Row } from '../../../lib/data-set/row'; 5 | import { DataSource } from '../../../lib/data-source/data-source'; 6 | 7 | @Component({ 8 | selector: 'ng2-st-tbody-edit-delete', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | template: ` 11 | 13 | 15 | `, 16 | }) 17 | export class TbodyEditDeleteComponent implements OnChanges { 18 | 19 | @Input() grid: Grid; 20 | @Input() row: Row; 21 | @Input() source: DataSource; 22 | @Input() deleteConfirm: EventEmitter; 23 | @Input() editConfirm: EventEmitter; 24 | 25 | @Output() edit = new EventEmitter(); 26 | @Output() delete = new EventEmitter(); 27 | @Output() editRowSelect = new EventEmitter(); 28 | 29 | isActionEdit: boolean; 30 | isActionDelete: boolean; 31 | editRowButtonContent: string; 32 | deleteRowButtonContent: string; 33 | 34 | onEdit(event: any) { 35 | event.preventDefault(); 36 | event.stopPropagation(); 37 | 38 | this.editRowSelect.emit(this.row); 39 | 40 | if (this.grid.getSetting('mode') === 'external') { 41 | this.edit.emit({ 42 | data: this.row.getData(), 43 | source: this.source, 44 | }); 45 | } else { 46 | this.grid.edit(this.row); 47 | } 48 | } 49 | 50 | onDelete(event: any) { 51 | event.preventDefault(); 52 | event.stopPropagation(); 53 | 54 | if (this.grid.getSetting('mode') === 'external') { 55 | this.delete.emit({ 56 | data: this.row.getData(), 57 | source: this.source, 58 | }); 59 | } else { 60 | this.grid.delete(this.row, this.deleteConfirm); 61 | } 62 | } 63 | 64 | ngOnChanges(){ 65 | this.isActionEdit = this.grid.getSetting('actions.edit'); 66 | this.isActionDelete = this.grid.getSetting('actions.delete'); 67 | this.editRowButtonContent = this.grid.getSetting('edit.editButtonContent'); 68 | this.deleteRowButtonContent = this.grid.getSetting('delete.deleteButtonContent'); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/custom-edit-view/basic-example-button-view.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | import { ViewCell } from 'ng2-smart-table'; 3 | 4 | @Component({ 5 | selector: 'button-view', 6 | template: ` 7 | 8 | `, 9 | }) 10 | export class ButtonViewComponent implements ViewCell, OnInit { 11 | renderValue: string; 12 | 13 | @Input() value: string | number; 14 | @Input() rowData: any; 15 | 16 | @Output() save: EventEmitter = new EventEmitter(); 17 | 18 | ngOnInit() { 19 | this.renderValue = this.value.toString().toUpperCase(); 20 | } 21 | 22 | onClick() { 23 | this.save.emit(this.rowData); 24 | } 25 | } 26 | 27 | @Component({ 28 | selector: 'basic-example-button-view', 29 | template: ` 30 | 31 | `, 32 | }) 33 | export class BasicExampleButtonViewComponent implements OnInit { 34 | 35 | settings = { 36 | columns: { 37 | id: { 38 | title: 'ID', 39 | }, 40 | name: { 41 | title: 'Full Name', 42 | }, 43 | username: { 44 | title: 'User Name', 45 | }, 46 | email: { 47 | title: 'Email', 48 | }, 49 | button: { 50 | title: 'Button', 51 | type: 'custom', 52 | renderComponent: ButtonViewComponent, 53 | onComponentInitFunction(instance) { 54 | instance.save.subscribe(row => { 55 | alert(`${row.name} saved!`) 56 | }); 57 | } 58 | }, 59 | }, 60 | }; 61 | 62 | data = [ 63 | { 64 | id: 1, 65 | name: 'Leanne Graham', 66 | username: 'Bret', 67 | email: 'Sincere@april.biz', 68 | button: 'Button #1', 69 | }, 70 | { 71 | id: 2, 72 | name: 'Ervin Howell', 73 | username: 'Antonette', 74 | email: 'Shanna@melissa.tv', 75 | button: 'Button #2', 76 | }, 77 | { 78 | id: 3, 79 | name: 'Clementine Bauch', 80 | username: 'Samantha', 81 | email: 'Nathan@yesenia.net', 82 | button: 'Button #3', 83 | }, 84 | { 85 | id: 4, 86 | name: 'Patricia Lebsack', 87 | username: 'Karianne', 88 | email: 'Julianne.OConner@kory.org', 89 | button: 'Button #4', 90 | }, 91 | { 92 | id: 5, 93 | name: 'Chelsey Dietrich', 94 | username: 'Kamren', 95 | email: 'Lucio_Hettinger@annie.ca', 96 | button: 'Button #5', 97 | }, 98 | ]; 99 | 100 | constructor() { } 101 | 102 | ngOnInit() { 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/filter/advanced-example-filters.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'advanced-example-filters', 5 | template: ` 6 | 7 | `, 8 | }) 9 | export class AdvancedExampleFiltersComponent { 10 | 11 | data = [ 12 | { 13 | id: 4, 14 | name: 'Patricia Lebsack', 15 | email: 'Julianne.OConner@kory.org', 16 | passed: 'Yes', 17 | }, 18 | { 19 | id: 5, 20 | name: 'Chelsey Dietrich', 21 | email: 'Lucio_Hettinger@annie.ca', 22 | passed: 'No', 23 | }, 24 | { 25 | id: 6, 26 | name: 'Mrs. Dennis Schulist', 27 | email: 'Karley_Dach@jasper.info', 28 | passed: 'Yes', 29 | }, 30 | { 31 | id: 7, 32 | name: 'Kurtis Weissnat', 33 | email: 'Telly.Hoeger@billy.biz', 34 | passed: 'No', 35 | }, 36 | { 37 | id: 8, 38 | name: 'Nicholas Runolfsdottir V', 39 | email: 'Sherwood@rosamond.me', 40 | passed: 'Yes', 41 | }, 42 | { 43 | id: 9, 44 | name: 'Glenna Reichert', 45 | email: 'Chaim_McDermott@dana.io', 46 | passed: 'No', 47 | }, 48 | { 49 | id: 10, 50 | name: 'Clementina DuBuque', 51 | email: 'Rey.Padberg@karina.biz', 52 | passed: 'No', 53 | }, 54 | { 55 | id: 11, 56 | name: 'Nicholas DuBuque', 57 | email: 'Rey.Padberg@rosamond.biz', 58 | passed: 'Yes', 59 | }, 60 | ]; 61 | 62 | settings = { 63 | columns: { 64 | id: { 65 | title: 'ID', 66 | }, 67 | name: { 68 | title: 'Full Name', 69 | filter: { 70 | type: 'list', 71 | config: { 72 | selectText: 'Select...', 73 | list: [ 74 | { value: 'Glenna Reichert', title: 'Glenna Reichert' }, 75 | { value: 'Kurtis Weissnat', title: 'Kurtis Weissnat' }, 76 | { value: 'Chelsey Dietrich', title: 'Chelsey Dietrich' }, 77 | ], 78 | }, 79 | }, 80 | }, 81 | email: { 82 | title: 'Email', 83 | filter: { 84 | type: 'completer', 85 | config: { 86 | completer: { 87 | data: this.data, 88 | searchFields: 'email', 89 | titleField: 'email', 90 | }, 91 | }, 92 | }, 93 | }, 94 | passed: { 95 | title: 'Passed', 96 | filter: { 97 | type: 'checkbox', 98 | config: { 99 | true: 'Yes', 100 | false: 'No', 101 | resetText: 'clear', 102 | }, 103 | }, 104 | }, 105 | }, 106 | }; 107 | } 108 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/components/tbody/tbody.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {{ noDataMessage }} 55 | 56 | 57 | -------------------------------------------------------------------------------- /projects/demo/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/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/custom-edit-view/custom-edit-view-examples.component.html: -------------------------------------------------------------------------------- 1 |

Customized edit and view cells examples

2 |

Select, Completer and Textarea column types

3 |

4 | An example on how to use select, completer, textarea column types: 5 |

6 |
7 | Demo Source 8 | 9 |
10 |

Custom editor/renderer column type

11 |

An example on how to use a custom cell editor and/or custom cell renderer:

12 |
13 | Demo Source 14 | 15 |
16 |

When implementing a custom editor or renderer remember to add it to the entryComponents and to the declarations part of your module

17 |
18 |   {{ snippets.customEditorModule }}
19 | 
20 |

21 | For the custom cell editor:
22 | To inherit the methods needed to interact with the table you can either extend the component with the DefaultEditor class or implement the Editor interface and reproduce the same methods on your component.
23 | For the custom cell renderer:
24 | In this example the custom component is applying a .toUpperCase() to one of the columns. You can implement the ViewCell interface to make sure you are setting up your component correctly. 25 |

26 | 27 |

28 | Button View 31 |

32 |

An example on how to use custom button view:

33 |
34 | Demo Source 36 | 37 |
38 | 39 |

Custom actions

40 |

An example on how to use custom actions:

41 |
42 | Demo Source 43 | 44 |
45 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/lib/helpers.ts: -------------------------------------------------------------------------------- 1 | import { cloneDeep } from 'lodash'; 2 | 3 | /** 4 | * Extending object that entered in first argument. 5 | * 6 | * Returns extended object or false if have no target object or incorrect type. 7 | * 8 | * If you wish to clone source object (without modify it), just use empty new 9 | * object as first argument, like this: 10 | * deepExtend({}, yourObj_1, [yourObj_N]); 11 | */ 12 | export const deepExtend = function(...objects: Array): any { 13 | if (arguments.length < 1 || typeof arguments[0] !== 'object') { 14 | return false; 15 | } 16 | 17 | if (arguments.length < 2) { 18 | return arguments[0]; 19 | } 20 | 21 | const target = arguments[0]; 22 | 23 | // convert arguments to array and cut off target object 24 | const args = Array.prototype.slice.call(arguments, 1); 25 | 26 | let val, src; 27 | 28 | args.forEach((obj: any) => { 29 | // skip argument if it is array or isn't object 30 | if (typeof obj !== 'object' || Array.isArray(obj)) { 31 | return; 32 | } 33 | 34 | Object.keys(obj).forEach(function (key) { 35 | src = target[key]; // source value 36 | val = obj[key]; // new value 37 | 38 | // recursion prevention 39 | if (val === target) { 40 | return; 41 | 42 | /** 43 | * if new value isn't object then just overwrite by new value 44 | * instead of extending. 45 | */ 46 | } else if (typeof val !== 'object' || val === null) { 47 | target[key] = val; 48 | return; 49 | 50 | // just clone arrays (and recursive clone objects inside) 51 | } else if (Array.isArray(val)) { 52 | target[key] = cloneDeep(val); 53 | return; 54 | 55 | // overwrite by new value if source isn't object or array 56 | } else if (typeof src !== 'object' || src === null || Array.isArray(src)) { 57 | target[key] = deepExtend({}, val); 58 | return; 59 | 60 | // source value and new value is objects both, extending... 61 | } else { 62 | target[key] = deepExtend(src, val); 63 | return; 64 | } 65 | }); 66 | }); 67 | 68 | return target; 69 | }; 70 | 71 | export class Deferred { 72 | 73 | promise: Promise; 74 | 75 | resolve: any; 76 | reject: any; 77 | 78 | constructor() { 79 | this.promise = new Promise((resolve, reject) => { 80 | this.resolve = resolve; 81 | this.reject = reject; 82 | }); 83 | } 84 | } 85 | 86 | // getDeepFromObject({result: {data: 1}}, 'result.data', 2); // returns 1 87 | export function getDeepFromObject(object = {}, name: string, defaultValue?: any) { 88 | const keys = name.split('.'); 89 | // clone the object 90 | let level = deepExtend({}, object); 91 | keys.forEach((k) => { 92 | if (level && typeof level[k] !== 'undefined') { 93 | level = level[k]; 94 | } 95 | }); 96 | 97 | return typeof level === 'undefined' ? defaultValue : level; 98 | } 99 | 100 | export function getPageForRowIndex(index: number, perPage: number): number { 101 | // we need to add 1 to convert 0-based index to 1-based page number. 102 | return Math.floor(index / perPage) + 1; 103 | } 104 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/lib/data-set/column.ts: -------------------------------------------------------------------------------- 1 | import { DataSet } from './data-set'; 2 | 3 | export class Column { 4 | 5 | title: string = ''; 6 | type: string = ''; 7 | class: string = ''; 8 | width: string = ''; 9 | hide: boolean = false; 10 | isSortable: boolean = false; 11 | isEditable: boolean = true; 12 | isAddable: boolean = true; 13 | isFilterable: boolean = false; 14 | sortDirection: string = ''; 15 | defaultSortDirection: string = ''; 16 | editor: { type: string, config: any, component: any } = { type: '', config: {}, component: null }; 17 | filter: { type: string, config: any, component: any } = { type: '', config: {}, component: null }; 18 | renderComponent: any = null; 19 | compareFunction: Function; 20 | valuePrepareFunction: Function; 21 | filterFunction: Function; 22 | onComponentInitFunction: Function; 23 | 24 | constructor(public id: string, protected settings: any, protected dataSet: DataSet) { 25 | this.process(); 26 | } 27 | 28 | getOnComponentInitFunction(): Function { 29 | return this.onComponentInitFunction; 30 | } 31 | 32 | getCompareFunction(): Function { 33 | return this.compareFunction; 34 | } 35 | 36 | getValuePrepareFunction(): Function { 37 | return this.valuePrepareFunction; 38 | } 39 | 40 | getFilterFunction(): Function { 41 | return this.filterFunction; 42 | } 43 | 44 | getConfig(): any { 45 | return this.editor && this.editor.config; 46 | } 47 | 48 | getFilterType(): any { 49 | return this.filter && this.filter.type; 50 | } 51 | 52 | getFilterConfig(): any { 53 | return this.filter && this.filter.config; 54 | } 55 | 56 | protected process() { 57 | this.title = this.settings['title']; 58 | this.class = this.settings['class']; 59 | this.width = this.settings['width']; 60 | this.hide = !!this.settings['hide']; 61 | this.type = this.prepareType(); 62 | this.editor = this.settings['editor']; 63 | this.filter = this.settings['filter']; 64 | this.renderComponent = this.settings['renderComponent']; 65 | 66 | this.isFilterable = typeof this.settings['filter'] === 'undefined' ? true : !!this.settings['filter']; 67 | this.defaultSortDirection = ['asc', 'desc'] 68 | .indexOf(this.settings['sortDirection']) !== -1 ? this.settings['sortDirection'] : ''; 69 | this.isSortable = typeof this.settings['sort'] === 'undefined' ? true : !!this.settings['sort']; 70 | this.isEditable = typeof this.settings['editable'] === 'undefined' ? true : !!this.settings['editable']; 71 | this.isAddable=typeof this.settings['addable'] === 'undefined' ? true : !!this.settings['addable']; 72 | this.sortDirection = this.prepareSortDirection(); 73 | 74 | this.compareFunction = this.settings['compareFunction']; 75 | this.valuePrepareFunction = this.settings['valuePrepareFunction']; 76 | this.filterFunction = this.settings['filterFunction']; 77 | this.onComponentInitFunction = this.settings['onComponentInitFunction']; 78 | } 79 | 80 | prepareType(): string { 81 | return this.settings['type'] || this.determineType(); 82 | } 83 | 84 | prepareSortDirection(): string { 85 | return this.settings['sort'] === 'desc' ? 'desc' : 'asc'; 86 | } 87 | 88 | determineType(): string { 89 | // TODO: determine type by data 90 | return 'text'; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/examples.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | import { CommonModule } from '@angular/common'; 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 5 | import { HttpClientModule } from '@angular/common/http'; 6 | import { Ng2SmartTableModule } from 'ng2-smart-table'; 7 | 8 | import { SharedModule } from '../../shared/shared.module'; 9 | import { routes } from './examples.routes'; 10 | import { ExamplesComponent } from './examples.component'; 11 | import { AdvancedExampleFiltersComponent } from './filter/advanced-example-filters.component'; 12 | import { AdvancedExampleConfirmComponent } from './various/advanced-example-confirm.component'; 13 | import { AdvancedExamplesCustomEditorComponent } from './custom-edit-view/advanced-example-custom-editor.component'; 14 | import { AdvancedExamplesTypesComponent } from './custom-edit-view/advanced-example-types.component'; 15 | import { AdvancedExampleServerComponent } from './server/advanced-example-server.component'; 16 | import { BasicExampleLoadComponent } from './server/basic-example-load.component'; 17 | import { BasicExampleMultiSelectComponent } from './various/basic-example-multi-select.component'; 18 | import { CustomEditorComponent } from './custom-edit-view/custom-editor.component'; 19 | import { BasicExampleSourceComponent } from './filter/basic-example-source.component'; 20 | import { CustomRenderComponent } from './custom-edit-view/custom-render.component'; 21 | import { CustomFilterComponent } from './custom-edit-view/custom-filter.component'; 22 | import { FilterExamplesComponent } from './filter/filter-examples.component'; 23 | import { ServerExamplesComponent } from './server/server-examples.component'; 24 | import { CustomViewEditExamplesComponent } from './custom-edit-view/custom-edit-view-examples.component'; 25 | import { BasicExampleCustomActionsComponent } from './custom-edit-view/basic-example-custom-actions.component'; 26 | import { VariousExamplesComponent } from './various/various-examples.component'; 27 | 28 | import { 29 | BasicExampleButtonViewComponent, 30 | ButtonViewComponent, 31 | } from './custom-edit-view/basic-example-button-view.component'; 32 | 33 | const EXAMPLES_COMPONENTS = [ 34 | AdvancedExampleFiltersComponent, 35 | AdvancedExampleConfirmComponent, 36 | AdvancedExamplesCustomEditorComponent, 37 | AdvancedExamplesTypesComponent, 38 | AdvancedExampleServerComponent, 39 | BasicExampleLoadComponent, 40 | BasicExampleMultiSelectComponent, 41 | BasicExampleSourceComponent, 42 | CustomEditorComponent, 43 | CustomRenderComponent, 44 | CustomFilterComponent, 45 | FilterExamplesComponent, 46 | ServerExamplesComponent, 47 | CustomViewEditExamplesComponent, 48 | VariousExamplesComponent, 49 | BasicExampleButtonViewComponent, 50 | BasicExampleCustomActionsComponent, 51 | ButtonViewComponent, 52 | ]; 53 | 54 | @NgModule({ 55 | imports: [ 56 | CommonModule, 57 | HttpClientModule, 58 | FormsModule, 59 | ReactiveFormsModule, 60 | RouterModule.forChild(routes), 61 | Ng2SmartTableModule, 62 | SharedModule, 63 | ], 64 | entryComponents: [ 65 | CustomEditorComponent, 66 | CustomRenderComponent, 67 | CustomFilterComponent, 68 | ButtonViewComponent, 69 | ], 70 | declarations: [ 71 | ExamplesComponent, 72 | ...EXAMPLES_COMPONENTS, 73 | ], 74 | }) 75 | export class ExamplesModule { } 76 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/filter/basic-example-source.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { LocalDataSource } from 'ng2-smart-table'; 4 | 5 | @Component({ 6 | selector: 'basic-example-source', 7 | template: ` 8 | 9 | 10 | `, 11 | }) 12 | export class BasicExampleSourceComponent { 13 | 14 | settings = { 15 | columns: { 16 | id: { 17 | title: 'ID', 18 | filter: false, 19 | }, 20 | name: { 21 | title: 'Full Name', 22 | filter: false, 23 | }, 24 | username: { 25 | title: 'User Name', 26 | filter: false, 27 | }, 28 | email: { 29 | title: 'Email', 30 | filter: false, 31 | }, 32 | }, 33 | }; 34 | 35 | data = [ 36 | { 37 | id: 1, 38 | name: 'Leanne Graham', 39 | username: 'Bret', 40 | email: 'Sincere@april.biz', 41 | }, 42 | { 43 | id: 2, 44 | name: 'Ervin Howell', 45 | username: 'Antonette', 46 | email: 'Shanna@melissa.tv', 47 | }, 48 | { 49 | id: 3, 50 | name: 'Clementine Bauch', 51 | username: 'Samantha', 52 | email: 'Nathan@yesenia.net', 53 | }, 54 | { 55 | id: 4, 56 | name: 'Patricia Lebsack', 57 | username: 'Karianne', 58 | email: 'Julianne.OConner@kory.org', 59 | }, 60 | { 61 | id: 5, 62 | name: 'Chelsey Dietrich', 63 | username: 'Kamren', 64 | email: 'Lucio_Hettinger@annie.ca', 65 | }, 66 | { 67 | id: 6, 68 | name: 'Mrs. Dennis Schulist', 69 | username: 'Leopoldo_Corkery', 70 | email: 'Karley_Dach@jasper.info', 71 | }, 72 | { 73 | id: 7, 74 | name: 'Kurtis Weissnat', 75 | username: 'Elwyn.Skiles', 76 | email: 'Telly.Hoeger@billy.biz', 77 | }, 78 | { 79 | id: 8, 80 | name: 'Nicholas Runolfsdottir V', 81 | username: 'Maxime_Nienow', 82 | email: 'Sherwood@rosamond.me', 83 | }, 84 | { 85 | id: 9, 86 | name: 'Glenna Reichert', 87 | username: 'Delphine', 88 | email: 'Chaim_McDermott@dana.io', 89 | }, 90 | { 91 | id: 10, 92 | name: 'Clementina DuBuque', 93 | username: 'Moriah.Stanton', 94 | email: 'Rey.Padberg@karina.biz', 95 | }, 96 | { 97 | id: 11, 98 | name: 'Nicholas DuBuque', 99 | username: 'Nicholas.Stanton', 100 | email: 'Rey.Padberg@rosamond.biz', 101 | }, 102 | ]; 103 | 104 | source: LocalDataSource; 105 | 106 | constructor() { 107 | this.source = new LocalDataSource(this.data); 108 | } 109 | 110 | onSearch(query: string = '') { 111 | this.source.setFilter([ 112 | // fields we want to inclue in the search 113 | { 114 | field: 'id', 115 | search: query, 116 | }, 117 | { 118 | field: 'name', 119 | search: query, 120 | }, 121 | { 122 | field: 'username', 123 | search: query, 124 | }, 125 | { 126 | field: 'email', 127 | search: query, 128 | }, 129 | ], false); 130 | // second parameter specifying whether to perform 'AND' or 'OR' search 131 | // (meaning all columns should contain search query or at least one) 132 | // 'AND' by default, so changing to 'OR' by setting false here 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/lib/data-source/data-source.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from 'rxjs'; 2 | import { Observable } from 'rxjs'; 3 | 4 | export abstract class DataSource { 5 | 6 | protected onChangedSource = new Subject(); 7 | protected onAddedSource = new Subject(); 8 | protected onUpdatedSource = new Subject(); 9 | protected onRemovedSource = new Subject(); 10 | 11 | abstract getAll(): Promise; 12 | abstract getElements(): Promise; 13 | abstract getSort(): any; 14 | abstract getFilter(): any; 15 | abstract getPaging(): any; 16 | abstract count(): number; 17 | 18 | refresh() { 19 | this.emitOnChanged('refresh'); 20 | } 21 | 22 | load(data: Array): Promise { 23 | this.emitOnChanged('load'); 24 | return Promise.resolve(); 25 | } 26 | 27 | onChanged(): Observable { 28 | return this.onChangedSource.asObservable(); 29 | } 30 | 31 | onAdded(): Observable { 32 | return this.onAddedSource.asObservable(); 33 | } 34 | 35 | onUpdated(): Observable { 36 | return this.onUpdatedSource.asObservable(); 37 | } 38 | 39 | onRemoved(): Observable { 40 | return this.onRemovedSource.asObservable(); 41 | } 42 | 43 | prepend(element: any): Promise { 44 | this.emitOnAdded(element); 45 | this.emitOnChanged('prepend'); 46 | return Promise.resolve(); 47 | } 48 | 49 | append(element: any): Promise { 50 | this.emitOnAdded(element); 51 | this.emitOnChanged('append'); 52 | return Promise.resolve(); 53 | } 54 | 55 | add(element: any): Promise { 56 | this.emitOnAdded(element); 57 | this.emitOnChanged('add'); 58 | return Promise.resolve(); 59 | } 60 | 61 | remove(element: any): Promise { 62 | this.emitOnRemoved(element); 63 | this.emitOnChanged('remove'); 64 | return Promise.resolve(); 65 | } 66 | 67 | update(element: any, values: any): Promise { 68 | this.emitOnUpdated(element); 69 | this.emitOnChanged('update'); 70 | return Promise.resolve(); 71 | } 72 | 73 | empty(): Promise { 74 | this.emitOnChanged('empty'); 75 | return Promise.resolve(); 76 | } 77 | 78 | setSort(conf: Array, doEmit?: boolean) { 79 | if (doEmit) { 80 | this.emitOnChanged('sort'); 81 | } 82 | } 83 | 84 | setFilter(conf: Array, andOperator?: boolean, doEmit?: boolean) { 85 | if (doEmit) { 86 | this.emitOnChanged('filter'); 87 | } 88 | } 89 | 90 | addFilter(fieldConf: {}, andOperator?: boolean, doEmit?: boolean) { 91 | if (doEmit) { 92 | this.emitOnChanged('filter'); 93 | } 94 | } 95 | 96 | setPaging(page: number, perPage: number, doEmit?: boolean) { 97 | if (doEmit) { 98 | this.emitOnChanged('paging'); 99 | } 100 | } 101 | 102 | setPage(page: number, doEmit?: boolean) { 103 | if (doEmit) { 104 | this.emitOnChanged('page'); 105 | } 106 | } 107 | 108 | protected emitOnRemoved(element: any) { 109 | this.onRemovedSource.next(element); 110 | } 111 | 112 | protected emitOnUpdated(element: any) { 113 | this.onUpdatedSource.next(element); 114 | } 115 | 116 | protected emitOnAdded(element: any) { 117 | this.onAddedSource.next(element); 118 | } 119 | 120 | protected emitOnChanged(action: string) { 121 | this.getElements().then((elements) => this.onChangedSource.next({ 122 | action: action, 123 | elements: elements, 124 | paging: this.getPaging(), 125 | filter: this.getFilter(), 126 | sort: this.getSort(), 127 | })); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /projects/demo/src/app/theme/sass/_light.scss: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | Copyright (c) 2016 GitHub, Inc. 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18 | SOFTWARE. 19 | */ 20 | 21 | .pl-c /* comment */ { 22 | color: #969896; 23 | } 24 | 25 | .pl-c1 /* constant, variable.other.constant, support, meta.property-name, support.constant, support.variable, meta.module-reference, markup.raw, meta.diff.header */, 26 | .pl-s .pl-v /* string variable */ { 27 | color: #0086b3; 28 | } 29 | 30 | .pl-e /* entity */, 31 | .pl-en /* entity.name */ { 32 | color: #795da3; 33 | } 34 | 35 | .pl-smi /* variable.parameter.function, storage.modifier.package, storage.modifier.import, storage.type.java, variable.other */, 36 | .pl-s .pl-s1 /* string source */ { 37 | color: #333; 38 | } 39 | 40 | .pl-ent /* entity.name.tag */ { 41 | color: #63a35c; 42 | } 43 | 44 | .pl-k /* keyword, storage, storage.type */ { 45 | color: #a71d5d; 46 | } 47 | 48 | .pl-s /* string */, 49 | .pl-pds /* punctuation.definition.string, string.regexp.character-class */, 50 | .pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */, 51 | .pl-sr /* string.regexp */, 52 | .pl-sr .pl-cce /* string.regexp constant.character.escape */, 53 | .pl-sr .pl-sre /* string.regexp source.ruby.embedded */, 54 | .pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */ { 55 | color: #183691; 56 | } 57 | 58 | .pl-v /* variable */ { 59 | color: #ed6a43; 60 | } 61 | 62 | .pl-id /* invalid.deprecated */ { 63 | color: #b52a1d; 64 | } 65 | 66 | .pl-ii /* invalid.illegal */ { 67 | color: #f8f8f8; 68 | background-color: #b52a1d; 69 | } 70 | 71 | .pl-sr .pl-cce /* string.regexp constant.character.escape */ { 72 | font-weight: bold; 73 | color: #63a35c; 74 | } 75 | 76 | .pl-ml /* markup.list */ { 77 | color: #693a17; 78 | } 79 | 80 | .pl-mh /* markup.heading */, 81 | .pl-mh .pl-en /* markup.heading entity.name */, 82 | .pl-ms /* meta.separator */ { 83 | font-weight: bold; 84 | color: #1d3e81; 85 | } 86 | 87 | .pl-mq /* markup.quote */ { 88 | color: #008080; 89 | } 90 | 91 | .pl-mi /* markup.italic */ { 92 | font-style: italic; 93 | color: #333; 94 | } 95 | 96 | .pl-mb /* markup.bold */ { 97 | font-weight: bold; 98 | color: #333; 99 | } 100 | 101 | .pl-md /* markup.deleted, meta.diff.header.from-file */ { 102 | color: #bd2c00; 103 | background-color: #ffecec; 104 | } 105 | 106 | .pl-mi1 /* markup.inserted, meta.diff.header.to-file */ { 107 | color: #55a532; 108 | background-color: #eaffea; 109 | } 110 | 111 | .pl-mdr /* meta.diff.range */ { 112 | font-weight: bold; 113 | color: #795da3; 114 | } 115 | 116 | .pl-mo /* meta.output */ { 117 | color: #1d3e81; 118 | } 119 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "align": { 8 | "options": [ 9 | "parameters", 10 | "statements" 11 | ] 12 | }, 13 | "array-type": false, 14 | "arrow-parens": false, 15 | "arrow-return-shorthand": true, 16 | "deprecation": { 17 | "severity": "warn" 18 | }, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "curly": true, 24 | "interface-name": false, 25 | "max-classes-per-file": false, 26 | "eofline": true, 27 | "max-line-length": [ 28 | true, 29 | 140 30 | ], 31 | "import-spacing": true, 32 | "indent": { 33 | "options": [ 34 | "spaces" 35 | ] 36 | }, 37 | "member-access": false, 38 | "member-ordering": [ 39 | true, 40 | { 41 | "order": [ 42 | "static-field", 43 | "instance-field", 44 | "static-method", 45 | "instance-method" 46 | ] 47 | } 48 | ], 49 | "no-consecutive-blank-lines": false, 50 | "no-console": [ 51 | true, 52 | "debug", 53 | "info", 54 | "time", 55 | "timeEnd", 56 | "trace" 57 | ], 58 | "no-empty": false, 59 | "no-inferrable-types": [ 60 | true, 61 | "ignore-params" 62 | ], 63 | "no-non-null-assertion": true, 64 | "no-redundant-jsdoc": true, 65 | "no-switch-case-fall-through": true, 66 | "no-var-requires": false, 67 | "object-literal-key-quotes": [ 68 | true, 69 | "as-needed" 70 | ], 71 | "object-literal-sort-keys": false, 72 | "ordered-imports": false, 73 | "quotemark": [ 74 | true, 75 | "single" 76 | ], 77 | "trailing-comma": false, 78 | "component-class-suffix": true, 79 | "contextual-lifecycle": true, 80 | "directive-class-suffix": true, 81 | "no-conflicting-lifecycle": true, 82 | "no-host-metadata-property": true, 83 | "no-input-rename": true, 84 | "no-inputs-metadata-property": true, 85 | "no-output-native": true, 86 | "no-output-on-prefix": true, 87 | "no-output-rename": true, 88 | "semicolon": { 89 | "options": [ 90 | "always" 91 | ] 92 | }, 93 | "space-before-function-paren": { 94 | "options": { 95 | "anonymous": "never", 96 | "asyncArrow": "always", 97 | "constructor": "never", 98 | "method": "never", 99 | "named": "never" 100 | } 101 | }, 102 | "no-outputs-metadata-property": true, 103 | "template-banana-in-box": true, 104 | "template-no-negated-async": true, 105 | "typedef-whitespace": { 106 | "options": [ 107 | { 108 | "call-signature": "nospace", 109 | "index-signature": "nospace", 110 | "parameter": "nospace", 111 | "property-declaration": "nospace", 112 | "variable-declaration": "nospace" 113 | }, 114 | { 115 | "call-signature": "onespace", 116 | "index-signature": "onespace", 117 | "parameter": "onespace", 118 | "property-declaration": "onespace", 119 | "variable-declaration": "onespace" 120 | } 121 | ] 122 | }, 123 | "use-lifecycle-interface": true, 124 | "use-pipe-transform-interface": true, 125 | "variable-name": { 126 | "options": [ 127 | "ban-keywords", 128 | "check-format", 129 | "allow-pascal-case" 130 | ] 131 | }, 132 | "whitespace": { 133 | "options": [ 134 | "check-branch", 135 | "check-decl", 136 | "check-operator", 137 | "check-separator", 138 | "check-type", 139 | "check-typecast" 140 | ] 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /projects/demo/src/app/pages/demo/demo.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

Getting Started

5 |

6 | Hello and Welcome! 7 |

8 | 9 |

Installation

10 |

11 | The library is available as npm package, so all you need to do is to run the following command: 12 |

13 |
14 |     {{ snippets.install }}
15 |   
16 |

This command will create a record in your `package.json` file and install the package into the npm modules folder.

17 | 18 |

Examples

19 |

Minimal Setup Example

20 |

21 | First thing you need to do is to import the ng2-smart-table directives into your component. 22 |

23 |
24 |     {{ snippets.require }}
25 |   
26 |

27 | Then register it by adding to the list of directives of your module: 28 |

29 |
30 |     {{ snippets.directive }}
31 |   
32 |

33 | Now, we need to configure the table and add it into the template. The only required setting for the component to start working is a columns configuration.
34 | Let's register settings property inside of the component where we want to have the table and configure some columns (Settings documentation): 35 |

36 |
37 |     {{ snippets.settings }}
38 |   
39 |

40 | Finally let's put the ng2-smart-table component inside of the template: 41 |

42 |
43 |     {{ snippets.template }}
44 |   
45 |

46 | At this step you will have a minimally configured table which should look something like this: 47 |

48 |
49 | 50 |
51 |

52 | All functions are available by default and you don't need to configure them somehow, so you already able to add/edit/delete rows, sort or filter the table, etc. 53 |

54 |

55 | But it feels like something is missing... Right, there is no data in the table by default. To add some, let's create an array property with a list of objects in the component. Please note that object keys are same as in the columns configuration. 56 |

57 |
58 |     {{ snippets.array }}
59 |   
60 |

And pass the data to the table:

61 |
62 |     {{ snippets.dataTemplate }}
63 |   
64 |

Now you have some data in the table:

65 |
66 | 67 |
68 |

69 | That's it for a minimal setup, our final component should look like this, pretty simple, huh? 70 |

71 |
72 |     Demo Source
73 |     {{ snippets.basicFull }}
74 |   
75 | 76 |

Full component documentation you can find here.

77 |
78 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/various/advanced-example-confirm.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { LocalDataSource } from 'ng2-smart-table'; 4 | 5 | @Component({ 6 | selector: 'advance-example-comfirm', 7 | template: ` 8 | 14 | `, 15 | }) 16 | export class AdvancedExampleConfirmComponent { 17 | 18 | settings = { 19 | delete: { 20 | confirmDelete: true, 21 | }, 22 | add: { 23 | confirmCreate: true, 24 | }, 25 | edit: { 26 | confirmSave: true, 27 | }, 28 | columns: { 29 | id: { 30 | title: 'ID', 31 | }, 32 | name: { 33 | title: 'Full Name', 34 | }, 35 | username: { 36 | title: 'User Name', 37 | }, 38 | email: { 39 | title: 'Email', 40 | }, 41 | }, 42 | }; 43 | 44 | data = [ 45 | { 46 | id: 1, 47 | name: 'Leanne Graham', 48 | username: 'Bret', 49 | email: 'Sincere@april.biz', 50 | notShownField: true, 51 | }, 52 | { 53 | id: 2, 54 | name: 'Ervin Howell', 55 | username: 'Antonette', 56 | email: 'Shanna@melissa.tv', 57 | notShownField: true, 58 | }, 59 | { 60 | id: 3, 61 | name: 'Clementine Bauch', 62 | username: 'Samantha', 63 | email: 'Nathan@yesenia.net', 64 | notShownField: false, 65 | }, 66 | { 67 | id: 4, 68 | name: 'Patricia Lebsack', 69 | username: 'Karianne', 70 | email: 'Julianne.OConner@kory.org', 71 | notShownField: false, 72 | }, 73 | { 74 | id: 5, 75 | name: 'Chelsey Dietrich', 76 | username: 'Kamren', 77 | email: 'Lucio_Hettinger@annie.ca', 78 | notShownField: false, 79 | }, 80 | { 81 | id: 6, 82 | name: 'Mrs. Dennis Schulist', 83 | username: 'Leopoldo_Corkery', 84 | email: 'Karley_Dach@jasper.info', 85 | notShownField: false, 86 | }, 87 | { 88 | id: 7, 89 | name: 'Kurtis Weissnat', 90 | username: 'Elwyn.Skiles', 91 | email: 'Telly.Hoeger@billy.biz', 92 | notShownField: false, 93 | }, 94 | { 95 | id: 8, 96 | name: 'Nicholas Runolfsdottir V', 97 | username: 'Maxime_Nienow', 98 | email: 'Sherwood@rosamond.me', 99 | notShownField: true, 100 | }, 101 | { 102 | id: 9, 103 | name: 'Glenna Reichert', 104 | username: 'Delphine', 105 | email: 'Chaim_McDermott@dana.io', 106 | notShownField: false, 107 | }, 108 | { 109 | id: 10, 110 | name: 'Clementina DuBuque', 111 | username: 'Moriah.Stanton', 112 | email: 'Rey.Padberg@karina.biz', 113 | notShownField: false, 114 | }, 115 | { 116 | id: 11, 117 | name: 'Nicholas DuBuque', 118 | username: 'Nicholas.Stanton', 119 | email: 'Rey.Padberg@rosamond.biz', 120 | notShownField: true, 121 | } 122 | ]; 123 | 124 | source: LocalDataSource; 125 | 126 | constructor() { 127 | this.source = new LocalDataSource(this.data); 128 | } 129 | 130 | onDeleteConfirm(event) { 131 | if (window.confirm('Are you sure you want to delete?')) { 132 | event.confirm.resolve(); 133 | } else { 134 | event.confirm.reject(); 135 | } 136 | } 137 | 138 | onSaveConfirm(event) { 139 | if (window.confirm('Are you sure you want to save?')) { 140 | event.newData['name'] += ' + added in code'; 141 | event.confirm.resolve(event.newData); 142 | } else { 143 | event.confirm.reject(); 144 | } 145 | } 146 | 147 | onCreateConfirm(event) { 148 | if (window.confirm('Are you sure you want to create?')) { 149 | event.newData['name'] += ' + added in code'; 150 | event.confirm.resolve(event.newData); 151 | } else { 152 | event.confirm.reject(); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/lib/data-source/server/server.data-source.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpParams } from '@angular/common/http'; 2 | import { Observable } from 'rxjs'; 3 | 4 | import { LocalDataSource } from '../local/local.data-source'; 5 | import { ServerSourceConf } from './server-source.conf'; 6 | import { getDeepFromObject } from '../../helpers'; 7 | 8 | import { map } from 'rxjs/operators'; 9 | 10 | export class ServerDataSource extends LocalDataSource { 11 | 12 | protected conf: ServerSourceConf; 13 | 14 | protected lastRequestCount: number = 0; 15 | 16 | constructor(protected http: HttpClient, conf: ServerSourceConf | {} = {}) { 17 | super(); 18 | 19 | this.conf = new ServerSourceConf(conf); 20 | 21 | if (!this.conf.endPoint) { 22 | throw new Error('At least endPoint must be specified as a configuration of the server data source.'); 23 | } 24 | } 25 | 26 | count(): number { 27 | return this.lastRequestCount; 28 | } 29 | 30 | getElements(): Promise { 31 | return this.requestElements() 32 | .pipe(map(res => { 33 | this.lastRequestCount = this.extractTotalFromResponse(res); 34 | this.data = this.extractDataFromResponse(res); 35 | 36 | return this.data; 37 | })).toPromise(); 38 | } 39 | 40 | /** 41 | * Extracts array of data from server response 42 | * @param res 43 | * @returns {any} 44 | */ 45 | protected extractDataFromResponse(res: any): Array { 46 | const rawData = res.body; 47 | const data = !!this.conf.dataKey ? getDeepFromObject(rawData, this.conf.dataKey, []) : rawData; 48 | 49 | if (data instanceof Array) { 50 | return data; 51 | } 52 | 53 | throw new Error(`Data must be an array. 54 | Please check that data extracted from the server response by the key '${this.conf.dataKey}' exists and is array.`); 55 | } 56 | 57 | /** 58 | * Extracts total rows count from the server response 59 | * Looks for the count in the heders first, then in the response body 60 | * @param res 61 | * @returns {any} 62 | */ 63 | protected extractTotalFromResponse(res: any): number { 64 | if (res.headers.has(this.conf.totalKey)) { 65 | return +res.headers.get(this.conf.totalKey); 66 | } else { 67 | const rawData = res.body; 68 | return getDeepFromObject(rawData, this.conf.totalKey, 0); 69 | } 70 | } 71 | 72 | protected requestElements(): Observable { 73 | let httpParams = this.createRequesParams(); 74 | return this.http.get(this.conf.endPoint, { params: httpParams, observe: 'response' }); 75 | } 76 | 77 | protected createRequesParams(): HttpParams { 78 | let httpParams = new HttpParams(); 79 | 80 | httpParams = this.addSortRequestParams(httpParams); 81 | httpParams = this.addFilterRequestParams(httpParams); 82 | return this.addPagerRequestParams(httpParams); 83 | } 84 | 85 | protected addSortRequestParams(httpParams: HttpParams): HttpParams { 86 | if (this.sortConf) { 87 | this.sortConf.forEach((fieldConf) => { 88 | httpParams = httpParams.set(this.conf.sortFieldKey, fieldConf.field); 89 | httpParams = httpParams.set(this.conf.sortDirKey, fieldConf.direction.toUpperCase()); 90 | }); 91 | } 92 | 93 | return httpParams; 94 | } 95 | 96 | protected addFilterRequestParams(httpParams: HttpParams): HttpParams { 97 | 98 | if (this.filterConf.filters) { 99 | this.filterConf.filters.forEach((fieldConf: any) => { 100 | if (fieldConf['search']) { 101 | httpParams = httpParams.set(this.conf.filterFieldKey.replace('#field#', fieldConf['field']), fieldConf['search']); 102 | } 103 | }); 104 | } 105 | 106 | return httpParams; 107 | } 108 | 109 | protected addPagerRequestParams(httpParams: HttpParams): HttpParams { 110 | 111 | if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) { 112 | httpParams = httpParams.set(this.conf.pagerPageKey, this.pagingConf['page']); 113 | httpParams = httpParams.set(this.conf.pagerLimitKey, this.pagingConf['perPage']); 114 | } 115 | 116 | return httpParams; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/filter/filter-examples.component.html: -------------------------------------------------------------------------------- 1 |

Customized filters examples

2 |

Standalone Filter Example

3 |

4 | Say you don't need to have a filter field in the each table column or the requirements says that search field should be placed outside of the table?
5 | Fortunately this is super easy to achieve, to do this we need to slightly modify our basic component example. 6 |

7 |

Data Source

8 |

9 | First thing you need to know is that all the data operations against the table must be done using the DataSource table property. DataSource is an abstraction around your data which allows you easily modify the table data or subscribe to events to modify the table behaviour. 10 |

11 |

12 | To access the DataSource we either can take it from the table or pass it instead of our data array. Let's do the second option as it requires less code and is more demonstrative. Let's import the DataSource class by modifying the import statement: 13 |

14 |
15 |   {{ snippets.sourceRequire }}
16 | 
17 |

18 | Note that the import now contains a LocalDataSource class (the word Local here means that the data is processed locally in a browser, not on the server side).
19 | Then let's create a DataSource instance and pass our data array into it as a constructor parameter: 20 |

21 |
22 |   {{ snippets.createSource }}
23 | 
24 |

25 | Now let's pass the source to the table instead of the data array: 26 |

27 |
28 |   {{ snippets.sourceTemplate }}
29 | 
30 |

31 | At this point if you look at the result there will be no difference comparing to the first example. Basically if you pass an array to the table component first thing it will do is wrap LocalDataSource object around your array as we did here manually.
32 | Now, having the DataSource we basically could change the table data in any way we need to - filter it, sort, paginate to some page, create/delete/modify the rows. In our example we need to be able to filter the data outside of the table, firstly let's add a search filed to the template with a listener: 33 |

34 |
35 |   {{ snippets.search }}
36 | 
37 |

38 | And the listener code which asks the DataSource to filter the data: 39 |

40 |
41 |   {{ snippets.searchTable }}
42 | 
43 |

44 | Last thing, let's hide the default table filters to not conflict with our standalone one: 45 |

46 |
47 |   {{ snippets.hideFilters }}
48 | 
49 |

50 | And done! Now the table has a standalone search field: 51 |

52 |
53 | 54 |
55 |

56 | Same way you can modify the data of the table, for example by adding a row from a third party form or listening to some external event. 57 | Here's a full component code: 58 |

59 |
60 |   Demo Source
61 |   {{ snippets.sourceFull }}
62 | 
63 | 64 |

Checkbox, Select and Completer filter types

65 |

66 | An example on how to use the built-in column filter types: 67 |

68 |
69 | Demo Source 70 | 71 |
72 | -------------------------------------------------------------------------------- /projects/ng2-smart-table/src/lib/lib/data-set/data-set.ts: -------------------------------------------------------------------------------- 1 | import { Row } from './row'; 2 | import { Column } from './column'; 3 | 4 | export class DataSet { 5 | 6 | newRow: Row; 7 | 8 | protected data: Array = []; 9 | protected columns: Array = []; 10 | protected rows: Array = []; 11 | protected selectedRow: Row; 12 | protected willSelect: string; 13 | 14 | constructor(data: Array = [], protected columnSettings: Object) { 15 | this.createColumns(columnSettings); 16 | this.setData(data); 17 | 18 | this.createNewRow(); 19 | } 20 | 21 | setData(data: Array) { 22 | this.data = data; 23 | this.createRows(); 24 | } 25 | 26 | getColumns(): Array { 27 | return this.columns; 28 | } 29 | 30 | getRows(): Array { 31 | return this.rows; 32 | } 33 | 34 | getFirstRow(): Row { 35 | return this.rows[0]; 36 | } 37 | 38 | getLastRow(): Row { 39 | return this.rows[this.rows.length - 1]; 40 | } 41 | 42 | findRowByData(data: any): Row { 43 | return this.rows.find((row: Row) => row.getData() === data); 44 | } 45 | 46 | deselectAll() { 47 | this.rows.forEach((row) => { 48 | row.isSelected = false; 49 | }); 50 | // we need to clear selectedRow field because no one row selected 51 | this.selectedRow = undefined; 52 | } 53 | 54 | selectRow(row: Row): Row | undefined { 55 | const previousIsSelected = row.isSelected; 56 | this.deselectAll(); 57 | 58 | row.isSelected = !previousIsSelected; 59 | this.selectedRow = row; 60 | 61 | return this.selectedRow; 62 | } 63 | 64 | multipleSelectRow(row: Row): Row { 65 | row.isSelected = !row.isSelected; 66 | this.selectedRow = row; 67 | 68 | return this.selectedRow; 69 | } 70 | 71 | selectPreviousRow(): Row { 72 | if (this.rows.length > 0) { 73 | let index = this.selectedRow ? this.selectedRow.index : 0; 74 | if (index > this.rows.length - 1) { 75 | index = this.rows.length - 1; 76 | } 77 | this.selectRow(this.rows[index]); 78 | return this.selectedRow; 79 | } 80 | } 81 | 82 | selectFirstRow(): Row | undefined { 83 | if (this.rows.length > 0) { 84 | this.selectRow(this.rows[0]); 85 | return this.selectedRow; 86 | } 87 | } 88 | 89 | selectLastRow(): Row | undefined { 90 | if (this.rows.length > 0) { 91 | this.selectRow(this.rows[this.rows.length - 1]); 92 | return this.selectedRow; 93 | } 94 | } 95 | 96 | selectRowByIndex(index: number): Row | undefined { 97 | const rowsLength: number = this.rows.length; 98 | if (rowsLength === 0) { 99 | return; 100 | } 101 | if (!index) { 102 | this.selectFirstRow(); 103 | return this.selectedRow; 104 | } 105 | if (index > 0 && index < rowsLength) { 106 | this.selectRow(this.rows[index]); 107 | return this.selectedRow; 108 | } 109 | // we need to deselect all rows if we got an incorrect index 110 | this.deselectAll(); 111 | } 112 | 113 | willSelectFirstRow() { 114 | this.willSelect = 'first'; 115 | } 116 | 117 | willSelectLastRow() { 118 | this.willSelect = 'last'; 119 | } 120 | 121 | select(selectedRowIndex?: number): Row | undefined { 122 | if (this.getRows().length === 0) { 123 | return; 124 | } 125 | if (this.willSelect) { 126 | if (this.willSelect === 'first') { 127 | this.selectFirstRow(); 128 | } 129 | if (this.willSelect === 'last') { 130 | this.selectLastRow(); 131 | } 132 | this.willSelect = ''; 133 | } else { 134 | this.selectRowByIndex(selectedRowIndex); 135 | } 136 | 137 | return this.selectedRow; 138 | } 139 | 140 | createNewRow() { 141 | this.newRow = new Row(-1, {}, this); 142 | this.newRow.isInEditing = true; 143 | } 144 | 145 | /** 146 | * Create columns by mapping from the settings 147 | * @param settings 148 | * @private 149 | */ 150 | createColumns(settings: any) { 151 | for (const id in settings) { 152 | if (settings.hasOwnProperty(id)) { 153 | this.columns.push(new Column(id, settings[id], this)); 154 | } 155 | } 156 | } 157 | 158 | /** 159 | * Create rows based on current data prepared in data source 160 | * @private 161 | */ 162 | createRows() { 163 | this.rows = []; 164 | this.data.forEach((el, index) => { 165 | this.rows.push(new Row(index, el, this)); 166 | }); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/examples/custom-edit-view/advanced-example-types.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'advanced-example-types', 5 | template: ` 6 | 7 | `, 8 | }) 9 | export class AdvancedExamplesTypesComponent { 10 | 11 | data = [ 12 | { 13 | id: 1, 14 | name: 'Leanne Graham', 15 | username: 'Bret', 16 | email: 'Sincere@april.biz', 17 | comments: 'Lorem ipsum dolor sit amet, ex dolorem officiis convenire usu.', 18 | passed: 'Yes', 19 | }, 20 | { 21 | id: 2, 22 | name: 'Ervin Howell', 23 | username: 'Antonette', 24 | email: 'Shanna@melissa.tv', 25 | comments: `Vix iudico graecis in? Malis eirmod consectetuer duo ut? 26 | Mel an aeterno vivendum accusata, qui ne amet stet definitiones.`, 27 | passed: 'Yes', 28 | }, 29 | { 30 | id: 3, 31 | name: 'Clementine Bauch', 32 | username: 'Samantha', 33 | email: 'Nathan@yesenia.net', 34 | comments: 'Mollis latine intellegebat ei usu, veri exerci intellegebat vel cu. Eu nec ferri copiosae.', 35 | passed: 'No', 36 | }, 37 | { 38 | id: 4, 39 | name: 'Patricia Lebsack', 40 | username: 'Karianne', 41 | email: 'Julianne.OConner@kory.org', 42 | comments: 'Eu sea graece corrumpit, et tation nominavi philosophia eam, veri posidonium ex mea?', 43 | passed: 'Yes', 44 | }, 45 | { 46 | id: 5, 47 | name: 'Chelsey Dietrich', 48 | username: 'Kamren', 49 | email: 'Lucio_Hettinger@annie.ca', 50 | comments: `Quo viris appellantur an, pro id eirmod oblique iuvaret, 51 | timeam omittam comprehensam ad eam? Eos id dico gubergren, 52 | cum dicant qualisque ea, id vim ferri moderatius?`, 53 | passed: 'No', 54 | }, 55 | { 56 | id: 6, 57 | name: 'Mrs. Dennis Schulist', 58 | username: 'Leopoldo_Corkery', 59 | email: 'Karley_Dach@jasper.info', 60 | comments: 'Audire appareat sententiae qui no. Sed no rebum vitae quidam.', 61 | passed: 'No', 62 | }, 63 | { 64 | id: 7, 65 | name: 'Kurtis Weissnat', 66 | username: 'Elwyn.Skiles', 67 | email: 'Telly.Hoeger@billy.biz', 68 | comments: `Mel dicat sanctus accusata ut! Eu sit choro vituperata, 69 | qui cu quod gubergren elaboraret, mollis vulputate ex cum!`, 70 | passed: 'Yes', 71 | }, 72 | { 73 | id: 8, 74 | name: 'Nicholas Runolfsdottir V', 75 | username: 'Maxime_Nienow', 76 | email: 'Sherwood@rosamond.me', 77 | comments: 'Cu usu nostrum quaerendum, no eripuit sanctus democritum cum.', 78 | passed: 'No', 79 | }, 80 | { 81 | id: 9, 82 | name: 'Glenna Reichert', 83 | username: 'Delphine', 84 | email: 'Chaim_McDermott@dana.io', 85 | comments: 'In iisque oporteat vix, amet volutpat constituto sit ut. Habeo suavitate vis ei.', 86 | passed: 'No', 87 | }, 88 | { 89 | id: 10, 90 | name: 'Clementina DuBuque', 91 | username: 'Moriah.Stanton', 92 | email: 'Rey.Padberg@karina.biz', 93 | comments: `Lorem ipsum dolor sit amet, causae fuisset ea has, adhuc tantas interesset per id. 94 | Ne vocibus persequeris has, meis lucilius ex mea, illum labores contentiones pro in?`, 95 | passed: 'Yes', 96 | }, 97 | { 98 | id: 11, 99 | name: 'Nicholas DuBuque', 100 | username: 'Nicholas.Stanton', 101 | email: 'Rey.Padberg@rosamond.biz', 102 | comments: 'Lorem ipsum dolor sit amet, mea dolorum detraxit ea?', 103 | passed: 'No', 104 | }, 105 | ]; 106 | 107 | settings = { 108 | columns: { 109 | id: { 110 | title: 'ID', 111 | }, 112 | name: { 113 | title: 'Full Name', 114 | editor: { 115 | type: 'completer', 116 | config: { 117 | completer: { 118 | data: this.data, 119 | searchFields: 'name', 120 | titleField: 'name', 121 | descriptionField: 'email', 122 | }, 123 | }, 124 | }, 125 | }, 126 | username: { 127 | title: 'User Name', 128 | type: 'html', 129 | editor: { 130 | type: 'list', 131 | config: { 132 | list: [{ value: 'Antonette', title: 'Antonette' }, { value: 'Bret', title: 'Bret' }, { 133 | value: 'Samantha', 134 | title: 'Samantha', 135 | }], 136 | }, 137 | }, 138 | }, 139 | email: { 140 | title: 'Email', 141 | type: 'string', 142 | }, 143 | comments: { 144 | title: 'Comments', 145 | editor: { 146 | type: 'textarea', 147 | }, 148 | }, 149 | passed: { 150 | title: 'Passed', 151 | editor: { 152 | type: 'checkbox', 153 | config: { 154 | true: 'Yes', 155 | false: 'No', 156 | }, 157 | }, 158 | }, 159 | }, 160 | }; 161 | } 162 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ng2-smart-table": { 7 | "projectType": "library", 8 | "root": "projects/ng2-smart-table", 9 | "sourceRoot": "projects/ng2-smart-table/src", 10 | "prefix": "lib", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-ng-packagr:build", 14 | "options": { 15 | "tsConfig": "projects/ng2-smart-table/tsconfig.lib.json", 16 | "project": "projects/ng2-smart-table/ng-package.json" 17 | }, 18 | "configurations": { 19 | "production": { 20 | "tsConfig": "projects/ng2-smart-table/tsconfig.lib.prod.json" 21 | } 22 | } 23 | }, 24 | "test": { 25 | "builder": "@angular-devkit/build-angular:karma", 26 | "options": { 27 | "main": "projects/ng2-smart-table/src/test.ts", 28 | "tsConfig": "projects/ng2-smart-table/tsconfig.spec.json", 29 | "karmaConfig": "projects/ng2-smart-table/karma.conf.js" 30 | } 31 | }, 32 | "lint": { 33 | "builder": "@angular-devkit/build-angular:tslint", 34 | "options": { 35 | "tsConfig": [ 36 | "projects/ng2-smart-table/tsconfig.lib.json", 37 | "projects/ng2-smart-table/tsconfig.spec.json" 38 | ], 39 | "exclude": [ 40 | "**/node_modules/**" 41 | ] 42 | } 43 | } 44 | } 45 | }, 46 | "demo": { 47 | "projectType": "application", 48 | "schematics": {}, 49 | "root": "projects/demo", 50 | "sourceRoot": "projects/demo/src", 51 | "prefix": "app", 52 | "architect": { 53 | "build": { 54 | "builder": "@angular-devkit/build-angular:browser", 55 | "options": { 56 | "outputPath": "dist/demo", 57 | "index": "projects/demo/src/index.html", 58 | "main": "projects/demo/src/main.ts", 59 | "polyfills": "projects/demo/src/polyfills.ts", 60 | "tsConfig": "projects/demo/tsconfig.app.json", 61 | "aot": true, 62 | "assets": [ 63 | "projects/demo/src/favicon.ico", 64 | "projects/demo/src/assets" 65 | ], 66 | "styles": [ 67 | "node_modules/highlight.js/styles/dracula.css", 68 | "projects/demo/src/app/theme/theme.scss" 69 | ], 70 | "scripts": [] 71 | }, 72 | "configurations": { 73 | "production": { 74 | "fileReplacements": [ 75 | { 76 | "replace": "projects/demo/src/environments/environment.ts", 77 | "with": "projects/demo/src/environments/environment.prod.ts" 78 | } 79 | ], 80 | "optimization": true, 81 | "outputHashing": "all", 82 | "sourceMap": false, 83 | "extractCss": true, 84 | "namedChunks": false, 85 | "extractLicenses": true, 86 | "vendorChunk": false, 87 | "buildOptimizer": true, 88 | "budgets": [ 89 | { 90 | "type": "initial", 91 | "maximumWarning": "2mb", 92 | "maximumError": "5mb" 93 | }, 94 | { 95 | "type": "anyComponentStyle", 96 | "maximumWarning": "6kb" 97 | } 98 | ] 99 | } 100 | } 101 | }, 102 | "serve": { 103 | "builder": "@angular-devkit/build-angular:dev-server", 104 | "options": { 105 | "browserTarget": "demo:build" 106 | }, 107 | "configurations": { 108 | "production": { 109 | "browserTarget": "demo:build:production" 110 | } 111 | } 112 | }, 113 | "extract-i18n": { 114 | "builder": "@angular-devkit/build-angular:extract-i18n", 115 | "options": { 116 | "browserTarget": "demo:build" 117 | } 118 | }, 119 | "test": { 120 | "builder": "@angular-devkit/build-angular:karma", 121 | "options": { 122 | "main": "projects/demo/src/test.ts", 123 | "polyfills": "projects/demo/src/polyfills.ts", 124 | "tsConfig": "projects/demo/tsconfig.spec.json", 125 | "karmaConfig": "projects/demo/karma.conf.js", 126 | "assets": [ 127 | "projects/demo/src/favicon.ico", 128 | "projects/demo/src/assets" 129 | ], 130 | "styles": [ 131 | "projects/demo/src/styles.css" 132 | ], 133 | "scripts": [] 134 | } 135 | }, 136 | "lint": { 137 | "builder": "@angular-devkit/build-angular:tslint", 138 | "options": { 139 | "tsConfig": [ 140 | "projects/demo/tsconfig.app.json", 141 | "projects/demo/tsconfig.spec.json", 142 | "projects/demo/e2e/tsconfig.json" 143 | ], 144 | "exclude": [ 145 | "**/node_modules/**" 146 | ] 147 | } 148 | }, 149 | "e2e": { 150 | "builder": "@angular-devkit/build-angular:protractor", 151 | "options": { 152 | "protractorConfig": "projects/demo/e2e/protractor.conf.js", 153 | "devServerTarget": "demo:serve" 154 | }, 155 | "configurations": { 156 | "production": { 157 | "devServerTarget": "demo:serve:production" 158 | } 159 | } 160 | } 161 | } 162 | } 163 | }, 164 | "defaultProject": "ng2-smart-table" 165 | } 166 | --------------------------------------------------------------------------------