Compare commits
29 Commits
production
...
0.0.0
Author | SHA1 | Date | |
---|---|---|---|
71e39dd9d8 | |||
2523cdacbf | |||
8a51ebf22d | |||
e1bd8398b6 | |||
7d2f00cf4e | |||
3e544ee2ef | |||
8d3c235758 | |||
2ab53cacaa | |||
d92d0dc7a5 | |||
e110382ce5 | |||
e85c9e52fc | |||
7345e2ed27 | |||
b8960b0eb2 | |||
a1418306fa | |||
fd552d1ea8 | |||
f21e120e00 | |||
2258c12b56 | |||
fe566961ce | |||
76bb80d159 | |||
9430642b5d | |||
da9e3960e2 | |||
7ee1eda2f0 | |||
d544581b0e | |||
feda2b6c53 | |||
0fb93f723e | |||
dc6ee49467 | |||
5f5abe7fc2 | |||
bbe41ef0b5 | |||
d96ff93076 |
@ -1,46 +0,0 @@
|
|||||||
version: 2
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
docker:
|
|
||||||
- image: circleci/node:10.4-browsers
|
|
||||||
|
|
||||||
working_directory: ~/repo
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- v1-dependencies-{{ checksum "package.json" }}
|
|
||||||
- v1-dependencies-
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Install
|
|
||||||
command: yarn
|
|
||||||
|
|
||||||
- save_cache:
|
|
||||||
paths:
|
|
||||||
- node_modules
|
|
||||||
key: v1-dependencies-{{ checksum "package.json" }}
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Build
|
|
||||||
command: yarn build-datatable
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Copy README and LICENSE
|
|
||||||
command: |
|
|
||||||
cp LICENSE dist/ng-datatable/
|
|
||||||
cp README.md dist/ng-datatable/
|
|
||||||
|
|
||||||
- store_artifacts:
|
|
||||||
path: dist/ng-datatable
|
|
||||||
|
|
||||||
- deploy:
|
|
||||||
command: |
|
|
||||||
echo "${CIRCLE_BRANCH}"
|
|
||||||
if [ "${CIRCLE_BRANCH}" == "production" ]; then
|
|
||||||
yarn ci-publish
|
|
||||||
else
|
|
||||||
echo "Only the production branch is published"
|
|
||||||
fi
|
|
@ -4,7 +4,7 @@ root = true
|
|||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 4
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
48
.github/workflows/build.yaml
vendored
Normal file
48
.github/workflows/build.yaml
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
name: Build
|
||||||
|
run-name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build NPM Project
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: node:8
|
||||||
|
steps:
|
||||||
|
- name: Clone Repository
|
||||||
|
uses: ztimson/actions/clone@develop
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm i
|
||||||
|
|
||||||
|
- name: Build Project
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Upload Artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
path: dist
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Push to registry
|
||||||
|
if: ${{github.ref_name}} == "production"
|
||||||
|
uses: ztimson/actions/npm/publish@develop
|
||||||
|
|
||||||
|
tag:
|
||||||
|
name: Tag Version
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: node:8
|
||||||
|
steps:
|
||||||
|
- name: Clone Repository
|
||||||
|
uses: ztimson/actions/clone@develop
|
||||||
|
|
||||||
|
- name: Get Version Number
|
||||||
|
run: echo "VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Tag Version
|
||||||
|
uses: ztimson/actions/tag@develop
|
||||||
|
with:
|
||||||
|
tag: ${{env.VERSION}}
|
378
README.md
378
README.md
@ -1,146 +1,232 @@
|
|||||||
# ng-datatable
|
<!-- Header -->
|
||||||
|
<div id="top" align="center">
|
||||||
[](https://circleci.com/gh/ztimson/ng-datatable/tree/master)
|
<br />
|
||||||
|
|
||||||
A lightweight, feature rich table to display data. It is built with twitter bootstrap in mind but its simple table structor makes it easy to style.
|
<!-- Logo -->
|
||||||
|
<img src="https://git.zakscode.com/repo-avatars/066f56124662dc95ddacf70bdc8839b98462c885e106728b342abe8bfb5be36a" alt="Logo" width="200" height="200">
|
||||||
Features:
|
|
||||||
|
<!-- Title -->
|
||||||
- Templating columns
|
### ng-datatable
|
||||||
- Sorting
|
|
||||||
- Filtering
|
<!-- Description -->
|
||||||
- Pagination
|
**Deprecated:** Angular Table Building Library
|
||||||
- Expanding/Master Detail
|
|
||||||
- Selection Modes
|
<!-- Repo badges -->
|
||||||
- Easy CSS
|
[](https://git.zakscode.com/ztimson/ng-datatable/tags)
|
||||||
- Mobile column hiding
|
[](https://git.zakscode.com/ztimson/ng-datatable/pulls)
|
||||||
- Checkboxes
|
[](https://git.zakscode.com/ztimson/ng-datatable/issues)
|
||||||
|
|
||||||
|
<!-- Links -->
|
||||||
[View on NPM](https://www.npmjs.com/package/@ztimson/ng-datatable)
|
|
||||||
|
---
|
||||||
[View the demo here](https://stackblitz.com/edit/angular-rzq6xm)
|
<div>
|
||||||
|
<a href="https://git.zakscode.com/ztimson/ng-datatable/releases" target="_blank">Release Notes</a>
|
||||||
## Installing
|
• <a href="https://git.zakscode.com/ztimson/ng-datatable/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
|
||||||
|
• <a href="https://git.zakscode.com/ztimson/ng-datatable/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
|
||||||
1. Install package `npm install @ztimson/ng-datatable --save`
|
</div>
|
||||||
2. Import into module
|
|
||||||
|
---
|
||||||
```Typescript
|
</div>
|
||||||
import {NgDatatableModule} from '@ztimsonm/ng-datatable'
|
|
||||||
|
## Table of Contents
|
||||||
@NgModule({
|
- [ng-datatable](#top)
|
||||||
imports: [
|
- [About](#about)
|
||||||
NgDatatableModule,
|
- [Built With](#built-with)
|
||||||
...
|
- [Setup](#setup)
|
||||||
],
|
- [Production](#production)
|
||||||
...
|
- [Development](#development)
|
||||||
})
|
- [Documentation](#documentation)
|
||||||
export class AppModule { }
|
- [NgDatatableComponent](#ngdatatablecomponent)
|
||||||
```
|
- [Column](#column)
|
||||||
|
- [License](#license)
|
||||||
3. Add to template
|
|
||||||
|
## About
|
||||||
```HTML
|
|
||||||
<ng-datatable [columns]="columns" [data]="data"></ng-datatable>
|
**Deprecated**
|
||||||
```
|
|
||||||
|
|
||||||
## API
|
A lightweight, feature rich table to display data. It is built with twitter bootstrap in mind so it will be automatically styled if you have the css included but its simple table structure makes it easy to style.
|
||||||
|
|
||||||
### NgDatatableComponent
|
|
||||||
|
Features:
|
||||||
Exported As: `NgDatatableComponent`
|
|
||||||
|
- Templating columns
|
||||||
Selector: `ng-datatable`
|
- Sorting
|
||||||
|
- Filtering
|
||||||
#### Properties
|
- Pagination
|
||||||
|
- Expanding/Master Detail
|
||||||
| Name | Description |
|
- Selection Modes
|
||||||
| --------------------------------------------------------- | --------------------------------------------------------- |
|
- Easy CSS
|
||||||
| @Input() cssClass: string | Class added to the main table element |
|
- Mobile column hiding
|
||||||
| @Input() columns: Column[] | Columns to display on table |
|
- Checkboxes
|
||||||
| @Input() expandedTemplate: TemplateRef<any> | An Angular template to render expanded rows |
|
|
||||||
| @Input() mobileBreakpoint: number | Hide mobile columns past this point. Default: 768px |
|
[View on NPM](https://www.npmjs.com/package/@ztimson/ng-datatable)
|
||||||
| @Input() pageLength: number | Number of rows per page. Default 20 |
|
|
||||||
| @Input() page: number | Current page |
|
### Built With
|
||||||
| @Input() paginate: boolean | Paginate rows or display all at once. Default true |
|
[](https://angular.io/)
|
||||||
| @Input() paginateCssClass: string | Class added to the paginator
|
[](https://typescriptlang.org/)
|
||||||
| @Input() selectionMode: null/'single'/'multi' | Allow selecting none, single or multiple rows at once |
|
|
||||||
| @Input() showCheckbox: boolean | Show checkbox' for mass selecting |
|
## Setup
|
||||||
| @Input() tableLayout: 'auto'/'fixed' | CSS table layout. Defaults to 'auto' |
|
|
||||||
| @Output() filterChanged: EventEmitter<(a, b) => 1/0/-1[]> | Applied filters |
|
<details>
|
||||||
| @Output() pageChanged: EventEmitter<number> | New page |
|
<summary>
|
||||||
| @Output() selectionChanged: EventEmitter<any[]> | Selected rows |
|
<h3 id="production" style="display: inline">
|
||||||
| pagedData: any[] | Array of rows on current page after sorting and filtering |
|
Production
|
||||||
| processedData: any[] | Array of remaining rows after sorting and filtering |
|
</h3>
|
||||||
| selectedRows: Set<number> | Index numbers of rows currently selected |
|
</summary>
|
||||||
|
|
||||||
#### Methods
|
#### Prerequisites
|
||||||
|
- [Node.js](https://nodejs.org/en/download)
|
||||||
##### addFilter(...filters)
|
|
||||||
|
#### Instructions
|
||||||
Add function to filter rows by.
|
1. Install package `npm install @ztimson/ng-datatable --save`
|
||||||
|
2. Import into module
|
||||||
| paramater | Description |
|
|
||||||
| ----------------------------------- | ------------------------- |
|
```Typescript
|
||||||
| ...filters: (el, i, arr) => boolean | Filters to add to dataset |
|
import {NgDatatableModule} from '@ztimsonm/ng-datatable'
|
||||||
|
|
||||||
##### changePage(page)
|
@NgModule({
|
||||||
|
imports: [
|
||||||
Change the current selected page.
|
NgDatatableModule,
|
||||||
|
...
|
||||||
| paramater | Description |
|
],
|
||||||
| ------------ | ----------------- |
|
...
|
||||||
| page: number | Page to change to |
|
})
|
||||||
|
export class AppModule { }
|
||||||
##### clearFilters(update)
|
```
|
||||||
|
|
||||||
Clear filters being applied to. Update can be set to false to prevent instant filtering in cases where you may be applying filters right after.
|
3. Add to template
|
||||||
|
|
||||||
| paramater | Description |
|
```HTML
|
||||||
| --------------- | ----------------------- |
|
<ng-datatable [columns]="columns" [data]="data"></ng-datatable>
|
||||||
| update: boolean | Clear filters imedietly |
|
```
|
||||||
|
</details>
|
||||||
##### clearSelected()
|
|
||||||
|
<details>
|
||||||
Clear all selected rows.
|
<summary>
|
||||||
|
<h3 id="development" style="display: inline">
|
||||||
##### selectAll()
|
Development
|
||||||
|
</h3>
|
||||||
Select all rows. Ignores pagination but not filtering.
|
</summary>
|
||||||
|
|
||||||
##### sort(columnIndex: number, desc?: boolean)
|
#### Prerequisites
|
||||||
|
- [Node.js](https://nodejs.org/en/download)
|
||||||
Sort the grid by column index
|
|
||||||
|
#### Instructions
|
||||||
| paramater | Description |
|
1. Install the dependencies: `npm install`
|
||||||
| ------------------- | ---------------------------- |
|
2. Start the Angular server: `npm run start`
|
||||||
| columnIndex: number | Column index to sort by |
|
3. Open [http://localhost:4200](http://localhost:4200)
|
||||||
| desc: boolean | Sort ascending or descending |
|
|
||||||
|
</details>
|
||||||
##### updateSelected(index)
|
|
||||||
|
## Documentation
|
||||||
Virtually click on row causing selection/expanding/collapsing
|
|
||||||
|
### NgDatatableComponent
|
||||||
| paramater | Description |
|
|
||||||
| ------------- | ------------- |
|
Exported As: `NgDatatableComponent`
|
||||||
| index: number | Row to select |
|
|
||||||
|
Selector: `ng-datatable`
|
||||||
### Column
|
|
||||||
|
#### Properties
|
||||||
Exported As: `Column`
|
|
||||||
|
| Name | Description |
|
||||||
#### Properties
|
| --------------------------------------------------------- | --------------------------------------------------------- |
|
||||||
|
| @Input() cssClass: string | Class added to the main table element |
|
||||||
| Name | Description |
|
| @Input() columns: Column[] | Columns to display on table |
|
||||||
| -------------------------- | ----------------------------------- |
|
| @Input() expandedTemplate: TemplateRef<any> | An Angular template to render expanded rows |
|
||||||
| cssClass: string | Style to add to column header |
|
| @Input() mobileBreakpoint: number | Hide mobile columns past this point. Default: 768px |
|
||||||
| hide: boolean | Hide column |
|
| @Input() pageLength: number | Number of rows per page. Default 20 |
|
||||||
| hideMobile: boolean | Hide column on mobile devices |
|
| @Input() page: number | Current page |
|
||||||
| initialSort: 'asc'/'desc' | Sort the column initially |
|
| @Input() paginate: boolean | Paginate rows or display all at once. Default true |
|
||||||
| label: string | Column header label |
|
| @Input() paginateCssClass: string | Class added to the paginator |
|
||||||
| property: string | Property to display in dot notation |
|
| @Input() selectionMode: null/'single'/'multi' | Allow selecting none, single or multiple rows at once |
|
||||||
| sort: boolean | Enable/Disable sorting |
|
| @Input() showCheckbox: boolean | Show checkbox' for mass selecting |
|
||||||
| sortFn: (a, b) => 1/0/-1 | Custom function to sort rows by |
|
| @Input() tableLayout: 'auto'/'fixed' | CSS table layout. Defaults to 'auto' |
|
||||||
| template: TemplateRef<any> | Template to render row with |
|
| @Output() filterChanged: EventEmitter<(a, b) => 1/0/-1[]> | Applied filters |
|
||||||
| width: number/string | CSS width property |
|
| @Output() finished: EventEmitter<any[]> | Emits when finished processing data |
|
||||||
|
| @Output() pageChanged: EventEmitter<number> | New page |
|
||||||
|
| @Output() processing: EventEmitter<any[]> | Emits when processing begins |
|
||||||
|
| @Output() selectionChanged: EventEmitter<any[]> | Selected rows |
|
||||||
|
| pagedData: any[] | Array of rows on current page after sorting and filtering |
|
||||||
|
| processedData: any[] | Array of remaining rows after sorting and filtering |
|
||||||
|
| selectedRows: Set<number> | Index numbers of rows currently selected |
|
||||||
|
|
||||||
|
#### Methods
|
||||||
|
|
||||||
|
##### addFilter(...filters)
|
||||||
|
|
||||||
|
Add function to filter rows by.
|
||||||
|
|
||||||
|
| paramater | Description |
|
||||||
|
| ----------------------------------- | ------------------------- |
|
||||||
|
| ...filters: (el, i, arr) => boolean | Filters to add to dataset |
|
||||||
|
|
||||||
|
##### changePage(page)
|
||||||
|
|
||||||
|
Change the current selected page.
|
||||||
|
|
||||||
|
| paramater | Description |
|
||||||
|
| ------------ | ----------------- |
|
||||||
|
| page: number | Page to change to |
|
||||||
|
|
||||||
|
##### clearFilters(update)
|
||||||
|
|
||||||
|
Clear filters being applied to. Update can be set to false to prevent instant filtering in cases where you may be applying filters right after.
|
||||||
|
|
||||||
|
| paramater | Description |
|
||||||
|
| --------------- | ----------------------- |
|
||||||
|
| update: boolean | Clear filters imedietly |
|
||||||
|
|
||||||
|
##### clearSelected()
|
||||||
|
|
||||||
|
Clear all selected rows.
|
||||||
|
|
||||||
|
#### refresh()
|
||||||
|
|
||||||
|
Refreshes the grid
|
||||||
|
|
||||||
|
##### selectAll()
|
||||||
|
|
||||||
|
Select all rows. Ignores pagination but not filtering.
|
||||||
|
|
||||||
|
##### sort(columnIndex: number, desc?: boolean)
|
||||||
|
|
||||||
|
Sort the grid by column index
|
||||||
|
|
||||||
|
| paramater | Description |
|
||||||
|
| ------------------- | ---------------------------- |
|
||||||
|
| columnIndex: number | Column index to sort by |
|
||||||
|
| desc: boolean | Sort ascending or descending |
|
||||||
|
|
||||||
|
##### updateSelected(index)
|
||||||
|
|
||||||
|
Virtually click on row causing selection/expanding/collapsing
|
||||||
|
|
||||||
|
| paramater | Description |
|
||||||
|
| ------------- | ------------- |
|
||||||
|
| index: number | Row to select |
|
||||||
|
|
||||||
|
### Column
|
||||||
|
|
||||||
|
Exported As: `Column`
|
||||||
|
|
||||||
|
#### Properties
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
| -------------------------- | ----------------------------------- |
|
||||||
|
| cssClass: string | Style to add to column header |
|
||||||
|
| hide: boolean | Hide column |
|
||||||
|
| hideMobile: boolean | Hide column on mobile devices |
|
||||||
|
| initialSort: 'asc'/'desc' | Sort the column initially |
|
||||||
|
| label: string | Column header label |
|
||||||
|
| property: string | Property to display in dot notation |
|
||||||
|
| sort: boolean | Enable/Disable sorting |
|
||||||
|
| sortFn: (a, b) => 1/0/-1 | Custom function to sort rows by |
|
||||||
|
| template: TemplateRef<any> | Template to render row with |
|
||||||
|
| width: number/string | CSS width property |
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
Copyright © 2023 Zakary Timson | Available under the Apache 2.0 License
|
||||||
|
|
||||||
|
See the [license](./LICENSE) for more information.
|
||||||
|
82
angular.json
82
angular.json
@ -17,9 +17,8 @@
|
|||||||
"index": "src/index.html",
|
"index": "src/index.html",
|
||||||
"main": "src/main.ts",
|
"main": "src/main.ts",
|
||||||
"polyfills": "src/polyfills.ts",
|
"polyfills": "src/polyfills.ts",
|
||||||
"tsConfig": "src/tsconfig.app.json",
|
"tsConfig": "tsconfig.json",
|
||||||
"assets": [
|
"assets": [
|
||||||
"src/favicon.ico",
|
|
||||||
"src/assets"
|
"src/assets"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
@ -57,63 +56,6 @@
|
|||||||
"browserTarget": "Sandbox:build:production"
|
"browserTarget": "Sandbox:build:production"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"extract-i18n": {
|
|
||||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
|
||||||
"options": {
|
|
||||||
"browserTarget": "Sandbox:build"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
|
||||||
"options": {
|
|
||||||
"main": "src/test.ts",
|
|
||||||
"polyfills": "src/polyfills.ts",
|
|
||||||
"tsConfig": "src/tsconfig.spec.json",
|
|
||||||
"karmaConfig": "src/karma.conf.js",
|
|
||||||
"styles": [
|
|
||||||
"styles.css"
|
|
||||||
],
|
|
||||||
"scripts": [],
|
|
||||||
"assets": [
|
|
||||||
"src/favicon.ico",
|
|
||||||
"src/assets"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"builder": "@angular-devkit/build-angular:tslint",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": [
|
|
||||||
"src/tsconfig.app.json",
|
|
||||||
"src/tsconfig.spec.json"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"**/node_modules/**"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Sandbox-e2e": {
|
|
||||||
"root": "e2e/",
|
|
||||||
"projectType": "application",
|
|
||||||
"architect": {
|
|
||||||
"e2e": {
|
|
||||||
"builder": "@angular-devkit/build-angular:protractor",
|
|
||||||
"options": {
|
|
||||||
"protractorConfig": "e2e/protractor.conf.js",
|
|
||||||
"devServerTarget": "Sandbox:serve"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"builder": "@angular-devkit/build-angular:tslint",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": "e2e/tsconfig.e2e.json",
|
|
||||||
"exclude": [
|
|
||||||
"**/node_modules/**"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -134,29 +76,9 @@
|
|||||||
"project": "projects/ng-datatable/ng-package.prod.json"
|
"project": "projects/ng-datatable/ng-package.prod.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@angular-devkit/build-angular:karma",
|
|
||||||
"options": {
|
|
||||||
"main": "projects/ng-datatable/src/test.ts",
|
|
||||||
"tsConfig": "projects/ng-datatable/tsconfig.spec.json",
|
|
||||||
"karmaConfig": "projects/ng-datatable/karma.conf.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"builder": "@angular-devkit/build-angular:tslint",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": [
|
|
||||||
"projects/ng-datatable/tsconfig.lib.json",
|
|
||||||
"projects/ng-datatable/tsconfig.spec.json"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"**/node_modules/**"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultProject": "Sandbox"
|
"defaultProject": "Sandbox"
|
||||||
}
|
}
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
// Protractor configuration file, see link for more information
|
|
||||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
|
||||||
|
|
||||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
|
||||||
|
|
||||||
exports.config = {
|
|
||||||
allScriptsTimeout: 11000,
|
|
||||||
specs: [
|
|
||||||
'./src/**/*.e2e-spec.ts'
|
|
||||||
],
|
|
||||||
capabilities: {
|
|
||||||
'browserName': 'chrome'
|
|
||||||
},
|
|
||||||
directConnect: true,
|
|
||||||
baseUrl: 'http://localhost:4200/',
|
|
||||||
framework: 'jasmine',
|
|
||||||
jasmineNodeOpts: {
|
|
||||||
showColors: true,
|
|
||||||
defaultTimeoutInterval: 30000,
|
|
||||||
print: function() {}
|
|
||||||
},
|
|
||||||
onPrepare() {
|
|
||||||
require('ts-node').register({
|
|
||||||
project: require('path').join(__dirname, './tsconfig.e2e.json')
|
|
||||||
});
|
|
||||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,14 +0,0 @@
|
|||||||
import { AppPage } from './app.po';
|
|
||||||
|
|
||||||
describe('workspace-project App', () => {
|
|
||||||
let page: AppPage;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
page = new AppPage();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should display welcome message', () => {
|
|
||||||
page.navigateTo();
|
|
||||||
expect(page.getParagraphText()).toEqual('Welcome to app!');
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,11 +0,0 @@
|
|||||||
import { browser, by, element } from 'protractor';
|
|
||||||
|
|
||||||
export class AppPage {
|
|
||||||
navigateTo() {
|
|
||||||
return browser.get('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
getParagraphText() {
|
|
||||||
return element(by.css('app-root h1')).getText();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "../out-tsc/app",
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "es5",
|
|
||||||
"types": [
|
|
||||||
"jasmine",
|
|
||||||
"jasminewd2",
|
|
||||||
"node"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,12 +4,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"build": "ng build",
|
"build": "ng build ng-datatable --prod"
|
||||||
"build-datatable": "ng build ng-datatable --prod",
|
|
||||||
"ci-publish": "cd dist/ng-datatable && ci-publish",
|
|
||||||
"test": "ng test",
|
|
||||||
"lint": "ng lint",
|
|
||||||
"e2e": "ng e2e"
|
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ztimson/ng-datatable",
|
"name": "@ztimson/ng-datatable",
|
||||||
"version": "1.5.2",
|
"version": "1.11.7",
|
||||||
"homepage": "https://github.com/ztimson/ng-datatable",
|
"homepage": "https://github.com/ztimson/ng-datatable",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"author": {
|
"author": {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import {TemplateRef} from "@angular/core";
|
import {TemplateRef} from "@angular/core";
|
||||||
|
|
||||||
export interface Column {
|
export interface Column {
|
||||||
|
aggregate?: (rows: any[]) => any;
|
||||||
cssClass?: string; // CSS to add to column
|
cssClass?: string; // CSS to add to column
|
||||||
|
canSelect?: boolean;
|
||||||
hide?: boolean; // Hide column
|
hide?: boolean; // Hide column
|
||||||
hideMobile?: boolean; // Hide column on mobile
|
hideMobile?: boolean; // Hide column on mobile
|
||||||
initialSort?: 'asc' | 'desc'; // Sort this column initially
|
initialSort?: 'asc' | 'desc'; // Sort this column initially
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<table [class]="cssClass" [style.table-layout]="tableLayout">
|
<table [class]="cssClass + ' ngdt'" [style.table-layout]="tableLayout">
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col *ngIf="showCheckbox && selectionMode !== null" width="30px">
|
<col *ngIf="showCheckbox && selectionMode !== null" width="30px">
|
||||||
<col *ngIf="expandedTemplate" width="30px">
|
<col *ngIf="expandedTemplate" width="30px">
|
||||||
@ -6,15 +6,15 @@
|
|||||||
<col *ngIf="c.hide !== true && !(c.hideMobile === true && width < mobileBreakpoint)" span="1" [width]="_convertWidth(c.width)">
|
<col *ngIf="c.hide !== true && !(c.hideMobile === true && width < mobileBreakpoint)" span="1" [width]="_convertWidth(c.width)">
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<thead>
|
<thead class="ngdt-header">
|
||||||
<tr>
|
<tr>
|
||||||
<th *ngIf="showCheckbox && selectionMode !== null">
|
<th *ngIf="showCheckbox && selectionMode !== null" class="ngdt-checkall">
|
||||||
<input *ngIf="selectionMode == 'multi'" type="checkbox" [checked]="processedData.length == selectedRows.size"
|
<input *ngIf="selectionMode == 'multi'" type="checkbox" [checked]="processedData.length == selectedRows.size"
|
||||||
(change)="$event.target.checked ? selectAll() : clearSelected()"/>
|
(change)="$event.target.checked ? selectAll() : clearSelected()"/>
|
||||||
</th>
|
</th>
|
||||||
<th *ngIf="expandedTemplate"></th>
|
<th *ngIf="expandedTemplate"></th>
|
||||||
<ng-container *ngFor="let c of columns; let i = index">
|
<ng-container *ngFor="let c of columns; let i = index">
|
||||||
<th *ngIf="c.hide !== true && !(c.hideMobile === true && width < mobileBreakpoint)" [class]="c.cssClass"
|
<th *ngIf="c.hide !== true && !(c.hideMobile === true && width < mobileBreakpoint)" [class]="c.cssClass + ' ngdt-column'"
|
||||||
style="cursor: pointer" (click)="sort(i)">
|
style="cursor: pointer" (click)="sort(i)">
|
||||||
<span *ngIf="sortedColumn == i && !sortedDesc">↑</span>
|
<span *ngIf="sortedColumn == i && !sortedDesc">↑</span>
|
||||||
<span *ngIf="sortedColumn == i && sortedDesc">↓</span>
|
<span *ngIf="sortedColumn == i && sortedDesc">↓</span>
|
||||||
@ -23,35 +23,54 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody class="ngdt-body">
|
||||||
<ng-container *ngFor="let row of pagedData; let i = index">
|
<ng-container *ngFor="let row of pagedData; let i = index">
|
||||||
<tr [ngClass]="{'active': selectedRows.has(i)}" (click)="updateSelected(i)">
|
<tr class="ngdt-row" [ngClass]="{'active': selectedRows.has(i)}">
|
||||||
<td *ngIf="showCheckbox && selectionMode !== null"><input type="checkbox" [checked]="selectedRows.has(i)"/></td>
|
<td *ngIf="showCheckbox && selectionMode !== null" class="ngdt-checkbox" (click)="updateSelected(i)">
|
||||||
<td *ngIf="expandedTemplate">
|
<input type="checkbox" [checked]="selectedRows.has(i)"/>
|
||||||
<span *ngIf="!selectedRows.has(i)">◢</span>
|
</td>
|
||||||
|
<td *ngIf="expandedTemplate" class="ngdt-expand" (click)="updateSelected(i)">
|
||||||
|
<span *ngIf="!selectedRows.has(i)">►</span>
|
||||||
<span *ngIf="selectedRows.has(i)">▼</span>
|
<span *ngIf="selectedRows.has(i)">▼</span>
|
||||||
</td>
|
</td>
|
||||||
<ng-container *ngFor="let c of columns">
|
<ng-container *ngFor="let c of columns">
|
||||||
<td *ngIf="c.hide !== true && !(c.hideMobile === true && width < mobileBreakpoint)">
|
<td *ngIf="c.hide !== true && !(c.hideMobile === true && width < mobileBreakpoint)" class="ngdt-cell" (click)="updateSelected(i, c)">
|
||||||
<ng-template #defaultTemplate let-value="value">{{value}}</ng-template>
|
<ng-template #defaultTemplate let-value="value">{{value}}</ng-template>
|
||||||
<ng-template [ngTemplateOutlet]="c.template || defaultTemplate"
|
<ng-template [ngTemplateOutlet]="c.template || defaultTemplate"
|
||||||
[ngTemplateOutletContext]="{object: row, value: _dotNotation(row, c.property)}">
|
[ngTemplateOutletContext]="{object: row, key: c.property, value: _dotNotation(row, c.property)}">
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="expandedTemplate && selectedRows.has(i)">
|
<tr *ngIf="expandedTemplate && selectedRows.has(i)" class="ngdt-detail">
|
||||||
<td [attr.colspan]="columns.length + (showCheckbox ? 1 : 0) + (expandedTemplate ? 1 : 0)">
|
<td [attr.colspan]="columns.length + (showCheckbox ? 1 : 0) + (expandedTemplate ? 1 : 0)">
|
||||||
<ng-template [ngTemplateOutlet]="expandedTemplate" [ngTemplateOutletContext]="{object: row}"></ng-template>
|
<ng-template [ngTemplateOutlet]="expandedTemplate" [ngTemplateOutletContext]="{object: row}"></ng-template>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<tr class="ngdt-total">
|
||||||
|
<td *ngIf="showCheckbox && selectionMode !== null" class="ngdt-checkbox"></td>
|
||||||
|
<td *ngIf="expandedTemplate" class="ngdt-expand"></td>
|
||||||
|
<ng-container *ngFor="let c of columns">
|
||||||
|
<td class="ngdt-cell">{{aggregate(c)}}</td>
|
||||||
|
</ng-container>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<nav *ngIf="paginate" [class]="paginateCssClass" aria-label="Page navigation">
|
<nav *ngIf="paginate" [class]="paginateCssClass + 'ngdt-paginator'" aria-label="Page navigation">
|
||||||
<ul class="pagination">
|
<ul class="pagination">
|
||||||
<li class="page-item" [ngClass]="{'disabled': page <= 1}" (click)="changePage(page - 1)"><a class="page-link">Previous</a></li>
|
<li class="page-item ngdt-first" [ngClass]="{'disabled': page <= 1}" (click)="changePage(1)"><a class="page-link">First</a></li>
|
||||||
<li *ngFor="let i of pages" class="page-item" [ngClass]="{'active': page == i}"><a class="page-link" (click)="changePage(i)">{{i}}</a></li>
|
<li class="page-item ngdt-next" [ngClass]="{'disabled': page <= 1}" (click)="changePage(page - 1)"><a class="page-link">Previous</a></li>
|
||||||
<li class="page-item" [ngClass]="{'disabled': page >= pages.length}" (click)="changePage(page + 1)"><a class="page-link">Next</a></li>
|
<ng-container *ngFor="let i of pages;">
|
||||||
|
<li *ngIf="i > page - 3 && i < page + 3" class="page-item ngdt-page" [ngClass]="{'active': page == i}">
|
||||||
|
<a class="page-link" (click)="changePage(i)">
|
||||||
|
<span *ngIf="i == page - 2 && i > 1">...</span>
|
||||||
|
{{i}}
|
||||||
|
<span *ngIf="i == page + 2 && i != pages.length">...</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ng-container>
|
||||||
|
<li class="page-item ngdt-previous" [ngClass]="{'disabled': page >= pages.length}" (click)="changePage(page + 1)"><a class="page-link">Next</a></li>
|
||||||
|
<li class="page-item ngdt-last" [ngClass]="{'disabled': page == pages.length}" (click)="changePage(pages.length)"><a class="page-link">Last</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -2,159 +2,179 @@ import {Component, EventEmitter, HostListener, Input, OnInit, Output, TemplateRe
|
|||||||
import {Column} from './column';
|
import {Column} from './column';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ng-datatable',
|
selector: 'ng-datatable',
|
||||||
templateUrl: 'ng-datatable.component.html'
|
templateUrl: 'ng-datatable.component.html',
|
||||||
|
styles: ['.ngdt-expand {font-family: sans-serif;}']
|
||||||
})
|
})
|
||||||
export class NgDatatableComponent implements OnInit {
|
export class NgDatatableComponent implements OnInit {
|
||||||
// Inputs ============================================================================================================
|
// Inputs ============================================================================================================
|
||||||
@Input() cssClass: string; // CSS class to add to table element
|
@Input() cssClass: string; // CSS class to add to table element
|
||||||
@Input() columns: Column[] = []; // Columns to display on table
|
@Input() columns: Column[] = []; // Columns to display on table
|
||||||
@Input() expandedTemplate: TemplateRef<any>; // Template to use when expanding columns
|
@Input() expandedTemplate: TemplateRef<any>; // Template to use when expanding columns
|
||||||
@Input() mobileBreakpoint: number = 768; // Hide mobile false columns when screen size is less than
|
@Input() mobileBreakpoint: number = 768; // Hide mobile false columns when screen size is less than
|
||||||
@Input() pageLength: number = 20; // Number of rows per page
|
@Input() pageLength: number = 20; // Number of rows per page
|
||||||
@Input() page: number = 1; // Current page number
|
@Input() page: number = 1; // Current page number
|
||||||
@Input() paginate: boolean = true; // Should we paginate results
|
@Input() paginate: boolean = true; // Should we paginate results
|
||||||
@Input() paginateCssClass: string; // CSS class to add to paginator
|
@Input() paginateCssClass: string; // CSS class to add to paginator
|
||||||
@Input() selectionMode: null | 'single' | 'multi'; // Allow selecting no/single/multiple rows
|
@Input() selectionMode: null | 'single' | 'multi'; // Allow selecting no/single/multiple rows
|
||||||
@Input() showCheckbox: boolean; // Selection checkboxes
|
@Input() showCheckbox: boolean; // Selection checkboxes
|
||||||
@Input() tableLayout: 'auto' | 'fixed' = 'auto'; // How column widths are decided
|
@Input() tableLayout: 'auto' | 'fixed' = 'auto'; // How column widths are decided
|
||||||
|
|
||||||
// Outputs ===========================================================================================================
|
// Outputs ===========================================================================================================
|
||||||
@Output() filterChanged = new EventEmitter<any[]>(); // Output when filters change
|
@Output() filterChanged = new EventEmitter<any[]>(); // Output when filters change
|
||||||
@Output() pageChanged = new EventEmitter<number>(); // Output when page is changed
|
@Output() finished = new EventEmitter<any[]>(); // Fired after processing is finished
|
||||||
@Output() selectionChanged = new EventEmitter<any[]>(); // Output when selected rows changes
|
@Output() pageChanged = new EventEmitter<number>(); // Output when page is changed
|
||||||
|
@Output() processing = new EventEmitter<any[]>(); // Fires when grid begins to process
|
||||||
|
@Output() selectionChanged = new EventEmitter<any[]>(); // Output when selected rows changes
|
||||||
|
|
||||||
// Properties ========================================================================================================
|
// Properties ========================================================================================================
|
||||||
filters: ((el?: any, i?: number, arr?: any[]) => boolean)[] = []; // Array of process functions to apply to data
|
filters: ((el?: any, i?: number, arr?: any[]) => boolean)[] = []; // Array of process functions to apply to data
|
||||||
pages: number[] = []; // Array of possible pages
|
pages: number[] = []; // Array of possible pages
|
||||||
pagedData: any[] = []; // The data for the current
|
pagedData: any[] = []; // The data for the current
|
||||||
processedData: any[]; // rows left after filtering
|
processedData: any[] = []; // rows left after filtering
|
||||||
selectedRows = new Set<number>(); // Keep track of selected rows
|
selectedRows = new Set<number>(); // Keep track of selected rows
|
||||||
sortedColumn: number; // Column currently being sorted
|
sortedColumn: number; // Column currently being sorted
|
||||||
sortedDesc = false; // Is the sorted column being sorted in ascending or descending order
|
sortedDesc = false; // Is the sorted column being sorted in ascending or descending order
|
||||||
width = window.innerWidth; // Width of the screen. Used for hiding mobile columns
|
width = window.innerWidth; // Width of the screen. Used for hiding mobile columns
|
||||||
|
|
||||||
// Fields ============================================================================================================
|
// Fields ============================================================================================================
|
||||||
get count(): number { return this.processedData.length; } // Number of rows after filtering
|
get count(): number {
|
||||||
private _data: any[] = []; // Original data entered into table
|
return this.processedData ? this.processedData.length : 0;
|
||||||
get data(): any[] { return this.processedData; } // Return the processed data
|
} // Number of rows after filtering
|
||||||
@Input() set data(data: any[]) {
|
private _data: any[] = []; // Original data entered into table
|
||||||
this._data = data;
|
get data(): any[] {
|
||||||
this._process();
|
return this.processedData;
|
||||||
}
|
} // Return the processed data
|
||||||
|
@Input() set data(data: any[]) {
|
||||||
// ===================================================================================================================
|
this._data = data;
|
||||||
constructor() { }
|
this.refresh();
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
// Look through columns for an initial sort
|
|
||||||
for(let i = 0; i < this.columns.length; i++) {
|
|
||||||
if(this.columns[i].initialSort) {
|
|
||||||
this.sort(i, (this.columns[i].initialSort == 'desc'));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers ===========================================================================================================
|
|
||||||
private _process() {
|
|
||||||
this.clearSelected();
|
|
||||||
this.processedData = this._data;
|
|
||||||
this.filters.forEach(f => this.processedData = this.processedData.filter(f));
|
|
||||||
if(this.sortedColumn != null) {
|
|
||||||
if (this.columns[this.sortedColumn].sortFn) {
|
|
||||||
this.processedData = this.processedData.sort(this.columns[this.sortedColumn].sortFn);
|
|
||||||
} else {
|
|
||||||
this.processedData = this.processedData.sort((a: any, b: any) => {
|
|
||||||
if (a[this.columns[this.sortedColumn].property] > b[this.columns[this.sortedColumn].property]) return 1;
|
|
||||||
if (a[this.columns[this.sortedColumn].property] < b[this.columns[this.sortedColumn].property]) return -1;
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.sortedDesc) this.processedData = this.processedData.reverse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.paginate) {
|
// ===================================================================================================================
|
||||||
this.pages = Array(Math.ceil(this.processedData.length / this.pageLength)).fill(0).map((ignore, i) => i + 1);
|
constructor() {
|
||||||
this.pagedData = this.processedData.filter((ignore, i) => i >= (this.page - 1) * this.pageLength && i < this.page * this.pageLength);
|
|
||||||
} else {
|
|
||||||
this.pagedData = this.processedData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_convertWidth(width) {
|
|
||||||
if(typeof width == 'number') return `${width}px`;
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
_dotNotation(obj: object, prop: string) {
|
|
||||||
return prop.split('.').reduce((obj, prop) => obj[prop], obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
addFilter(...filters: ((row?: any, index?: number, arr?: any[]) => boolean)[]) {
|
|
||||||
this.filters = this.filters.concat(filters);
|
|
||||||
this._process();
|
|
||||||
this.filterChanged.emit(this.filters);
|
|
||||||
}
|
|
||||||
|
|
||||||
changePage(page: number) {
|
|
||||||
if(!this.paginate || page < 1 || page > this.pages.length) return;
|
|
||||||
this.page = page;
|
|
||||||
this._process();
|
|
||||||
this.pageChanged.emit(this.page);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearFilters(update=true) {
|
|
||||||
this.filters = [];
|
|
||||||
if(update) this._process();
|
|
||||||
this.filterChanged.emit(this.filters);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearSelected() {
|
|
||||||
let emit = this.selectedRows.size > 0;
|
|
||||||
this.selectedRows.clear();
|
|
||||||
if(emit) this.selectionChanged.emit([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
|
||||||
onResize(event) { this.width = event.target.innerWidth; }
|
|
||||||
|
|
||||||
selectAll() {
|
|
||||||
this.processedData.forEach((ignore, i) => this.selectedRows.add(i));
|
|
||||||
this.selectionChanged.emit(this.processedData);
|
|
||||||
}
|
|
||||||
|
|
||||||
sort(columnIndex: number, desc?: boolean) {
|
|
||||||
let column = this.columns[columnIndex];
|
|
||||||
if (!column || column.sort === false) return; // If column is un-sortable return
|
|
||||||
|
|
||||||
// Figure out sorting direction if not supplied
|
|
||||||
if(desc === undefined) {
|
|
||||||
desc = false;
|
|
||||||
if(columnIndex == this.sortedColumn) desc = !this.sortedDesc;
|
|
||||||
}
|
|
||||||
this.sortedColumn = columnIndex;
|
|
||||||
this.sortedDesc = desc;
|
|
||||||
|
|
||||||
// Preform sort
|
|
||||||
this._process();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSelected(index: number) {
|
|
||||||
if (this.selectionMode == null) return;
|
|
||||||
|
|
||||||
if (this.selectionMode == 'single') {
|
|
||||||
let alreadySelected = this.selectedRows.has(index);
|
|
||||||
this.selectedRows.clear();
|
|
||||||
if(!alreadySelected) this.selectedRows.add(index);
|
|
||||||
} else {
|
|
||||||
if (this.selectedRows.has(index)) {
|
|
||||||
this.selectedRows.delete(index);
|
|
||||||
} else {
|
|
||||||
this.selectedRows.add(index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectionChanged.emit(this.processedData.filter((row, i) => this.selectedRows.has(i)));
|
ngOnInit() {
|
||||||
}
|
// Look through columns for an initial sort
|
||||||
|
for (let i = 0; i < this.columns.length; i++) {
|
||||||
|
if (this.columns[i].initialSort) {
|
||||||
|
this.sort(i, (this.columns[i].initialSort == 'desc'));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers ===========================================================================================================
|
||||||
|
_convertWidth(width) {
|
||||||
|
if (typeof width == 'number') return `${width}px`;
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dotNotation(obj: object, prop: string) {
|
||||||
|
return prop.split('.').reduce((obj, prop) => obj[prop], obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
addFilter(...filters: ((row?: any, index?: number, arr?: any[]) => boolean)[]) {
|
||||||
|
this.filters = this.filters.concat(filters);
|
||||||
|
this.refresh();
|
||||||
|
this.filterChanged.emit(this.filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregate(col: Column) {
|
||||||
|
if (!col.aggregate) return '';
|
||||||
|
return col.aggregate(this.processedData.map(row => this._dotNotation(row, col.property)));
|
||||||
|
}
|
||||||
|
|
||||||
|
changePage(page: number) {
|
||||||
|
if (!this.paginate || page < 1 || page > this.pages.length) return;
|
||||||
|
this.page = page;
|
||||||
|
this.refresh();
|
||||||
|
this.pageChanged.emit(this.page);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearFilters(update = true) {
|
||||||
|
this.filters = [];
|
||||||
|
if (update) this.refresh();
|
||||||
|
this.filterChanged.emit(this.filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSelected() {
|
||||||
|
let emit = this.selectedRows.size > 0;
|
||||||
|
this.selectedRows.clear();
|
||||||
|
if (emit) this.selectionChanged.emit([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('window:resize', ['$event'])
|
||||||
|
onResize(event) {
|
||||||
|
this.width = event.target.innerWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.processing.emit(this.processedData);
|
||||||
|
this.clearSelected();
|
||||||
|
this.processedData = this._data;
|
||||||
|
this.filters.forEach(f => this.processedData = this.processedData.filter(f));
|
||||||
|
if (this.sortedColumn != null && this.processedData) {
|
||||||
|
if (this.columns[this.sortedColumn].sortFn) {
|
||||||
|
this.processedData = this.processedData.sort(this.columns[this.sortedColumn].sortFn);
|
||||||
|
} else {
|
||||||
|
this.processedData = this.processedData.sort((a: any, b: any) => {
|
||||||
|
if (this._dotNotation(a, this.columns[this.sortedColumn].property) > this._dotNotation(b, this.columns[this.sortedColumn].property)) return 1;
|
||||||
|
if (this._dotNotation(a, this.columns[this.sortedColumn].property) < this._dotNotation(b, this.columns[this.sortedColumn].property)) return -1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.sortedDesc) this.processedData = this.processedData.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.paginate && this.processedData) {
|
||||||
|
this.pages = Array(Math.ceil(this.processedData.length / this.pageLength)).fill(0).map((ignore, i) => i + 1);
|
||||||
|
if (!this.page) this.page = 1;
|
||||||
|
if (this.page > this.pages.length) this.page = this.pages.length;
|
||||||
|
this.pagedData = this.processedData.filter((ignore, i) => i >= (this.page - 1) * this.pageLength && i < this.page * this.pageLength);
|
||||||
|
} else {
|
||||||
|
this.pagedData = this.processedData;
|
||||||
|
}
|
||||||
|
this.finished.emit(this.processedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAll() {
|
||||||
|
this.processedData.forEach((ignore, i) => this.selectedRows.add(i));
|
||||||
|
this.selectionChanged.emit(this.processedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
sort(columnIndex: number, desc?: boolean) {
|
||||||
|
let column = this.columns[columnIndex];
|
||||||
|
if (!column || column.sort === false) return; // If column is un-sortable return
|
||||||
|
|
||||||
|
// Figure out sorting direction if not supplied
|
||||||
|
if (desc === undefined) {
|
||||||
|
desc = false;
|
||||||
|
if (columnIndex == this.sortedColumn) desc = !this.sortedDesc;
|
||||||
|
}
|
||||||
|
this.sortedColumn = columnIndex;
|
||||||
|
this.sortedDesc = desc;
|
||||||
|
|
||||||
|
// Preform sort
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSelected(index: number, column?: Column) {
|
||||||
|
if (this.selectionMode == null) return;
|
||||||
|
if (column && column.canSelect === false) return;
|
||||||
|
|
||||||
|
if (this.selectionMode == 'single') {
|
||||||
|
let alreadySelected = this.selectedRows.has(index);
|
||||||
|
this.selectedRows.clear();
|
||||||
|
if (!alreadySelected) this.selectedRows.add(index);
|
||||||
|
} else {
|
||||||
|
if (this.selectedRows.has(index)) {
|
||||||
|
this.selectedRows.delete(index);
|
||||||
|
} else {
|
||||||
|
this.selectedRows.add(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectionChanged.emit(this.processedData.filter((row, i) => this.selectedRows.has(i)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,5 @@
|
|||||||
"strictInjectionParameters": true,
|
"strictInjectionParameters": true,
|
||||||
"flatModuleId": "AUTOGENERATED",
|
"flatModuleId": "AUTOGENERATED",
|
||||||
"flatModuleOutFile": "AUTOGENERATED"
|
"flatModuleOutFile": "AUTOGENERATED"
|
||||||
},
|
}
|
||||||
"exclude": [
|
|
||||||
"**/*.spec.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
Table CSS
|
Table CSS
|
||||||
<input [(ngModel)]="tableCSS"> Selection Mode
|
<input [(ngModel)]="tableCSS"> Selection Mode
|
||||||
<select [(ngModel)]="selectionMode">
|
<select [(ngModel)]="selectionMode">
|
||||||
<option>None</option>
|
<option>None</option>
|
||||||
<option>single</option>
|
<option>single</option>
|
||||||
<option>multi</option>
|
<option>multi</option>
|
||||||
</select>
|
</select>
|
||||||
Checkbox
|
Checkbox
|
||||||
<input type="checkbox" [(ngModel)]="checkbox"> Expandable
|
<input type="checkbox" [(ngModel)]="checkbox"> Expandable
|
||||||
@ -13,13 +13,13 @@ Checkbox
|
|||||||
<input placeholder="Search" (keyup)="search.next($event.target.value)">
|
<input placeholder="Search" (keyup)="search.next($event.target.value)">
|
||||||
<br> Selected: {{table.selectedRows.size}}/{{table.processedData.length}}
|
<br> Selected: {{table.selectedRows.size}}/{{table.processedData.length}}
|
||||||
<ng-datatable #table [cssClass]="tableCSS" [columns]="columns" [data]="data" [expandedTemplate]="expandable ? expanded : null"
|
<ng-datatable #table [cssClass]="tableCSS" [columns]="columns" [data]="data" [expandedTemplate]="expandable ? expanded : null"
|
||||||
[showCheckbox]="checkbox" [paginate]="false" [selectionMode]="selectionMode == 'None' ? null : selectionMode" (rowSelected)="log($event)">
|
[showCheckbox]="checkbox" [paginate]="true" [selectionMode]="selectionMode == 'None' ? null : selectionMode" (rowSelected)="log($event)">
|
||||||
<ng-template #expanded let-object="object">
|
<ng-template #expanded let-object="object">
|
||||||
Hello {{object.firstName}} {{object.lastName}}, How are you today?
|
Hello {{object.firstName}} {{object.lastName}}, How are you today?
|
||||||
<span *ngIf="object.age < 18">I can see that you are under age.</span>
|
<span *ngIf="object.age < 18">I can see that you are under age.</span>
|
||||||
<span *ngIf="object.age > 84">I can see that you are over the average life expectancy.</span>
|
<span *ngIf="object.age > 84">I can see that you are over the average life expectancy.</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #age let-value="value">
|
<ng-template #age let-value="value">
|
||||||
<strong [ngClass]="{'text-success': value < 18, 'text-danger': value > 84}">{{value}}</strong>
|
<strong [ngClass]="{'text-success': value < 18, 'text-danger': value > 84}">{{value}}</strong>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-datatable>
|
</ng-datatable>
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import { TestBed, async } from '@angular/core/testing';
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
describe('AppComponent', () => {
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
declarations: [
|
|
||||||
AppComponent
|
|
||||||
],
|
|
||||||
}).compileComponents();
|
|
||||||
}));
|
|
||||||
it('should create the app', async(() => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.debugElement.componentInstance;
|
|
||||||
expect(app).toBeTruthy();
|
|
||||||
}));
|
|
||||||
it(`should have as title 'app'`, async(() => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.debugElement.componentInstance;
|
|
||||||
expect(app.title).toEqual('app');
|
|
||||||
}));
|
|
||||||
it('should render title in a h1 tag', async(() => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
fixture.detectChanges();
|
|
||||||
const compiled = fixture.debugElement.nativeElement;
|
|
||||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
|
||||||
}));
|
|
||||||
});
|
|
File diff suppressed because one or more lines are too long
@ -1,20 +1,20 @@
|
|||||||
import { BrowserModule } from '@angular/platform-browser';
|
import {NgModule} from '@angular/core';
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
import {FormsModule} from "@angular/forms";
|
import {FormsModule} from "@angular/forms";
|
||||||
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
import {NgDatatableModule} from "../../projects/ng-datatable/src/lib/ng-datatable.module";
|
import {NgDatatableModule} from "../../projects/ng-datatable/src/lib/ng-datatable.module";
|
||||||
|
|
||||||
|
import {AppComponent} from './app.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent
|
AppComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NgDatatableModule
|
NgDatatableModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule {}
|
||||||
|
@ -1,15 +1,3 @@
|
|||||||
// This file can be replaced during build by using the `fileReplacements` array.
|
|
||||||
// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
|
|
||||||
// The list of file replacements can be found in `angular.json`.
|
|
||||||
|
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: false
|
production: false
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* In development mode, to ignore zone related error stack frames such as
|
|
||||||
* `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can
|
|
||||||
* import the following file, but please comment it out in production mode
|
|
||||||
* because it will have performance impact when throw error
|
|
||||||
*/
|
|
||||||
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
|
|
||||||
|
BIN
src/favicon.ico
BIN
src/favicon.ico
Binary file not shown.
Before Width: | Height: | Size: 5.3 KiB |
@ -1,31 +0,0 @@
|
|||||||
// Karma configuration file, see link for more information
|
|
||||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
|
||||||
|
|
||||||
module.exports = function (config) {
|
|
||||||
config.set({
|
|
||||||
basePath: '',
|
|
||||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
|
||||||
plugins: [
|
|
||||||
require('karma-jasmine'),
|
|
||||||
require('karma-chrome-launcher'),
|
|
||||||
require('karma-jasmine-html-reporter'),
|
|
||||||
require('karma-coverage-istanbul-reporter'),
|
|
||||||
require('@angular-devkit/build-angular/plugins/karma')
|
|
||||||
],
|
|
||||||
client: {
|
|
||||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
|
||||||
},
|
|
||||||
coverageIstanbulReporter: {
|
|
||||||
dir: require('path').join(__dirname, '../coverage'),
|
|
||||||
reports: ['html', 'lcovonly'],
|
|
||||||
fixWebpackSourcePaths: true
|
|
||||||
},
|
|
||||||
reporters: ['progress', 'kjhtml'],
|
|
||||||
port: 9876,
|
|
||||||
colors: true,
|
|
||||||
logLevel: config.LOG_INFO,
|
|
||||||
autoWatch: true,
|
|
||||||
browsers: ['Chrome'],
|
|
||||||
singleRun: false
|
|
||||||
});
|
|
||||||
};
|
|
20
src/test.ts
20
src/test.ts
@ -1,20 +0,0 @@
|
|||||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
|
||||||
|
|
||||||
import 'zone.js/dist/zone-testing';
|
|
||||||
import { getTestBed } from '@angular/core/testing';
|
|
||||||
import {
|
|
||||||
BrowserDynamicTestingModule,
|
|
||||||
platformBrowserDynamicTesting
|
|
||||||
} from '@angular/platform-browser-dynamic/testing';
|
|
||||||
|
|
||||||
declare const require: any;
|
|
||||||
|
|
||||||
// First, initialize the Angular testing environment.
|
|
||||||
getTestBed().initTestEnvironment(
|
|
||||||
BrowserDynamicTestingModule,
|
|
||||||
platformBrowserDynamicTesting()
|
|
||||||
);
|
|
||||||
// Then we find all the tests.
|
|
||||||
const context = require.context('./', true, /\.spec\.ts$/);
|
|
||||||
// And load the modules.
|
|
||||||
context.keys().map(context);
|
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "../out-tsc/app",
|
|
||||||
"module": "es2015",
|
|
||||||
"types": []
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"src/test.ts",
|
|
||||||
"**/*.spec.ts"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "../out-tsc/spec",
|
|
||||||
"module": "commonjs",
|
|
||||||
"types": [
|
|
||||||
"jasmine",
|
|
||||||
"node"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"test.ts",
|
|
||||||
"polyfills.ts"
|
|
||||||
],
|
|
||||||
"include": [
|
|
||||||
"**/*.spec.ts",
|
|
||||||
"**/*.d.ts"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../tslint.json",
|
|
||||||
"rules": {
|
|
||||||
"directive-selector": [
|
|
||||||
true,
|
|
||||||
"attribute",
|
|
||||||
"app",
|
|
||||||
"camelCase"
|
|
||||||
],
|
|
||||||
"component-selector": [
|
|
||||||
true,
|
|
||||||
"element",
|
|
||||||
"app",
|
|
||||||
"kebab-case"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +1,29 @@
|
|||||||
{
|
{
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"outDir": "./dist/out-tsc",
|
"outDir": "../out-tsc/app",
|
||||||
"sourceMap": true,
|
"module": "es2015",
|
||||||
"declaration": false,
|
"sourceMap": true,
|
||||||
"moduleResolution": "node",
|
"declaration": false,
|
||||||
"emitDecoratorMetadata": true,
|
"moduleResolution": "node",
|
||||||
"experimentalDecorators": true,
|
"emitDecoratorMetadata": true,
|
||||||
"target": "es5",
|
"experimentalDecorators": true,
|
||||||
"typeRoots": [
|
"target": "es5",
|
||||||
"node_modules/@types"
|
"typeRoots": [
|
||||||
],
|
"node_modules/@types"
|
||||||
"lib": [
|
],
|
||||||
"es2017",
|
"lib": [
|
||||||
"dom"
|
"es2017",
|
||||||
],
|
"dom"
|
||||||
"paths": {
|
],
|
||||||
"ng-datatable": [
|
"paths": {
|
||||||
"dist/ng-datatable"
|
"ng-datatable": [
|
||||||
],
|
"dist/ng-datatable"
|
||||||
"ng-datatable/*": [
|
],
|
||||||
"dist/ng-datatable/*"
|
"ng-datatable/*": [
|
||||||
]
|
"dist/ng-datatable/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
130
tslint.json
130
tslint.json
@ -1,130 +0,0 @@
|
|||||||
{
|
|
||||||
"rulesDirectory": [
|
|
||||||
"node_modules/codelyzer"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"arrow-return-shorthand": true,
|
|
||||||
"callable-types": true,
|
|
||||||
"class-name": true,
|
|
||||||
"comment-format": [
|
|
||||||
true,
|
|
||||||
"check-space"
|
|
||||||
],
|
|
||||||
"curly": true,
|
|
||||||
"deprecation": {
|
|
||||||
"severity": "warn"
|
|
||||||
},
|
|
||||||
"eofline": true,
|
|
||||||
"forin": true,
|
|
||||||
"import-blacklist": [
|
|
||||||
true,
|
|
||||||
"rxjs/Rx"
|
|
||||||
],
|
|
||||||
"import-spacing": true,
|
|
||||||
"indent": [
|
|
||||||
true,
|
|
||||||
"spaces"
|
|
||||||
],
|
|
||||||
"interface-over-type-literal": true,
|
|
||||||
"label-position": true,
|
|
||||||
"max-line-length": [
|
|
||||||
true,
|
|
||||||
140
|
|
||||||
],
|
|
||||||
"member-access": false,
|
|
||||||
"member-ordering": [
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
"order": [
|
|
||||||
"static-field",
|
|
||||||
"instance-field",
|
|
||||||
"static-method",
|
|
||||||
"instance-method"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-arg": true,
|
|
||||||
"no-bitwise": true,
|
|
||||||
"no-console": [
|
|
||||||
true,
|
|
||||||
"debug",
|
|
||||||
"info",
|
|
||||||
"time",
|
|
||||||
"timeEnd",
|
|
||||||
"trace"
|
|
||||||
],
|
|
||||||
"no-construct": true,
|
|
||||||
"no-debugger": true,
|
|
||||||
"no-duplicate-super": true,
|
|
||||||
"no-empty": false,
|
|
||||||
"no-empty-interface": true,
|
|
||||||
"no-eval": true,
|
|
||||||
"no-inferrable-types": [
|
|
||||||
true,
|
|
||||||
"ignore-params"
|
|
||||||
],
|
|
||||||
"no-misused-new": true,
|
|
||||||
"no-non-null-assertion": true,
|
|
||||||
"no-shadowed-variable": true,
|
|
||||||
"no-string-literal": false,
|
|
||||||
"no-string-throw": true,
|
|
||||||
"no-switch-case-fall-through": true,
|
|
||||||
"no-trailing-whitespace": true,
|
|
||||||
"no-unnecessary-initializer": true,
|
|
||||||
"no-unused-expression": true,
|
|
||||||
"no-use-before-declare": true,
|
|
||||||
"no-var-keyword": true,
|
|
||||||
"object-literal-sort-keys": false,
|
|
||||||
"one-line": [
|
|
||||||
true,
|
|
||||||
"check-open-brace",
|
|
||||||
"check-catch",
|
|
||||||
"check-else",
|
|
||||||
"check-whitespace"
|
|
||||||
],
|
|
||||||
"prefer-const": true,
|
|
||||||
"quotemark": [
|
|
||||||
true,
|
|
||||||
"single"
|
|
||||||
],
|
|
||||||
"radix": true,
|
|
||||||
"semicolon": [
|
|
||||||
true,
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"triple-equals": [
|
|
||||||
true,
|
|
||||||
"allow-null-check"
|
|
||||||
],
|
|
||||||
"typedef-whitespace": [
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
"call-signature": "nospace",
|
|
||||||
"index-signature": "nospace",
|
|
||||||
"parameter": "nospace",
|
|
||||||
"property-declaration": "nospace",
|
|
||||||
"variable-declaration": "nospace"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"unified-signatures": true,
|
|
||||||
"variable-name": false,
|
|
||||||
"whitespace": [
|
|
||||||
true,
|
|
||||||
"check-branch",
|
|
||||||
"check-decl",
|
|
||||||
"check-operator",
|
|
||||||
"check-separator",
|
|
||||||
"check-type"
|
|
||||||
],
|
|
||||||
"no-output-on-prefix": true,
|
|
||||||
"use-input-property-decorator": true,
|
|
||||||
"use-output-property-decorator": true,
|
|
||||||
"use-host-property-decorator": true,
|
|
||||||
"no-input-rename": true,
|
|
||||||
"no-output-rename": true,
|
|
||||||
"use-life-cycle-interface": true,
|
|
||||||
"use-pipe-transform-interface": true,
|
|
||||||
"component-class-suffix": true,
|
|
||||||
"directive-class-suffix": true
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user