diff --git a/.editorconfig b/.editorconfig index 6e87a00..9b73521 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,7 +4,7 @@ root = true [*] charset = utf-8 indent_style = space -indent_size = 2 +indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true diff --git a/projects/ng-datatable/package.json b/projects/ng-datatable/package.json index b3bacf8..a18d7b0 100644 --- a/projects/ng-datatable/package.json +++ b/projects/ng-datatable/package.json @@ -1,6 +1,6 @@ { "name": "@ztimson/ng-datatable", - "version": "1.9.7", + "version": "1.10.7", "homepage": "https://github.com/ztimson/ng-datatable", "license": "Apache-2.0", "author": { diff --git a/projects/ng-datatable/src/lib/column.ts b/projects/ng-datatable/src/lib/column.ts index f153076..e60848c 100644 --- a/projects/ng-datatable/src/lib/column.ts +++ b/projects/ng-datatable/src/lib/column.ts @@ -3,6 +3,7 @@ import {TemplateRef} from "@angular/core"; export interface Column { aggregate?: (rows: any[]) => any; cssClass?: string; // CSS to add to column + canSelect?: boolean; hide?: boolean; // Hide column hideMobile?: boolean; // Hide column on mobile initialSort?: 'asc' | 'desc'; // Sort this column initially diff --git a/projects/ng-datatable/src/lib/ng-datatable.component.html b/projects/ng-datatable/src/lib/ng-datatable.component.html index 912aa6f..b542e99 100644 --- a/projects/ng-datatable/src/lib/ng-datatable.component.html +++ b/projects/ng-datatable/src/lib/ng-datatable.component.html @@ -25,16 +25,16 @@ - - + + - + - + {{value}} diff --git a/projects/ng-datatable/src/lib/ng-datatable.component.ts b/projects/ng-datatable/src/lib/ng-datatable.component.ts index e9aff4a..fc3eb90 100644 --- a/projects/ng-datatable/src/lib/ng-datatable.component.ts +++ b/projects/ng-datatable/src/lib/ng-datatable.component.ts @@ -2,171 +2,179 @@ import {Component, EventEmitter, HostListener, Input, OnInit, Output, TemplateRe import {Column} from './column'; @Component({ - selector: 'ng-datatable', - templateUrl: 'ng-datatable.component.html', - styles: ['.ngdt-expand {font-family: sans-serif;}'] + selector: 'ng-datatable', + templateUrl: 'ng-datatable.component.html', + styles: ['.ngdt-expand {font-family: sans-serif;}'] }) 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; // 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() paginateCssClass: string; // CSS class to add to paginator - @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 + // Inputs ============================================================================================================ + @Input() cssClass: string; // CSS class to add to table element + @Input() columns: Column[] = []; // Columns to display on table + @Input() expandedTemplate: TemplateRef; // 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() paginateCssClass: string; // CSS class to add to paginator + @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(); // Output when filters change - @Output() finished = new EventEmitter(); // Fired after processing is finished - @Output() pageChanged = new EventEmitter(); // Output when page is changed - @Output() processing = new EventEmitter(); // Fires when grid begins to process - @Output() selectionChanged = new EventEmitter(); // Output when selected rows changes + // Outputs =========================================================================================================== + @Output() filterChanged = new EventEmitter(); // Output when filters change + @Output() finished = new EventEmitter(); // Fired after processing is finished + @Output() pageChanged = new EventEmitter(); // Output when page is changed + @Output() processing = new EventEmitter(); // Fires when grid begins to process + @Output() selectionChanged = new EventEmitter(); // 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(); // 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 + // 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(); // 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 ? this.processedData.length : 0; } // 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.refresh(); - } - - // =================================================================================================================== - 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 =========================================================================================================== - _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(); + // Fields ============================================================================================================ + get count(): number { + return this.processedData ? this.processedData.length : 0; + } // 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.refresh(); } - 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) { - 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); - } + // =================================================================================================================== + constructor() { } - 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))); + } } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 011e32a..483f543 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -61,7 +61,7 @@ export class AppComponent implements OnInit { return `Males: ${total.M}, Females: ${total.F}`; } }, - {label: 'Age', property: 'age', initialSort: 'desc', hideMobile: true, template: this.ageTemplate} + {label: 'Age', canSelect: false, property: 'age', initialSort: 'desc', hideMobile: true, template: this.ageTemplate} ]; this.search.subscribe(text => {