This commit is contained in:
2018-06-07 12:17:36 -04:00
parent 01090d6a6b
commit 2d1885ccbf
17 changed files with 1494 additions and 98 deletions

View File

@ -0,0 +1,8 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/ng-datatable",
"deleteDestPath": false,
"lib": {
"entryFile": "src/public_api.ts"
}
}

View File

@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/ng-datatable",
"lib": {
"entryFile": "src/public_api.ts"
}
}

View File

@ -0,0 +1,8 @@
{
"name": "ng-datatable",
"version": "1.0.0",
"peerDependencies": {
"@angular/common": "^6.0.0-rc.0 || ^6.0.0",
"@angular/core": "^6.0.0-rc.0 || ^6.0.0"
}
}

View File

@ -0,0 +1,14 @@
import {TemplateRef} from "@angular/core";
export interface Column {
cssClass?: string; // CSS to add to column
hide?: boolean; // Hide column
hideMobile?: boolean; // Hide column on mobile
initialSort?: 'asc' | 'desc'; // Sort this column initially
label?: string; // Column name
property?: string; // object property to provide to cell
sort?: boolean; // Allow sorting
sortFn?: (a: any, b: any) => 1 | 0 | -1; // Custom sorting function
template?: TemplateRef<any>; // TemplateRef to render the column
width?: string; // Width to give column
}

View File

@ -0,0 +1,57 @@
<table [class]="cssClass" [style.table-layout]="tableLayout">
<colgroup>
<col *ngIf="showCheckbox && selectionMode !== null" width="30px">
<col *ngIf="expandedTemplate" width="30px">
<ng-container *ngFor="let c of columns">
<col *ngIf="c.hide !== true && !(c.hideMobile === true && width < mobileBreakpoint)" span="1" [width]="c.width">
</ng-container>
</colgroup>
<thead>
<tr>
<th *ngIf="showCheckbox && selectionMode !== null">
<input *ngIf="selectionMode == 'multi'" type="checkbox" [checked]="processedData.length == selectedRows.size"
(change)="$event.target.checked ? selectAll() : clearSelected()"/>
</th>
<th *ngIf="expandedTemplate"></th>
<ng-container *ngFor="let c of columns; let i = index">
<th *ngIf="c.hide !== true && !(c.hideMobile === true && width < mobileBreakpoint)" [class]="c.cssClass"
style="cursor: pointer" (click)="sort(i)">
<span *ngIf="sortedColumn == i && !sortedDesc">&#9650;</span>
<span *ngIf="sortedColumn == i && sortedDesc">&#9660;</span>
{{c.label}}
</th>
</ng-container>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let row of pagedData; let i = index">
<tr [ngClass]="{'active': selectedRows.has(i)}" (click)="updateSelected(i)">
<td *ngIf="showCheckbox && selectionMode !== null"><input type="checkbox" [checked]="selectedRows.has(i)"/></td>
<td *ngIf="expandedTemplate">
<span *ngIf="!selectedRows.has(i)">&#9658;</span>
<span *ngIf="selectedRows.has(i)">&#9660;</span>
</td>
<ng-container *ngFor="let c of columns">
<td *ngIf="c.hide !== true && !(c.hideMobile === true && width < mobileBreakpoint)">
<ng-template #defaultTemplate let-value="value">{{value}}</ng-template>
<ng-template [ngTemplateOutlet]="c.template || defaultTemplate"
[ngTemplateOutletContext]="{object: row, value: row[c.property]}">
</ng-template>
</td>
</ng-container>
</tr>
<tr *ngIf="expandedTemplate && selectedRows.has(i)">
<td [attr.colspan]="columns.length + (showCheckbox ? 1 : 0) + (expandedTemplate ? 1 : 0)">
<ng-template [ngTemplateOutlet]="expandedTemplate" [ngTemplateOutletContext]="{object: row}"></ng-template>
</td>
</tr>
</ng-container>
</tbody>
</table>
<nav *ngIf="paginate" aria-label="Page navigation">
<ul class="pagination">
<li class="page-item" [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>
<li class="page-item" [ngClass]="{'disabled': page >= pages.length}" (click)="changePage(page + 1)"><a class="page-link">Next</a></li>
</ul>
</nav>

View File

@ -0,0 +1,150 @@
import {Component, EventEmitter, HostListener, Input, OnInit, Output, TemplateRef} from '@angular/core';
import {Column} from './column';
@Component({
selector: 'ng-datatable',
templateUrl: 'ng-datatable.component.html'
})
export class NgDatatableComponent implements OnInit {
// Inputs ============================================================================================================
@Input() cssClass: string; // CSS class to add to table element
@Input() columns: Column[] = []; // Columns to display on table
@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() pageLength: number = 20; // Number of rows per page
@Input() page: number = 1; // Current page number
@Input() paginate: boolean = true; // Should we paginate results
@Input() selectionMode: null | 'single' | 'multi'; // Allow selecting no/single/multiple rows
@Input() showCheckbox: boolean; // Selection checkboxes
@Input() tableLayout: 'auto' | 'fixed' = 'auto'; // How column widths are decided
// Outputs ===========================================================================================================
@Output() filterChanged = new EventEmitter<any[]>(); // Output when filters change
@Output() pageChanged = new EventEmitter<number>(); // Output when page is changed
@Output() selectionChanged = new EventEmitter<any[]>(); // Output when selected rows changes
// Properties ========================================================================================================
filters: ((el?: any, i?: number, arr?: any[]) => boolean)[] = []; // Array of process functions to apply to data
pages: number[] = []; // Array of possible pages
pagedData: any[] = []; // The data for the current
processedData: any[]; // rows left after filtering
selectedRows = new Set<number>(); // Keep track of selected rows
sortedColumn: number; // Column currently being sorted
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
// Fields ============================================================================================================
get count(): number { return this.processedData.length; } // Number of rows after filtering
private _data: any[] = []; // Original data entered into table
get data(): any[] { return this.processedData; } // Return the processed data
@Input() set data(data: any[]) {
this._data = data;
this.process();
}
// ===================================================================================================================
constructor() { }
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);
this.pagedData = this.processedData.filter((ignore, i) => i >= (this.page - 1) * this.pageLength && i < this.page * this.pageLength);
} else {
this.pagedData = this.processedData;
}
}
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)));
}
}

View File

@ -0,0 +1,14 @@
import {NgModule} from '@angular/core';
import {NgDatatableComponent} from './ng-datatable.component';
import {FormsModule} from "@angular/forms";
import {BrowserModule} from "@angular/platform-browser";
@NgModule({
imports: [
BrowserModule,
FormsModule,
],
declarations: [NgDatatableComponent],
exports: [NgDatatableComponent]
})
export class NgDatatableModule { }

View File

@ -0,0 +1,3 @@
export * from './lib/column'
export * from './lib/ng-dataTable.component';
export * from './lib/ng-dataTable.module';

View File

@ -0,0 +1,32 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"module": "es2015",
"moduleResolution": "node",
"declaration": true,
"sourceMap": true,
"inlineSources": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"types": [],
"lib": [
"dom",
"es2015"
]
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true,
"flatModuleId": "AUTOGENERATED",
"flatModuleOutFile": "AUTOGENERATED"
},
"exclude": [
"**/*.spec.ts"
]
}