Compare commits

..

No commits in common. "develop" and "production" have entirely different histories.

31 changed files with 8479 additions and 506 deletions

46
.circleci/config.yml Normal file
View File

@ -0,0 +1,46 @@
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

View File

@ -4,7 +4,7 @@ root = true
[*] [*]
charset = utf-8 charset = utf-8
indent_style = space indent_style = space
indent_size = 4 indent_size = 2
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true

View File

@ -1,41 +0,0 @@
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 to Registry
uses: ztimson/actions/npm/publish@develop
tag:
name: Tag Version
needs: build
if: ${{github.ref_name}} == develop
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}}

108
README.md
View File

@ -1,52 +1,8 @@
<!-- Header --> # ng-datatable
<div id="top" align="center">
<br />
<!-- Logo --> [![CircleCI](https://circleci.com/gh/ztimson/ng-datatable/tree/master.svg?style=svg)](https://circleci.com/gh/ztimson/ng-datatable/tree/master)
<img src="https://git.zakscode.com/repo-avatars/56e7adede041749f5df1d8b88b334e8c4cac38d7a4a574ccb42a624c52a35624" alt="Logo" width="200" height="200">
<!-- Title -->
### ng-datatable
<!-- Description -->
**Deprecated:** Angular Table Building Library
<!-- Repo badges -->
[![Version](https://img.shields.io/badge/dynamic/json.svg?label=Version&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/ng-datatable/tags&query=$[0].name)](https://git.zakscode.com/ztimson/ng-datatable/tags)
[![Pull Requests](https://img.shields.io/badge/dynamic/json.svg?label=Pull%20Requests&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/ng-datatable&query=open_pr_counter)](https://git.zakscode.com/ztimson/ng-datatable/pulls)
[![Issues](https://img.shields.io/badge/dynamic/json.svg?label=Issues&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/ng-datatable&query=open_issues_count)](https://git.zakscode.com/ztimson/ng-datatable/issues)
<!-- Links -->
---
<div>
<a href="https://git.zakscode.com/ztimson/ng-datatable/releases" target="_blank">Release Notes</a>
<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>
</div>
---
</div>
## Table of Contents
- [ng-datatable](#top)
- [About](#about)
- [Built With](#built-with)
- [Setup](#setup)
- [Production](#production)
- [Development](#development)
- [Documentation](#documentation)
- [NgDatatableComponent](#ngdatatablecomponent)
- [Column](#column)
- [License](#license)
## About
**Deprecated**
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.
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.
Features: Features:
@ -60,27 +16,15 @@ Features:
- Mobile column hiding - Mobile column hiding
- Checkboxes - Checkboxes
[View on NPM](https://www.npmjs.com/package/@ztimson/ng-datatable) [View on NPM](https://www.npmjs.com/package/@ztimson/ng-datatable)
### Built With [View the demo here](https://stackblitz.com/edit/angular-rzq6xm)
[![Angular](https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular)](https://angular.io/)
[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://typescriptlang.org/)
## Setup ## Installing
<details> 1. Install package `npm install @ztimson/ng-datatable --save`
<summary> 2. Import into module
<h3 id="production" style="display: inline">
Production
</h3>
</summary>
#### Prerequisites
- [Node.js](https://nodejs.org/en/download)
#### Instructions
1. Install package `npm install @ztimson/ng-datatable --save`
2. Import into module
```Typescript ```Typescript
import {NgDatatableModule} from '@ztimsonm/ng-datatable' import {NgDatatableModule} from '@ztimsonm/ng-datatable'
@ -95,31 +39,13 @@ import {NgDatatableModule} from '@ztimsonm/ng-datatable'
export class AppModule { } export class AppModule { }
``` ```
3. Add to template 3. Add to template
```HTML ```HTML
<ng-datatable [columns]="columns" [data]="data"></ng-datatable> <ng-datatable [columns]="columns" [data]="data"></ng-datatable>
``` ```
</details>
<details> ## API
<summary>
<h3 id="development" style="display: inline">
Development
</h3>
</summary>
#### Prerequisites
- [Node.js](https://nodejs.org/en/download)
#### Instructions
1. Install the dependencies: `npm install`
2. Start the Angular server: `npm run start`
3. Open [http://localhost:4200](http://localhost:4200)
</details>
## Documentation
### NgDatatableComponent ### NgDatatableComponent
@ -138,14 +64,12 @@ Selector: `ng-datatable`
| @Input() pageLength: number | Number of rows per page. Default 20 | | @Input() pageLength: number | Number of rows per page. Default 20 |
| @Input() page: number | Current page | | @Input() page: number | Current page |
| @Input() paginate: boolean | Paginate rows or display all at once. Default true | | @Input() paginate: boolean | Paginate rows or display all at once. Default true |
| @Input() paginateCssClass: string | Class added to the paginator | | @Input() paginateCssClass: string | Class added to the paginator
| @Input() selectionMode: null/'single'/'multi' | Allow selecting none, single or multiple rows at once | | @Input() selectionMode: null/'single'/'multi' | Allow selecting none, single or multiple rows at once |
| @Input() showCheckbox: boolean | Show checkbox' for mass selecting | | @Input() showCheckbox: boolean | Show checkbox' for mass selecting |
| @Input() tableLayout: 'auto'/'fixed' | CSS table layout. Defaults to 'auto' | | @Input() tableLayout: 'auto'/'fixed' | CSS table layout. Defaults to 'auto' |
| @Output() filterChanged: EventEmitter<(a, b) => 1/0/-1[]> | Applied filters | | @Output() filterChanged: EventEmitter<(a, b) => 1/0/-1[]> | Applied filters |
| @Output() finished: EventEmitter<any[]> | Emits when finished processing data |
| @Output() pageChanged: EventEmitter<number> | New page | | @Output() pageChanged: EventEmitter<number> | New page |
| @Output() processing: EventEmitter<any[]> | Emits when processing begins |
| @Output() selectionChanged: EventEmitter<any[]> | Selected rows | | @Output() selectionChanged: EventEmitter<any[]> | Selected rows |
| pagedData: any[] | Array of rows on current page after sorting and filtering | | pagedData: any[] | Array of rows on current page after sorting and filtering |
| processedData: any[] | Array of remaining rows after sorting and filtering | | processedData: any[] | Array of remaining rows after sorting and filtering |
@ -181,10 +105,6 @@ Clear filters being applied to. Update can be set to false to prevent instant fi
Clear all selected rows. Clear all selected rows.
#### refresh()
Refreshes the grid
##### selectAll() ##### selectAll()
Select all rows. Ignores pagination but not filtering. Select all rows. Ignores pagination but not filtering.
@ -224,9 +144,3 @@ Exported As: `Column`
| sortFn: (a, b) => 1/0/-1 | Custom function to sort rows by | | sortFn: (a, b) => 1/0/-1 | Custom function to sort rows by |
| template: TemplateRef<any> | Template to render row with | | template: TemplateRef<any> | Template to render row with |
| width: number/string | CSS width property | | 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.

View File

@ -17,8 +17,9 @@
"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": "tsconfig.json", "tsConfig": "src/tsconfig.app.json",
"assets": [ "assets": [
"src/favicon.ico",
"src/assets" "src/assets"
], ],
"styles": [ "styles": [
@ -56,6 +57,63 @@
"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/**"
]
}
} }
} }
}, },
@ -76,6 +134,26 @@
"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/**"
]
}
} }
} }
} }

28
e2e/protractor.conf.js Normal file
View File

@ -0,0 +1,28 @@
// 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 } }));
}
};

14
e2e/src/app.e2e-spec.ts Normal file
View File

@ -0,0 +1,14 @@
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!');
});
});

11
e2e/src/app.po.ts Normal file
View File

@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}

13
e2e/tsconfig.e2e.json Normal file
View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View File

@ -1,11 +1,17 @@
{ {
"name": "ng-datatable", "name": "sandbox",
"version": "1.0.0", "version": "0.0.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
"build": "ng build ng-datatable --prod" "build": "ng build",
"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,
"dependencies": { "dependencies": {
"@angular/animations": "^6.0.0", "@angular/animations": "^6.0.0",
"@angular/common": "^6.0.0", "@angular/common": "^6.0.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "@ztimson/ng-datatable", "name": "@ztimson/ng-datatable",
"version": "1.11.7", "version": "1.5.2",
"homepage": "https://github.com/ztimson/ng-datatable", "homepage": "https://github.com/ztimson/ng-datatable",
"license": "Apache-2.0", "license": "Apache-2.0",
"author": { "author": {

View File

@ -1,9 +1,7 @@
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

View File

@ -1,4 +1,4 @@
<table [class]="cssClass + ' ngdt'" [style.table-layout]="tableLayout"> <table [class]="cssClass" [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 class="ngdt-header"> <thead>
<tr> <tr>
<th *ngIf="showCheckbox && selectionMode !== null" class="ngdt-checkall"> <th *ngIf="showCheckbox && selectionMode !== null">
<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 + ' ngdt-column'" <th *ngIf="c.hide !== true && !(c.hideMobile === true && width < mobileBreakpoint)" [class]="c.cssClass"
style="cursor: pointer" (click)="sort(i)"> style="cursor: pointer" (click)="sort(i)">
<span *ngIf="sortedColumn == i && !sortedDesc">&uarr;</span> <span *ngIf="sortedColumn == i && !sortedDesc">&uarr;</span>
<span *ngIf="sortedColumn == i && sortedDesc">&darr;</span> <span *ngIf="sortedColumn == i && sortedDesc">&darr;</span>
@ -23,54 +23,35 @@
</ng-container> </ng-container>
</tr> </tr>
</thead> </thead>
<tbody class="ngdt-body"> <tbody>
<ng-container *ngFor="let row of pagedData; let i = index"> <ng-container *ngFor="let row of pagedData; let i = index">
<tr class="ngdt-row" [ngClass]="{'active': selectedRows.has(i)}"> <tr [ngClass]="{'active': selectedRows.has(i)}" (click)="updateSelected(i)">
<td *ngIf="showCheckbox && selectionMode !== null" class="ngdt-checkbox" (click)="updateSelected(i)"> <td *ngIf="showCheckbox && selectionMode !== null"><input type="checkbox" [checked]="selectedRows.has(i)"/></td>
<input type="checkbox" [checked]="selectedRows.has(i)"/> <td *ngIf="expandedTemplate">
</td> <span *ngIf="!selectedRows.has(i)">&#9698;</span>
<td *ngIf="expandedTemplate" class="ngdt-expand" (click)="updateSelected(i)">
<span *ngIf="!selectedRows.has(i)">&#9658;</span>
<span *ngIf="selectedRows.has(i)">&#9660;</span> <span *ngIf="selectedRows.has(i)">&#9660;</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)" class="ngdt-cell" (click)="updateSelected(i, c)"> <td *ngIf="c.hide !== true && !(c.hideMobile === true && width < mobileBreakpoint)">
<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, key: c.property, value: _dotNotation(row, c.property)}"> [ngTemplateOutletContext]="{object: row, value: _dotNotation(row, c.property)}">
</ng-template> </ng-template>
</td> </td>
</ng-container> </ng-container>
</tr> </tr>
<tr *ngIf="expandedTemplate && selectedRows.has(i)" class="ngdt-detail"> <tr *ngIf="expandedTemplate && selectedRows.has(i)">
<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 + 'ngdt-paginator'" aria-label="Page navigation"> <nav *ngIf="paginate" [class]="paginateCssClass" aria-label="Page navigation">
<ul class="pagination"> <ul class="pagination">
<li class="page-item ngdt-first" [ngClass]="{'disabled': page <= 1}" (click)="changePage(1)"><a class="page-link">First</a></li> <li class="page-item" [ngClass]="{'disabled': page <= 1}" (click)="changePage(page - 1)"><a class="page-link">Previous</a></li>
<li class="page-item ngdt-next" [ngClass]="{'disabled': page <= 1}" (click)="changePage(page - 1)"><a class="page-link">Previous</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>
<ng-container *ngFor="let i of pages;"> <li class="page-item" [ngClass]="{'disabled': page >= pages.length}" (click)="changePage(page + 1)"><a class="page-link">Next</a></li>
<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>

View File

@ -3,8 +3,7 @@ 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 ============================================================================================================
@ -22,42 +21,35 @@ export class NgDatatableComponent implements OnInit {
// Outputs =========================================================================================================== // Outputs ===========================================================================================================
@Output() filterChanged = new EventEmitter<any[]>(); // Output when filters change @Output() filterChanged = new EventEmitter<any[]>(); // Output when filters change
@Output() finished = new EventEmitter<any[]>(); // Fired after processing is finished
@Output() pageChanged = new EventEmitter<number>(); // Output when page is changed @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 @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 { get count(): number { return this.processedData.length; } // Number of rows after filtering
return this.processedData ? this.processedData.length : 0;
} // Number of rows after filtering
private _data: any[] = []; // Original data entered into table private _data: any[] = []; // Original data entered into table
get data(): any[] { get data(): any[] { return this.processedData; } // Return the processed data
return this.processedData;
} // Return the processed data
@Input() set data(data: any[]) { @Input() set data(data: any[]) {
this._data = data; this._data = data;
this.refresh(); this._process();
} }
// =================================================================================================================== // ===================================================================================================================
constructor() { constructor() { }
}
ngOnInit() { ngOnInit() {
// Look through columns for an initial sort // Look through columns for an initial sort
for (let i = 0; i < this.columns.length; i++) { for(let i = 0; i < this.columns.length; i++) {
if (this.columns[i].initialSort) { if(this.columns[i].initialSort) {
this.sort(i, (this.columns[i].initialSort == 'desc')); this.sort(i, (this.columns[i].initialSort == 'desc'));
break; break;
} }
@ -65,8 +57,33 @@ export class NgDatatableComponent implements OnInit {
} }
// Helpers =========================================================================================================== // 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);
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) { _convertWidth(width) {
if (typeof width == 'number') return `${width}px`; if(typeof width == 'number') return `${width}px`;
return width; return width;
} }
@ -76,67 +93,31 @@ export class NgDatatableComponent implements OnInit {
addFilter(...filters: ((row?: any, index?: number, arr?: any[]) => boolean)[]) { addFilter(...filters: ((row?: any, index?: number, arr?: any[]) => boolean)[]) {
this.filters = this.filters.concat(filters); this.filters = this.filters.concat(filters);
this.refresh(); this._process();
this.filterChanged.emit(this.filters); 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) { changePage(page: number) {
if (!this.paginate || page < 1 || page > this.pages.length) return; if(!this.paginate || page < 1 || page > this.pages.length) return;
this.page = page; this.page = page;
this.refresh(); this._process();
this.pageChanged.emit(this.page); this.pageChanged.emit(this.page);
} }
clearFilters(update = true) { clearFilters(update=true) {
this.filters = []; this.filters = [];
if (update) this.refresh(); if(update) this._process();
this.filterChanged.emit(this.filters); this.filterChanged.emit(this.filters);
} }
clearSelected() { clearSelected() {
let emit = this.selectedRows.size > 0; let emit = this.selectedRows.size > 0;
this.selectedRows.clear(); this.selectedRows.clear();
if (emit) this.selectionChanged.emit([]); if(emit) this.selectionChanged.emit([]);
} }
@HostListener('window:resize', ['$event']) @HostListener('window:resize', ['$event'])
onResize(event) { onResize(event) { this.width = event.target.innerWidth; }
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() { selectAll() {
this.processedData.forEach((ignore, i) => this.selectedRows.add(i)); this.processedData.forEach((ignore, i) => this.selectedRows.add(i));
@ -148,25 +129,24 @@ export class NgDatatableComponent implements OnInit {
if (!column || column.sort === false) return; // If column is un-sortable return if (!column || column.sort === false) return; // If column is un-sortable return
// Figure out sorting direction if not supplied // Figure out sorting direction if not supplied
if (desc === undefined) { if(desc === undefined) {
desc = false; desc = false;
if (columnIndex == this.sortedColumn) desc = !this.sortedDesc; if(columnIndex == this.sortedColumn) desc = !this.sortedDesc;
} }
this.sortedColumn = columnIndex; this.sortedColumn = columnIndex;
this.sortedDesc = desc; this.sortedDesc = desc;
// Preform sort // Preform sort
this.refresh(); this._process();
} }
updateSelected(index: number, column?: Column) { updateSelected(index: number) {
if (this.selectionMode == null) return; if (this.selectionMode == null) return;
if (column && column.canSelect === false) return;
if (this.selectionMode == 'single') { if (this.selectionMode == 'single') {
let alreadySelected = this.selectedRows.has(index); let alreadySelected = this.selectedRows.has(index);
this.selectedRows.clear(); this.selectedRows.clear();
if (!alreadySelected) this.selectedRows.add(index); if(!alreadySelected) this.selectedRows.add(index);
} else { } else {
if (this.selectedRows.has(index)) { if (this.selectedRows.has(index)) {
this.selectedRows.delete(index); this.selectedRows.delete(index);

View File

@ -25,5 +25,8 @@
"strictInjectionParameters": true, "strictInjectionParameters": true,
"flatModuleId": "AUTOGENERATED", "flatModuleId": "AUTOGENERATED",
"flatModuleOutFile": "AUTOGENERATED" "flatModuleOutFile": "AUTOGENERATED"
} },
"exclude": [
"**/*.spec.ts"
]
} }

View File

View File

@ -13,7 +13,7 @@ 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]="true" [selectionMode]="selectionMode == 'None' ? null : selectionMode" (rowSelected)="log($event)"> [showCheckbox]="checkbox" [paginate]="false" [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>

View File

@ -0,0 +1,27 @@
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

View File

@ -1,9 +1,9 @@
import {NgModule} from '@angular/core'; import { BrowserModule } from '@angular/platform-browser';
import {FormsModule} from "@angular/forms"; import { NgModule } from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {NgDatatableModule} from "../../projects/ng-datatable/src/lib/ng-datatable.module";
import {AppComponent} from './app.component'; import { AppComponent } from './app.component';
import {FormsModule} from "@angular/forms";
import {NgDatatableModule} from "../../projects/ng-datatable/src/lib/ng-datatable.module";
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -17,4 +17,4 @@ import {AppComponent} from './app.component';
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })
export class AppModule {} export class AppModule { }

0
src/assets/.gitkeep Normal file
View File

View File

@ -1,3 +1,15 @@
// 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 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

31
src/karma.conf.js Normal file
View File

@ -0,0 +1,31 @@
// 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 Normal file
View File

@ -0,0 +1,20 @@
// 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);

12
src/tsconfig.app.json Normal file
View File

@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "es2015",
"types": []
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

19
src/tsconfig.spec.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"module": "commonjs",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts",
"polyfills.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

17
src/tslint.json Normal file
View File

@ -0,0 +1,17 @@
{
"extends": "../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
]
}
}

View File

@ -2,8 +2,7 @@
"compileOnSave": false, "compileOnSave": false,
"compilerOptions": { "compilerOptions": {
"baseUrl": "./", "baseUrl": "./",
"outDir": "../out-tsc/app", "outDir": "./dist/out-tsc",
"module": "es2015",
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,
"moduleResolution": "node", "moduleResolution": "node",

130
tslint.json Normal file
View File

@ -0,0 +1,130 @@
{
"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
}
}

7689
yarn.lock Normal file

File diff suppressed because it is too large Load Diff