import {NgModule, Component, ElementRef, AfterContentInit, AfterViewInit, AfterViewChecked, OnInit, OnDestroy, Input,
ViewContainerRef, ViewChild, IterableDiffers,
Output, EventEmitter, ContentChild, ContentChildren, Renderer2, QueryList, TemplateRef,
ChangeDetectorRef, Inject, forwardRef, EmbeddedViewRef, NgZone
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms'
import {SharedModule} from '../common/shared';
import {PaginatorModule} from '../paginator/paginator';
import {Column,Header,Footer,HeaderColumnGroup,FooterColumnGroup,PrimeTemplate} from '../common/shared';
import {LazyLoadEvent} from '../common/lazyloadevent';
import {FilterMetadata} from '../common/filtermetadata';
import {SortMeta} from '../common/sortmeta';
import {DomHandler} from '../dom/domhandler';
import {ObjectUtils} from '../utils/objectutils';
import {Subscription} from 'rxjs/Subscription';
import {BlockableUI} from '../common/blockableui';
@Component({
selector: 'p-dtRadioButton',
template: `
`
})
export class DTRadioButton {
@Input() checked: boolean;
@Output() onClick: EventEmitter = new EventEmitter();
public hover: boolean;
handleClick(event) {
this.onClick.emit(event);
}
}
@Component({
selector: 'p-dtCheckbox',
template: `
`
})
export class DTCheckbox {
@Input() checked: boolean;
@Input() disabled: boolean;
@Output() onChange: EventEmitter = new EventEmitter();
public hover: boolean;
handleClick(event) {
if(!this.disabled) {
this.onChange.emit({originalEvent: event, checked: !this.checked});
}
}
}
@Component({
selector: '[pColumnHeaders]',
template: `
{{col.header}}
|
`
})
export class ColumnHeaders {
constructor(@Inject(forwardRef(() => DataTable)) public dt:DataTable) {}
@Input("pColumnHeaders") columns: Column[];
}
@Component({
selector: '[pColumnFooters]',
template: `
{{col.footer}}
|
`
})
export class ColumnFooters {
constructor(@Inject(forwardRef(() => DataTable)) public dt:DataTable) {}
@Input("pColumnFooters") columns: Column[];
}
@Component({
selector: '[pTableBody]',
template: `
|
{{col.header}}
{{dt.resolveFieldData(rowData,col.field)}}
|
|
|
|
{{dt.emptyMessage}}
|
`
})
export class TableBody {
constructor(@Inject(forwardRef(() => DataTable)) public dt:DataTable) {}
@Input("pTableBody") columns: Column[];
@Input() data: any[];
visibleColumns() {
return this.columns ? this.columns.filter(c => !c.hidden): [];
}
}
@Component({
selector: '[pScrollableView]',
template: `
`
})
export class ScrollableView implements AfterViewInit,AfterViewChecked,OnDestroy {
constructor(@Inject(forwardRef(() => DataTable)) public dt:DataTable, public domHandler: DomHandler, public el: ElementRef, public renderer: Renderer2, public zone: NgZone) {}
@Input("pScrollableView") columns: Column[];
@Input() headerColumnGroup: HeaderColumnGroup;
@Input() footerColumnGroup: HeaderColumnGroup;
@ViewChild('scrollHeader') scrollHeaderViewChild: ElementRef;
@ViewChild('scrollHeaderBox') scrollHeaderBoxViewChild: ElementRef;
@ViewChild('scrollBody') scrollBodyViewChild: ElementRef;
@ViewChild('scrollTable') scrollTableViewChild: ElementRef;
@ViewChild('scrollTableWrapper') scrollTableWrapperViewChild: ElementRef;
@ViewChild('scrollFooter') scrollFooterViewChild: ElementRef;
@ViewChild('scrollFooterBox') scrollFooterBoxViewChild: ElementRef;
@Input() frozen: boolean;
@Input() width: string;
@Input() virtualScroll: boolean;
@Output() onVirtualScroll: EventEmitter = new EventEmitter();
public scrollBody: HTMLDivElement;
public scrollHeader: HTMLDivElement;
public scrollHeaderBox: HTMLDivElement;
public scrollTable: HTMLDivElement;
public scrollTableWrapper: HTMLDivElement;
public scrollFooter: HTMLDivElement;
public scrollFooterBox: HTMLDivElement;
public bodyScrollListener: Function;
public headerScrollListener: Function;
public scrollBodyMouseWheelListener: Function;
public scrollFunction: Function;
public rowHeight: number;
public scrollTimeout: any;
ngAfterViewInit() {
this.initScrolling();
}
ngAfterViewChecked() {
if(this.virtualScroll && !this.rowHeight) {
let row = this.domHandler.findSingle(this.scrollTable, 'tr.ui-widget-content:not(.ui-datatable-emptymessage-row)');
if(row) {
this.rowHeight = this.domHandler.getOuterHeight(row);
}
}
if(!this.frozen) {
this.zone.runOutsideAngular(() => {
setTimeout(() => {
this.alignScrollBar();
}, 1);
});
}
}
initScrolling() {
this.scrollHeader = this.scrollHeaderViewChild.nativeElement;
this.scrollHeaderBox = this.scrollHeaderBoxViewChild.nativeElement;
this.scrollBody = this.scrollBodyViewChild.nativeElement;
this.scrollTable = this.scrollTableViewChild.nativeElement;
this.scrollTableWrapper = this.scrollTableWrapperViewChild.nativeElement;
this.scrollFooter = this.scrollFooterViewChild ? this.scrollFooterViewChild.nativeElement : null;
this.scrollFooterBox = this.scrollFooterBoxViewChild ? this.scrollFooterBoxViewChild.nativeElement : null;
this.setScrollHeight();
if(!this.frozen) {
this.zone.runOutsideAngular(() => {
this.scrollHeader.addEventListener('scroll', this.onHeaderScroll.bind(this));
this.scrollBody.addEventListener('scroll', this.onBodyScroll.bind(this));
});
}
if(!this.frozen) {
this.alignScrollBar();
}
else {
this.scrollBody.style.paddingBottom = this.domHandler.calculateScrollbarWidth() + 'px';
}
}
onBodyScroll(event) {
let frozenView = this.el.nativeElement.previousElementSibling;
if(frozenView) {
var frozenScrollBody = this.domHandler.findSingle(frozenView, '.ui-datatable-scrollable-body');
}
this.scrollHeaderBox.style.marginLeft = -1 * this.scrollBody.scrollLeft + 'px';
if(this.scrollFooterBox) {
this.scrollFooterBox.style.marginLeft = -1 * this.scrollBody.scrollLeft + 'px';
}
if(frozenScrollBody) {
frozenScrollBody.scrollTop = this.scrollBody.scrollTop;
}
if(this.virtualScroll) {
let viewport = this.domHandler.getOuterHeight(this.scrollBody);
let tableHeight = this.domHandler.getOuterHeight(this.scrollTable);
let pageHeight = this.rowHeight * this.dt.rows;
let virtualTableHeight = this.domHandler.getOuterHeight(this.scrollTableWrapper);
let pageCount = (virtualTableHeight / pageHeight)||1;
if(this.scrollBody.scrollTop + viewport > parseFloat(this.scrollTable.style.top) + tableHeight || this.scrollBody.scrollTop < parseFloat(this.scrollTable.style.top)) {
let page = Math.floor((this.scrollBody.scrollTop * pageCount) / (this.scrollBody.scrollHeight)) + 1;
this.onVirtualScroll.emit({
page: page,
callback: () => {
this.scrollTable.style.top = ((page - 1) * pageHeight) + 'px';
}
});
}
}
}
setScrollHeight() {
if(this.dt.scrollHeight) {
if(this.dt.scrollHeight.indexOf('%') !== -1) {
this.scrollBody.style.visibility = 'hidden';
this.scrollBody.style.height = '100px'; //temporary height to calculate static height
let containerHeight = this.domHandler.getOuterHeight(this.dt.el.nativeElement.children[0]);
let relativeHeight = this.domHandler.getOuterHeight(this.dt.el.nativeElement.parentElement) * parseInt(this.dt.scrollHeight) / 100;
let staticHeight = containerHeight - 100; //total height of headers, footers, paginators
let scrollBodyHeight = (relativeHeight - staticHeight);
this.scrollBody.style.height = 'auto';
this.scrollBody.style.maxHeight = scrollBodyHeight + 'px';
this.scrollBody.style.visibility = 'visible';
}
else {
this.scrollBody.style.maxHeight = this.dt.scrollHeight;
}
}
}
onHeaderScroll(event) {
this.scrollHeader.scrollLeft = 0;
}
hasVerticalOverflow() {
return this.domHandler.getOuterHeight(this.scrollTable) > this.domHandler.getOuterHeight(this.scrollBody);
}
alignScrollBar() {
let scrollBarWidth = this.hasVerticalOverflow() ? this.domHandler.calculateScrollbarWidth() : 0;
this.scrollHeaderBox.style.marginRight = scrollBarWidth + 'px';
if(this.scrollFooterBox) {
this.scrollFooterBox.style.marginRight = scrollBarWidth + 'px';
}
}
ngOnDestroy() {
this.scrollHeader.removeEventListener('scroll', this.onHeaderScroll);
this.scrollBody.removeEventListener('scroll', this.onBodyScroll);
}
}
@Component({
selector: 'p-dataTable',
template: `
`,
providers: [DomHandler,ObjectUtils]
})
export class DataTable implements AfterViewChecked,AfterViewInit,AfterContentInit,OnInit,OnDestroy,BlockableUI {
@Input() paginator: boolean;
@Input() rows: number;
@Input() pageLinks: number = 5;
@Input() rowsPerPageOptions: number[];
@Input() responsive: boolean;
@Input() stacked: boolean;
@Input() selectionMode: string;
@Output() selectionChange: EventEmitter = new EventEmitter();
@Input() editable: boolean;
@Input() showHeaderCheckbox: boolean = true;
@Output() onRowClick: EventEmitter = new EventEmitter();
@Output() onRowSelect: EventEmitter = new EventEmitter();
@Output() onRowUnselect: EventEmitter = new EventEmitter();
@Output() onRowDblclick: EventEmitter = new EventEmitter();
@Output() onHeaderCheckboxToggle: EventEmitter = new EventEmitter();
@Input() headerCheckboxToggleAllPages: boolean;
@Output() onContextMenuSelect: EventEmitter = new EventEmitter();
@Input() filterDelay: number = 300;
@Input() lazy: boolean;
@Output() onLazyLoad: EventEmitter = new EventEmitter();
@Input() resizableColumns: boolean;
@Input() columnResizeMode: string = 'fit';
@Output() onColResize: EventEmitter = new EventEmitter();
@Input() reorderableColumns: boolean;
@Output() onColReorder: EventEmitter = new EventEmitter();
@Input() scrollable: boolean;
@Input() virtualScroll: boolean;
@Input() scrollHeight: any;
@Input() scrollWidth: any;
@Input() frozenWidth: any;
@Input() unfrozenWidth: any;
@Input() style: any;
@Input() styleClass: string;
@Input() tableStyle: any;
@Input() tableStyleClass: string;
@Input() globalFilter: any;
@Input() sortMode: string = 'single';
@Input() defaultSortOrder: number = 1;
@Input() groupField: string;
@Input() contextMenu: any;
@Input() csvSeparator: string = ',';
@Input() exportFilename: string = 'download';
@Input() emptyMessage: string = 'No records found';
@Input() paginatorPosition: string = 'bottom';
@Input() alwaysShowPaginator: boolean = true;
@Input() metaKeySelection: boolean = true;
@Input() rowTrackBy: Function = (index: number, item: any) => item;
@Input() immutable: boolean = true;
@Input() frozenValue: any[];
@Input() compareSelectionBy: string = 'deepEquals';
@Output() onEditInit: EventEmitter = new EventEmitter();
@Output() onEditComplete: EventEmitter = new EventEmitter();
@Output() onEdit: EventEmitter = new EventEmitter();
@Output() onEditCancel: EventEmitter = new EventEmitter();
@Output() onPage: EventEmitter = new EventEmitter();
@Output() onSort: EventEmitter = new EventEmitter();
@Output() onFilter: EventEmitter = new EventEmitter();
@ContentChild(Header) header;
@ContentChild(Footer) footer;
@Input() expandableRows: boolean;
@Input() expandedRows: any[];
@Input() expandableRowGroups: boolean;
@Input() rowExpandMode: string = 'multiple';
@Input() public expandedRowsGroups: any[];
@Input() expandedIcon: string = 'fa-chevron-circle-down';
@Input() collapsedIcon: string = 'fa-chevron-circle-right';
@Input() tabindex: number = 1;
@Input() rowStyleClass: Function;
@Input() rowStyleMap: Object;
@Input() rowGroupMode: string;
@Input() sortableRowGroup: boolean = true;
@Input() sortFile: string;
@Input() rowHover: boolean;
@Input() public filters: {[s: string]: FilterMetadata;} = {};
@Input() dataKey: string;
@Input() loading: boolean;
@Input() loadingIcon: string = 'fa-circle-o-notch';
@Input() virtualScrollDelay: number = 500;
@Input() rowGroupExpandMode: string = 'multiple';
@Output() valueChange: EventEmitter = new EventEmitter();
@Output() firstChange: EventEmitter = new EventEmitter();
@Output() onRowExpand: EventEmitter = new EventEmitter();
@Output() onRowCollapse: EventEmitter = new EventEmitter();
@Output() onRowGroupExpand: EventEmitter = new EventEmitter();
@Output() onRowGroupCollapse: EventEmitter = new EventEmitter();
@ContentChildren(PrimeTemplate) templates: QueryList;
@ContentChildren(Column) cols: QueryList;
@ContentChildren(HeaderColumnGroup) headerColumnGroups: QueryList;
@ContentChildren(FooterColumnGroup) footerColumnGroups: QueryList;
public _value: any[];
public dataToRender: any[];
public page: number = 0;
public filterTimeout: any;
public filteredValue: any[];
public columns: Column[];
public frozenColumns: Column[];
public scrollableColumns: Column[];
public frozenHeaderColumnGroup: HeaderColumnGroup;
public scrollableHeaderColumnGroup: HeaderColumnGroup;
public frozenFooterColumnGroup: HeaderColumnGroup;
public scrollableFooterColumnGroup: HeaderColumnGroup;
public columnsChanged: boolean = false;
public sortColumn: Column;
public columnResizing: boolean;
public lastResizerHelperX: number;
public documentEditListener: Function;
public documentColumnResizeEndListener: Function;
public resizerHelper: any;
public resizeColumn: any;
public reorderIndicatorUp: any;
public reorderIndicatorDown: any;
public iconWidth: number;
public iconHeight: number;
public draggedColumn: any;
public dropPosition: number;
public tbody: any;
public rowTouched: boolean;
public rowGroupToggleClick: boolean;
public editingCell: any;
public virtualTableHeight: number;
public rowGroupMetadata: any;
public rowGroupHeaderTemplate: TemplateRef;
public rowGroupFooterTemplate: TemplateRef;
public rowExpansionTemplate: TemplateRef;
public emptyMessageTemplate: TemplateRef;
public paginatorLeftTemplate: TemplateRef;
public paginatorRightTemplate: TemplateRef;
public scrollBarWidth: number;
public editorClick: boolean;
public _first: number = 0;
public selectionKeys: any;
public preventSelectionKeysPropagation: boolean;
public preventSortPropagation: boolean;
public preventRowClickPropagation: boolean;
_multiSortMeta: SortMeta[];
_sortField: string;
_sortOrder: number = 1;
differ: any;
_selection: any;
_totalRecords: number;
globalFilterFunction: any;
columnsSubscription: Subscription;
totalRecordsChanged: boolean;
anchorRowIndex: number;
rangeRowIndex: number;
initialized: boolean;
virtualScrollTimer: any;
virtualScrollableTableWrapper: HTMLDivElement;
virtualScrollCallback: Function;
editChanged: boolean;
constructor(public el: ElementRef, public domHandler: DomHandler, public differs: IterableDiffers,
public renderer: Renderer2, public changeDetector: ChangeDetectorRef, public objectUtils: ObjectUtils,
public zone: NgZone) {
this.differ = differs.find([]).create(null);
}
ngOnInit() {
if(this.lazy) {
this.onLazyLoad.emit(this.createLazyLoadMetadata());
}
}
ngAfterContentInit() {
this.initColumns();
this.initColumnGroups();
this.columnsSubscription = this.cols.changes.subscribe(_ => {
this.initColumns();
this.changeDetector.markForCheck();
});
this.templates.forEach((item) => {
switch(item.getType()) {
case 'rowexpansion':
this.rowExpansionTemplate = item.template;
break;
case 'rowgroupheader':
this.rowGroupHeaderTemplate = item.template;
break;
case 'rowgroupfooter':
this.rowGroupFooterTemplate = item.template;
break;
case 'emptymessage':
this.emptyMessageTemplate = item.template;
break;
case 'paginatorLeft':
this.paginatorLeftTemplate = item.template;
break;
case 'paginatorRight':
this.paginatorRightTemplate = item.template;
break;
}
});
}
ngAfterViewChecked() {
if(this.columnsChanged && this.el.nativeElement.offsetParent) {
if(this.resizableColumns) {
this.initResizableColumns();
}
if(this.reorderableColumns) {
this.initColumnReordering();
}
this.columnsChanged = false;
}
if(this.totalRecordsChanged && this.virtualScroll && this.virtualScrollableTableWrapper && this.virtualScrollableTableWrapper.offsetParent) {
let row = this.domHandler.findSingle(this.virtualScrollableTableWrapper,'tr.ui-widget-content');
let rowHeight = this.domHandler.getOuterHeight(row);
this.virtualTableHeight = this._totalRecords * rowHeight;
this.virtualScrollableTableWrapper.style.height = this.virtualTableHeight + 'px';
this.totalRecordsChanged = false;
}
}
ngAfterViewInit() {
if(this.globalFilter) {
this.globalFilterFunction = this.renderer.listen(this.globalFilter, 'keyup', () => {
if (this.filterTimeout) {
clearTimeout(this.filterTimeout);
}
this.filterTimeout = setTimeout(() => {
this._filter();
this.filterTimeout = null;
}, this.filterDelay);
});
}
this.virtualScrollableTableWrapper = this.domHandler.findSingle(this.el.nativeElement, 'div.ui-datatable-scrollable-table-wrapper');
this.initialized = true;
}
@Input() get multiSortMeta(): SortMeta[]{
return this._multiSortMeta;
}
set multiSortMeta(val: SortMeta[]){
this._multiSortMeta = val;
if(this.sortMode === 'multiple') {
this.sortMultiple();
}
}
@Input() get sortField(): string{
return this._sortField;
}
set sortField(val: string){
this._sortField = val;
if(this.sortMode === 'single') {
this.sortSingle();
}
}
@Input() get sortOrder(): number {
return this._sortOrder;
}
set sortOrder(val: number) {
this._sortOrder = val;
if(this.sortMode === 'single') {
this.sortSingle();
}
}
@Input() get value(): any[] {
return this._value;
}
set value(val:any[]) {
if(this.immutable) {
this._value = val ? [...val] : null;
this.handleDataChange();
}
else {
this._value = val;
}
this.valueChange.emit(this.value);
}
@Input() get first(): number {
return this._first;
}
set first(val:number) {
let shouldPaginate = this.initialized && this._first !== val;
this._first = val;
if(shouldPaginate) {
this.paginate();
}
}
@Input() get totalRecords(): number {
return this._totalRecords;
}
set totalRecords(val:number) {
this._totalRecords = val;
this.totalRecordsChanged = true;
}
@Input() get selection(): any {
return this._selection;
}
set selection(val: any) {
this._selection = val;
if(this.dataKey && !this.preventSelectionKeysPropagation) {
this.selectionKeys = {};
if(this._selection) {
if(Array.isArray(this._selection)) {
for(let data of this._selection) {
this.selectionKeys[String(this.objectUtils.resolveFieldData(data, this.dataKey))] = 1;
}
}
else {
this.selectionKeys[String(this.objectUtils.resolveFieldData(this._selection, this.dataKey))] = 1;
}
}
}
this.preventSelectionKeysPropagation = false;
}
ngDoCheck() {
if(!this.immutable) {
let changes = this.differ.diff(this.value);
if(changes) {
this.handleDataChange();
}
}
}
handleDataChange() {
if(this.paginator) {
this.updatePaginator();
}
if(this.virtualScroll && this.virtualScrollCallback) {
this.virtualScrollCallback();
}
if(!this.lazy) {
if(this.hasFilter()) {
this._filter();
}
if(this.preventSortPropagation) {
this.preventSortPropagation = false;
}
else if(this.sortField||this.multiSortMeta) {
if(!this.sortColumn && this.columns) {
this.sortColumn = this.columns.find(col => col.field === this.sortField && col.sortable === 'custom');
}
if(this.sortMode == 'single')
this.sortSingle();
else if(this.sortMode == 'multiple')
this.sortMultiple();
}
}
this.updateDataToRender(this.filteredValue||this.value);
}
initColumns(): void {
this.columns = this.cols.toArray();
this.initScrollableColumns();
this.columnsChanged = true;
}
initScrollableColumns() {
this.scrollableColumns = [];
this.frozenColumns = [];
for(let col of this.columns) {
if(col.frozen)
this.frozenColumns.push(col);
else
this.scrollableColumns.push(col);
}
}
initColumnGroups(): void {
let headerColumnsGroups = this.headerColumnGroups.toArray();
let footerColumnsGroups = this.footerColumnGroups.toArray();
for(let columnGroup of headerColumnsGroups) {
if(columnGroup.frozen)
this.frozenHeaderColumnGroup = columnGroup;
else
this.scrollableHeaderColumnGroup = columnGroup;
}
for(let columnGroup of footerColumnsGroups) {
if(columnGroup.frozen)
this.frozenFooterColumnGroup = columnGroup;
else
this.scrollableFooterColumnGroup = columnGroup;
}
}
resolveFieldData(data: any, field: string): any {
return this.objectUtils.resolveFieldData(data, field);
}
updateRowGroupMetadata() {
this.rowGroupMetadata = {};
if(this.dataToRender) {
for(let i = 0; i < this.dataToRender.length; i++) {
let rowData = this.dataToRender[i];
let group = this.resolveFieldData(rowData, this.sortField);
if(i == 0) {
this.rowGroupMetadata[group] = {index:0, size: 1};
}
else {
let previousRowData = this.dataToRender[i-1];
let previousRowGroup = this.resolveFieldData(previousRowData, this.sortField);
if(group === previousRowGroup) {
this.rowGroupMetadata[group].size++;
}
else {
this.rowGroupMetadata[group] = {index:i, size: 1};
}
}
}
}
}
updatePaginator() {
//total records
this.updateTotalRecords();
//first
if(this.totalRecords && this.first >= this.totalRecords) {
let numberOfPages = Math.ceil(this.totalRecords/this.rows);
this._first = Math.max((numberOfPages-1) * this.rows, 0);
}
}
updateTotalRecords() {
this.totalRecords = this.lazy ? this.totalRecords : (this.value ? this.value.length: 0);
}
onPageChange(event) {
this._first = event.first;
this.firstChange.emit(this.first);
this.rows = event.rows;
this.paginate();
}
paginate() {
if(this.lazy)
this.onLazyLoad.emit(this.createLazyLoadMetadata());
else
this.updateDataToRender(this.filteredValue||this.value);
this.onPage.emit({
first: this.first,
rows: this.rows
});
}
updateDataToRender(datasource) {
if((this.paginator || this.virtualScroll) && datasource) {
this.dataToRender = [];
let startIndex: number = this.lazy ? 0 : this.first;
let endIndex: number = this.virtualScroll ? this.first + this.rows * 2 : startIndex + this.rows;
for(let i = startIndex; i < endIndex; i++) {
if(i >= datasource.length) {
break;
}
this.dataToRender.push(datasource[i]);
}
}
else {
this.dataToRender = datasource;
}
if(this.rowGroupMode) {
this.updateRowGroupMetadata();
}
this.changeDetector.markForCheck();
}
onVirtualScroll(event) {
this._first = (event.page - 1) * this.rows;
this.virtualScrollCallback = event.callback;
this.zone.run(() => {
if(this.virtualScrollTimer) {
clearTimeout(this.virtualScrollTimer);
}
this.virtualScrollTimer = setTimeout(() => {
if(this.lazy)
this.onLazyLoad.emit(this.createLazyLoadMetadata());
else
this.updateDataToRender(this.filteredValue||this.value);
}, this.virtualScrollDelay);
});
}
onHeaderKeydown(event, column: Column) {
if(event.keyCode == 13) {
this.sort(event, column);
event.preventDefault();
}
}
onHeaderMousedown(event, header: any) {
if(this.reorderableColumns) {
if(event.target.nodeName !== 'INPUT') {
header.draggable = true;
} else if(event.target.nodeName === 'INPUT') {
header.draggable = false;
}
}
}
sort(event, column: Column) {
if(!column.sortable) {
return;
}
let targetNode = event.target;
if(this.domHandler.hasClass(targetNode, 'ui-sortable-column') || this.domHandler.hasClass(targetNode, 'ui-column-title') || this.domHandler.hasClass(targetNode, 'ui-sortable-column-icon')) {
if(!this.immutable) {
this.preventSortPropagation = true;
}
let columnSortField = column.sortField||column.field;
this._sortOrder = (this.sortField === columnSortField) ? this.sortOrder * -1 : this.defaultSortOrder;
this._sortField = columnSortField;
this.sortColumn = column;
let metaKey = event.metaKey||event.ctrlKey;
if(this.sortMode == 'multiple') {
if(!this.multiSortMeta||!metaKey) {
this._multiSortMeta = [];
}
this.addSortMeta({field: this.sortField, order: this.sortOrder});
}
if(this.lazy) {
this._first = 0;
this.onLazyLoad.emit(this.createLazyLoadMetadata());
}
else {
if(this.sortMode == 'multiple')
this.sortMultiple();
else
this.sortSingle();
}
this.onSort.emit({
field: this.sortField,
order: this.sortOrder,
multisortmeta: this.multiSortMeta
});
}
this.updateDataToRender(this.filteredValue||this.value);
}
sortSingle() {
if(this.value) {
if(this.sortColumn && this.sortColumn.sortable === 'custom') {
this.preventSortPropagation = true;
this.sortColumn.sortFunction.emit({
field: this.sortField,
order: this.sortOrder
});
}
else {
this.value.sort((data1, data2) => {
let value1 = this.resolveFieldData(data1, this.sortField);
let value2 = this.resolveFieldData(data2, this.sortField);
let result = null;
if (value1 == null && value2 != null)
result = -1;
else if (value1 != null && value2 == null)
result = 1;
else if (value1 == null && value2 == null)
result = 0;
else if (typeof value1 === 'string' && typeof value2 === 'string')
result = value1.localeCompare(value2);
else
result = (value1 < value2) ? -1 : (value1 > value2) ? 1 : 0;
return (this.sortOrder * result);
});
}
this._first = 0;
if(this.hasFilter()) {
this._filter();
}
}
}
sortMultiple() {
if(this.value) {
this.value.sort((data1,data2) => {
return this.multisortField(data1, data2, this.multiSortMeta, 0);
});
if(this.hasFilter()) {
this._filter();
}
}
}
multisortField(data1,data2,multiSortMeta,index) {
let value1 = this.resolveFieldData(data1, multiSortMeta[index].field);
let value2 = this.resolveFieldData(data2, multiSortMeta[index].field);
let result = null;
if (typeof value1 == 'string' || value1 instanceof String) {
if (value1.localeCompare && (value1 != value2)) {
return (multiSortMeta[index].order * value1.localeCompare(value2));
}
}
else {
result = (value1 < value2) ? -1 : 1;
}
if(value1 == value2) {
return (multiSortMeta.length - 1) > (index) ? (this.multisortField(data1, data2, multiSortMeta, index + 1)) : 0;
}
return (multiSortMeta[index].order * result);
}
addSortMeta(meta) {
var index = -1;
for(var i = 0; i < this.multiSortMeta.length; i++) {
if(this.multiSortMeta[i].field === meta.field) {
index = i;
break;
}
}
if(index >= 0)
this.multiSortMeta[index] = meta;
else
this.multiSortMeta.push(meta);
}
isSorted(column: Column) {
if(!column.sortable) {
return false;
}
let columnSortField = column.sortField||column.field;
if(this.sortMode === 'single') {
return (this.sortField && columnSortField === this.sortField);
}
else if(this.sortMode === 'multiple') {
let sorted = false;
if(this.multiSortMeta) {
for(let i = 0; i < this.multiSortMeta.length; i++) {
if(this.multiSortMeta[i].field == columnSortField) {
sorted = true;
break;
}
}
}
return sorted;
}
}
getSortOrder(column: Column) {
let order = 0;
let columnSortField = column.sortField||column.field;
if(this.sortMode === 'single') {
if(this.sortField && columnSortField === this.sortField) {
order = this.sortOrder;
}
}
else if(this.sortMode === 'multiple') {
if(this.multiSortMeta) {
for(let i = 0; i < this.multiSortMeta.length; i++) {
if(this.multiSortMeta[i].field == columnSortField) {
order = this.multiSortMeta[i].order;
break;
}
}
}
}
return order;
}
onRowGroupClick(event) {
if(this.rowGroupToggleClick) {
this.rowGroupToggleClick = false;
return;
}
if(this.sortableRowGroup) {
let targetNode = event.target.nodeName;
if((targetNode == 'TD' || (targetNode == 'SPAN' && !this.domHandler.hasClass(event.target, 'ui-clickable')))) {
if(this.sortField != this.groupField) {
this._sortField = this.groupField;
this.sortSingle();
}
else {
this._sortOrder = -1 * this.sortOrder;
this.sortSingle();
}
}
}
}
clearSelectionRange(event: MouseEvent) {
let rangeStart, rangeEnd;
if(this.rangeRowIndex > this.anchorRowIndex) {
rangeStart = this.anchorRowIndex;
rangeEnd = this.rangeRowIndex;
}
else if(this.rangeRowIndex < this.anchorRowIndex) {
rangeStart = this.rangeRowIndex;
rangeEnd = this.anchorRowIndex;
}
else {
rangeStart = this.rangeRowIndex;
rangeEnd = this.rangeRowIndex;
}
for(let i = rangeStart; i <= rangeEnd; i++) {
let rangeRowData = this.dataToRender[i];
let selectionIndex = this.findIndexInSelection(rangeRowData);
this._selection = this.selection.filter((val,i) => i!=selectionIndex);
let dataKeyValue: string = this.dataKey ? String(this.resolveFieldData(rangeRowData, this.dataKey)) : null;
if(dataKeyValue) {
delete this.selectionKeys[dataKeyValue];
}
this.onRowUnselect.emit({originalEvent: event, data: rangeRowData, type: 'row'});
}
}
selectRange(event: MouseEvent, rowIndex: number) {
let rangeStart, rangeEnd;
if(this.anchorRowIndex > rowIndex) {
rangeStart = rowIndex;
rangeEnd = this.anchorRowIndex;
}
else if(this.anchorRowIndex < rowIndex) {
rangeStart = this.anchorRowIndex;
rangeEnd = rowIndex;
}
else {
rangeStart = rowIndex;
rangeEnd = rowIndex;
}
for(let i = rangeStart; i <= rangeEnd; i++) {
let rangeRowData = this.dataToRender[i];
this._selection = [...this.selection, rangeRowData];
this.selectionChange.emit(this.selection);
let dataKeyValue: string = this.dataKey ? String(this.resolveFieldData(rangeRowData, this.dataKey)) : null;
if(dataKeyValue) {
this.selectionKeys[dataKeyValue] = 1;
}
this.onRowSelect.emit({originalEvent: event, data: rangeRowData, type: 'row'});
}
}
handleRowClick(event: MouseEvent, rowData: any, index: number) {
if(this.preventRowClickPropagation) {
this.preventRowClickPropagation = false;
return;
}
let targetNode = ( event.target).nodeName;
if(targetNode == 'INPUT' || targetNode == 'BUTTON' || targetNode == 'A' || (this.domHandler.hasClass(event.target, 'ui-clickable'))) {
return;
}
this.onRowClick.next({originalEvent: event, data: rowData});
if(this.selectionMode) {
if(this.isMultipleSelectionMode() && event.shiftKey && this.anchorRowIndex != null) {
this.domHandler.clearSelection();
if(this.rangeRowIndex != null) {
this.clearSelectionRange(event);
}
this.rangeRowIndex = index;
this.selectRange(event, index);
}
else {
let selected = this.isSelected(rowData);
let metaSelection = this.rowTouched ? false : this.metaKeySelection;
let dataKeyValue: string = this.dataKey ? String(this.resolveFieldData(rowData, this.dataKey)) : null;
this.anchorRowIndex = index;
this.rangeRowIndex = index;
if(metaSelection) {
let metaKey = event.metaKey||event.ctrlKey;
if(selected && metaKey) {
if(this.isSingleSelectionMode()) {
this._selection = null;
this.selectionKeys = {};
this.selectionChange.emit(null);
}
else {
let selectionIndex = this.findIndexInSelection(rowData);
this._selection = this.selection.filter((val,i) => i!=selectionIndex);
this.selectionChange.emit(this.selection);
if(dataKeyValue) {
delete this.selectionKeys[dataKeyValue];
}
}
this.onRowUnselect.emit({originalEvent: event, data: rowData, type: 'row'});
}
else {
if(this.isSingleSelectionMode()) {
this._selection = rowData;
this.selectionChange.emit(rowData);
if(dataKeyValue) {
this.selectionKeys = {};
this.selectionKeys[dataKeyValue] = 1;
}
}
else if(this.isMultipleSelectionMode()) {
if(metaKey) {
this._selection = this.selection||[];
}
else {
this._selection = [];
this.selectionKeys = {};
}
this._selection = [...this.selection,rowData];
this.selectionChange.emit(this.selection);
if(dataKeyValue) {
this.selectionKeys[dataKeyValue] = 1;
}
}
this.onRowSelect.emit({originalEvent: event, data: rowData, type: 'row'});
}
}
else {
if(this.isSingleSelectionMode()) {
if(selected) {
this._selection = null;
this.selectionKeys = {};
this.selectionChange.emit(this.selection);
this.onRowUnselect.emit({originalEvent: event, data: rowData, type: 'row'});
}
else {
this._selection = rowData;
this.selectionChange.emit(this.selection);
this.onRowSelect.emit({originalEvent: event, data: rowData, type: 'row'});
if(dataKeyValue) {
this.selectionKeys = {};
this.selectionKeys[dataKeyValue] = 1;
}
}
}
else {
if(selected) {
let selectionIndex = this.findIndexInSelection(rowData);
this._selection = this.selection.filter((val,i) => i!=selectionIndex);
this.selectionChange.emit(this.selection);
this.onRowUnselect.emit({originalEvent: event, data: rowData, type: 'row'});
if(dataKeyValue) {
delete this.selectionKeys[dataKeyValue];
}
}
else {
this._selection = [...this.selection||[],rowData];
this.selectionChange.emit(this.selection);
this.onRowSelect.emit({originalEvent: event, data: rowData, type: 'row'});
if(dataKeyValue) {
this.selectionKeys[dataKeyValue] = 1;
}
}
}
}
}
this.preventSelectionKeysPropagation = true;
}
this.rowTouched = false;
}
handleRowTouchEnd(event: Event) {
this.rowTouched = true;
}
selectRowWithRadio(event: Event, rowData:any) {
if(this.selection != rowData) {
this._selection = rowData;
this.selectionChange.emit(this.selection);
this.onRowSelect.emit({originalEvent: event, data: rowData, type: 'radiobutton'});
if(this.dataKey) {
this.selectionKeys = {};
this.selectionKeys[String(this.resolveFieldData(rowData, this.dataKey))] = 1;
}
}
else {
this._selection = null;
this.selectionChange.emit(this.selection);
this.onRowUnselect.emit({originalEvent: event, data: rowData, type: 'radiobutton'});
}
this.preventSelectionKeysPropagation = true;
this.preventRowClickPropagation = true;
}
toggleRowWithCheckbox(event, rowData: any) {
let selectionIndex = this.findIndexInSelection(rowData);
this.selection = this.selection||[];
let dataKeyValue: string = this.dataKey ? String(this.resolveFieldData(rowData, this.dataKey)) : null;
if(selectionIndex != -1) {
this._selection = this.selection.filter((val,i) => i!=selectionIndex);
this.selectionChange.emit(this.selection);
this.onRowUnselect.emit({originalEvent: event, data: rowData, type: 'checkbox'});
if(dataKeyValue) {
delete this.selectionKeys[dataKeyValue];
}
}
else {
this._selection = [...this.selection,rowData];
this.selectionChange.emit(this.selection);
this.onRowSelect.emit({originalEvent: event, data: rowData, type: 'checkbox'});
if(dataKeyValue) {
this.selectionKeys[dataKeyValue] = 1;
}
}
this.preventSelectionKeysPropagation = true;
this.preventRowClickPropagation = true;
}
toggleRowsWithCheckbox(event) {
if(event.checked)
this.selection = this.headerCheckboxToggleAllPages ? this.value.slice() : this.dataToRender.slice();
else
this.selection = [];
this.selectionChange.emit(this.selection);
this.onHeaderCheckboxToggle.emit({originalEvent: event, checked: event.checked});
}
onRowRightClick(event, rowData) {
if(this.contextMenu) {
let selectionIndex = this.findIndexInSelection(rowData);
let selected = selectionIndex != -1;
let dataKeyValue: string = this.dataKey ? String(this.resolveFieldData(rowData, this.dataKey)) : null;
if(!selected) {
if(this.isSingleSelectionMode()) {
this.selection = rowData;
this.selectionChange.emit(rowData);
}
else if(this.isMultipleSelectionMode()) {
this.selection = [rowData];
this.selectionChange.emit(this.selection);
}
if(this.dataKey) {
this.selectionKeys[String(this.resolveFieldData(rowData, this.dataKey))] = 1;
this.preventSelectionKeysPropagation = true;
}
}
this.contextMenu.show(event);
this.onContextMenuSelect.emit({originalEvent: event, data: rowData});
}
}
rowDblclick(event, rowData) {
this.onRowDblclick.emit({originalEvent: event, data: rowData});
}
isSingleSelectionMode() {
return this.selectionMode === 'single';
}
isMultipleSelectionMode() {
return this.selectionMode === 'multiple';
}
findIndexInSelection(rowData: any) {
let index: number = -1;
if(this.selection) {
for(let i = 0; i < this.selection.length; i++) {
if(this.equals(rowData, this.selection[i])) {
index = i;
break;
}
}
}
return index;
}
isSelected(rowData) {
if(rowData && this.selection) {
if(this.dataKey) {
return this.selectionKeys[this.objectUtils.resolveFieldData(rowData, this.dataKey)] !== undefined;
}
else {
if(this.selection instanceof Array)
return this.findIndexInSelection(rowData) > -1;
else
return this.equals(rowData, this.selection);
}
}
return false;
}
equals(data1, data2) {
return this.compareSelectionBy === 'equals' ? (data1 === data2) : this.objectUtils.equals(data1, data2, this.dataKey);
}
get allSelected() {
if(this.headerCheckboxToggleAllPages) {
return this.selection && this.value && this.selection.length === this.value.length;
}
else {
let val = true;
if(this.dataToRender && this.selection && (this.dataToRender.length <= this.selection.length)) {
for(let data of this.dataToRender) {
if(!this.isSelected(data)) {
val = false;
break;
}
}
}
else {
val = false;
}
return val;
}
}
onFilterKeyup(value, field, matchMode) {
if(this.filterTimeout) {
clearTimeout(this.filterTimeout);
}
this.filterTimeout = setTimeout(() => {
this.filter(value, field, matchMode);
this.filterTimeout = null;
}, this.filterDelay);
}
filter(value, field, matchMode) {
if(!this.isFilterBlank(value))
this.filters[field] = {value: value, matchMode: matchMode};
else if(this.filters[field])
delete this.filters[field];
this._filter();
}
isFilterBlank(filter: any): boolean {
if(filter !== null && filter !== undefined) {
if((typeof filter === 'string' && filter.trim().length == 0) || (filter instanceof Array && filter.length == 0))
return true;
else
return false;
}
return true;
}
_filter() {
this._first = 0;
if(this.lazy) {
this.onLazyLoad.emit(this.createLazyLoadMetadata());
}
else {
if(!this.value || !this.columns) {
return;
}
this.filteredValue = [];
for(let i = 0; i < this.value.length; i++) {
let localMatch = true;
let globalMatch = false;
for(let j = 0; j < this.columns.length; j++) {
let col = this.columns[j],
filterMeta = this.filters[col.filterField||col.field];
//local
if(filterMeta) {
let filterValue = filterMeta.value,
filterField = col.filterField||col.field,
filterMatchMode = filterMeta.matchMode||'startsWith',
dataFieldValue = this.resolveFieldData(this.value[i], filterField);
let filterConstraint = this.filterConstraints[filterMatchMode];
if(!filterConstraint(dataFieldValue, filterValue)) {
localMatch = false;
}
if(!localMatch) {
break;
}
}
//global
if(!col.excludeGlobalFilter && this.globalFilter && !globalMatch) {
globalMatch = this.filterConstraints['contains'](this.resolveFieldData(this.value[i], col.filterField||col.field), this.globalFilter.value);
}
}
let matches = localMatch;
if(this.globalFilter) {
matches = localMatch&&globalMatch;
}
if(matches) {
this.filteredValue.push(this.value[i]);
}
}
if(this.filteredValue.length === this.value.length) {
this.filteredValue = null;
}
if(this.paginator) {
this.totalRecords = this.filteredValue ? this.filteredValue.length: this.value ? this.value.length: 0;
}
this.updateDataToRender(this.filteredValue||this.value);
}
this.onFilter.emit({
filters: this.filters,
filteredValue: this.filteredValue||this.value
});
}
hasFilter() {
let empty = true;
for(let prop in this.filters) {
if(this.filters.hasOwnProperty(prop)) {
empty = false;
break;
}
}
return !empty || (this.globalFilter && this.globalFilter.value && this.globalFilter.value.trim().length);
}
onFilterInputClick(event) {
event.stopPropagation();
}
filterConstraints = {
startsWith(value, filter): boolean {
if(filter === undefined || filter === null || filter.trim() === '') {
return true;
}
if(value === undefined || value === null) {
return false;
}
let filterValue = filter.toLowerCase();
return value.toString().toLowerCase().slice(0, filterValue.length) === filterValue;
},
contains(value, filter): boolean {
if(filter === undefined || filter === null || (typeof filter === 'string' && filter.trim() === '')) {
return true;
}
if(value === undefined || value === null) {
return false;
}
return value.toString().toLowerCase().indexOf(filter.toLowerCase()) !== -1;
},
endsWith(value, filter): boolean {
if(filter === undefined || filter === null || filter.trim() === '') {
return true;
}
if(value === undefined || value === null) {
return false;
}
let filterValue = filter.toString().toLowerCase();
return value.toString().toLowerCase().indexOf(filterValue, value.toString().length - filterValue.length) !== -1;
},
equals(value, filter): boolean {
if(filter === undefined || filter === null || (typeof filter === 'string' && filter.trim() === '')) {
return true;
}
if(value === undefined || value === null) {
return false;
}
return value.toString().toLowerCase() == filter.toString().toLowerCase();
},
notEquals(value, filter): boolean {
if(filter === undefined || filter === null || (typeof filter === 'string' && filter.trim() === '')) {
return false;
}
if(value === undefined || value === null) {
return true;
}
return value.toString().toLowerCase() != filter.toString().toLowerCase();
},
in(value, filter: any[]): boolean {
if(filter === undefined || filter === null || filter.length === 0) {
return true;
}
if(value === undefined || value === null) {
return false;
}
for(let i = 0; i < filter.length; i++) {
if(filter[i] === value)
return true;
}
return false;
}
}
switchCellToEditMode(cell: any, column: Column, rowData: any) {
if(!this.selectionMode && this.editable && column.editable) {
this.editorClick = true;
this.bindDocumentEditListener();
if(cell != this.editingCell) {
if(this.editingCell && this.domHandler.find(this.editingCell, '.ng-invalid.ng-dirty').length == 0) {
this.domHandler.removeClass(this.editingCell, 'ui-cell-editing');
}
this.editingCell = cell;
this.onEditInit.emit({column: column, data: rowData});
this.domHandler.addClass(cell, 'ui-cell-editing');
let focusable = this.domHandler.findSingle(cell, '.ui-cell-editor input, .ui-cell-editor textarea');
if(focusable) {
setTimeout(() => this.domHandler.invokeElementMethod(focusable, 'focus'), 50);
}
}
}
}
switchCellToViewMode(element: any) {
this.editingCell = null;
let cell = this.findCell(element);
this.domHandler.removeClass(cell, 'ui-cell-editing');
this.unbindDocumentEditListener();
}
closeCell() {
if(this.editingCell) {
this.domHandler.removeClass(this.editingCell, 'ui-cell-editing');
this.editingCell = null;
this.unbindDocumentEditListener();
}
}
bindDocumentEditListener() {
if(!this.documentEditListener) {
this.documentEditListener = this.renderer.listen('document', 'click', (event) => {
if(!this.editorClick) {
this.closeCell();
}
this.editorClick = false;
});
}
}
unbindDocumentEditListener() {
if(this.documentEditListener) {
this.documentEditListener();
this.documentEditListener = null;
}
}
onCellEditorKeydown(event, column: Column, rowData: any, rowIndex: number) {
if(this.editable) {
//enter
if(event.keyCode == 13) {
if(this.domHandler.find(this.editingCell, '.ng-invalid.ng-dirty').length == 0) {
this.switchCellToViewMode(event.target);
event.preventDefault();
}
}
//escape
else if(event.keyCode == 27) {
this.switchCellToViewMode(event.target);
event.preventDefault();
}
//tab
else if(event.keyCode == 9) {
if(event.shiftKey)
this.moveToPreviousCell(event);
else
this.moveToNextCell(event);
}
}
}
onCellEditorInput(event, column: Column, rowData: any, rowIndex: number) {
if(this.editable) {
this.onEdit.emit({originalEvent: event, column: column, data: rowData, index: rowIndex});
}
}
onCellEditorChange(event, column: Column, rowData: any, rowIndex: number) {
if(this.editable) {
this.editChanged = true;
this.onEditComplete.emit({column: column, data: rowData, index: rowIndex});
}
}
onCellEditorBlur(event, column: Column, rowData: any, rowIndex: number) {
if(this.editable) {
if(this.editChanged)
this.editChanged = false;
else
this.onEditCancel.emit({column: column, data: rowData, index: rowIndex});
}
}
moveToPreviousCell(event: KeyboardEvent) {
let currentCell = this.findCell(event.target);
let row = currentCell.parentElement;
let targetCell = this.findPreviousEditableColumn(currentCell);
if(targetCell) {
this.domHandler.invokeElementMethod(targetCell, 'click');
event.preventDefault();
}
}
moveToNextCell(event: KeyboardEvent) {
let currentCell = this.findCell(event.target);
let row = currentCell.parentElement;
let targetCell = this.findNextEditableColumn(currentCell);
if(targetCell) {
this.domHandler.invokeElementMethod(targetCell, 'click');
event.preventDefault();
}
}
findPreviousEditableColumn(cell: Element) {
let prevCell = cell.previousElementSibling;
if(!prevCell) {
let previousRow = cell.parentElement.previousElementSibling;
if(previousRow) {
prevCell = previousRow.lastElementChild;
}
}
if(prevCell) {
if(this.domHandler.hasClass(prevCell, 'ui-editable-column'))
return prevCell;
else
return this.findPreviousEditableColumn(prevCell);
}
else {
return null;
}
}
findNextEditableColumn(cell: Element) {
let nextCell = cell.nextElementSibling;
if(!nextCell) {
let nextRow = cell.parentElement.nextElementSibling;
if(nextRow) {
nextCell = nextRow.firstElementChild;
}
}
if(nextCell) {
if(this.domHandler.hasClass(nextCell, 'ui-editable-column'))
return nextCell;
else
return this.findNextEditableColumn(nextCell);
}
else {
return null;
}
}
onCustomEditorFocusPrev(event: KeyboardEvent) {
this.moveToPreviousCell(event);
}
onCustomEditorFocusNext(event: KeyboardEvent) {
this.moveToNextCell(event);
}
findCell(element) {
if(element) {
let cell = element;
while(cell && cell.tagName != 'TD') {
cell = cell.parentElement;
}
return cell;
}
else {
return null;
}
}
initResizableColumns() {
this.tbody = this.domHandler.findSingle(this.el.nativeElement, 'tbody.ui-datatable-data');
this.resizerHelper = this.domHandler.findSingle(this.el.nativeElement, 'div.ui-column-resizer-helper');
this.fixColumnWidths();
}
onDocumentMouseMove(event) {
if(this.columnResizing) {
this.onColumnResize(event);
}
}
onDocumentMouseUp(event) {
if(this.columnResizing) {
this.columnResizing = false;
this.onColumnResizeEnd(event);
}
}
bindColumnResizeEvents() {
this.zone.runOutsideAngular(() => {
window.document.addEventListener('mousemove', this.onDocumentMouseMove.bind(this));
});
this.documentColumnResizeEndListener = this.renderer.listen('document', 'mouseup', (event) => {
if(this.columnResizing) {
this.columnResizing = false;
this.onColumnResizeEnd(event);
}
});
}
unbindColumnResizeEvents() {
window.document.removeEventListener('mousemove', this.onDocumentMouseMove);
if(this.documentColumnResizeEndListener) {
this.documentColumnResizeEndListener();
this.documentColumnResizeEndListener = null;
}
}
initColumnResize(event) {
this.bindColumnResizeEvents();
let container = this.el.nativeElement.children[0];
let containerLeft = this.domHandler.getOffset(container).left;
this.resizeColumn = event.target.parentElement;
this.columnResizing = true;
this.lastResizerHelperX = (event.pageX - containerLeft + container.scrollLeft);
}
onColumnResize(event) {
let container = this.el.nativeElement.children[0];
let containerLeft = this.domHandler.getOffset(container).left;
this.domHandler.addClass(container, 'ui-unselectable-text');
this.resizerHelper.style.height = container.offsetHeight + 'px';
this.resizerHelper.style.top = 0 + 'px';
this.resizerHelper.style.left = (event.pageX - containerLeft + container.scrollLeft) + 'px';
this.resizerHelper.style.display = 'block';
}
onColumnResizeEnd(event) {
let delta = this.resizerHelper.offsetLeft - this.lastResizerHelperX;
let columnWidth = this.resizeColumn.offsetWidth;
let newColumnWidth = columnWidth + delta;
let minWidth = this.resizeColumn.style.minWidth||15;
if(columnWidth + delta > parseInt(minWidth)) {
if(this.columnResizeMode === 'fit') {
let nextColumn = this.resizeColumn.nextElementSibling;
while (this.domHandler.hasClass(nextColumn, 'ui-helper-hidden')) {
nextColumn = nextColumn.nextElementSibling;
}
if(nextColumn) {
let nextColumnWidth = nextColumn.offsetWidth - delta;
let nextColumnMinWidth = nextColumn.style.minWidth || 15;
if (newColumnWidth > 15 && nextColumnWidth > parseInt(nextColumnMinWidth)) {
this.resizeColumn.style.width = newColumnWidth + 'px';
if (nextColumn) {
nextColumn.style.width = nextColumnWidth + 'px';
}
if (this.scrollable) {
let colGroup = this.domHandler.findSingle(this.el.nativeElement, 'colgroup.ui-datatable-scrollable-colgroup');
let resizeColumnIndex = this.domHandler.index(this.resizeColumn);
colGroup.children[resizeColumnIndex].style.width = newColumnWidth + 'px';
if (nextColumn) {
colGroup.children[resizeColumnIndex + 1].style.width = nextColumnWidth + 'px';
}
}
}
}
}
else if(this.columnResizeMode === 'expand') {
this.tbody.parentElement.style.width = this.tbody.parentElement.offsetWidth + delta + 'px';
this.resizeColumn.style.width = newColumnWidth + 'px';
let containerWidth = this.tbody.parentElement.style.width;
if(this.scrollable) {
this.domHandler.findSingle(this.el.nativeElement, '.ui-datatable-scrollable-header-box').children[0].style.width = containerWidth;
let colGroup = this.domHandler.findSingle(this.el.nativeElement, 'colgroup.ui-datatable-scrollable-colgroup');
let resizeColumnIndex = this.domHandler.index(this.resizeColumn);
colGroup.children[resizeColumnIndex].style.width = newColumnWidth + 'px';
}
else {
this.el.nativeElement.children[0].style.width = containerWidth;
}
}
this.onColResize.emit({
element: this.resizeColumn,
delta: delta
});
}
this.resizerHelper.style.display = 'none';
this.resizeColumn = null;
this.domHandler.removeClass(this.el.nativeElement.children[0], 'ui-unselectable-text');
this.unbindColumnResizeEvents();
}
fixColumnWidths() {
let columns = this.domHandler.find(this.el.nativeElement, 'th.ui-resizable-column');
let bodyCols;
for(let i = 0; i < columns.length; i++) {
columns[i].style.width = columns[i].offsetWidth + 'px';
}
if(this.scrollable) {
let colGroup = this.domHandler.findSingle(this.el.nativeElement, 'colgroup.ui-datatable-scrollable-colgroup');
bodyCols = colGroup.children;
if(bodyCols) {
for (let i = 0; i < bodyCols.length; i++) {
bodyCols[i].style.width = columns[i].offsetWidth + 'px';
}
}
}
}
onColumnDragStart(event) {
if (this.columnResizing) {
event.preventDefault();
return;
}
this.draggedColumn = this.findParentHeader(event.target);
event.dataTransfer.setData('text', 'b'); // Firefox requires this to make dragging possible
this.zone.runOutsideAngular(() => {
window.document.addEventListener('dragover', this.onColumnDragover.bind(this));
});
}
onColumnDragover(event) {
let dropHeader = this.findParentHeader(event.target);
if(this.reorderableColumns && this.draggedColumn && dropHeader) {
event.preventDefault();
let container = this.el.nativeElement.children[0];
let containerOffset = this.domHandler.getOffset(container);
let dropHeaderOffset = this.domHandler.getOffset(dropHeader);
if(this.draggedColumn != dropHeader) {
let targetLeft = dropHeaderOffset.left - containerOffset.left;
let targetTop = containerOffset.top - dropHeaderOffset.top;
let columnCenter = dropHeaderOffset.left + dropHeader.offsetWidth / 2;
this.reorderIndicatorUp.style.top = dropHeaderOffset.top - containerOffset.top - (this.iconHeight - 1) + 'px';
this.reorderIndicatorDown.style.top = dropHeaderOffset.top - containerOffset.top + dropHeader.offsetHeight + 'px';
if(event.pageX > columnCenter) {
this.reorderIndicatorUp.style.left = (targetLeft + dropHeader.offsetWidth - Math.ceil(this.iconWidth / 2)) + 'px';
this.reorderIndicatorDown.style.left = (targetLeft + dropHeader.offsetWidth - Math.ceil(this.iconWidth / 2))+ 'px';
this.dropPosition = 1;
}
else {
this.reorderIndicatorUp.style.left = (targetLeft - Math.ceil(this.iconWidth / 2)) + 'px';
this.reorderIndicatorDown.style.left = (targetLeft - Math.ceil(this.iconWidth / 2))+ 'px';
this.dropPosition = -1;
}
this.reorderIndicatorUp.style.display = 'block';
this.reorderIndicatorDown.style.display = 'block';
}
else {
event.dataTransfer.dropEffect = 'none';
}
}
}
onColumnDragleave(event) {
if(this.reorderableColumns && this.draggedColumn) {
event.preventDefault();
this.reorderIndicatorUp.style.display = 'none';
this.reorderIndicatorDown.style.display = 'none';
window.document.removeEventListener('dragover', this.onColumnDragover);
}
}
onColumnDrop(event) {
event.preventDefault();
if(this.draggedColumn) {
let dragIndex = this.domHandler.index(this.draggedColumn);
let dropIndex = this.domHandler.index(this.findParentHeader(event.target));
let allowDrop = (dragIndex != dropIndex);
if(allowDrop && ((dropIndex - dragIndex == 1 && this.dropPosition === -1) || (dragIndex - dropIndex == 1 && this.dropPosition === 1))) {
allowDrop = false;
}
if(allowDrop) {
this.objectUtils.reorderArray(this.columns, dragIndex, dropIndex);
if(this.scrollable) {
this.initScrollableColumns();
}
this.onColReorder.emit({
dragIndex: dragIndex,
dropIndex: dropIndex,
columns: this.columns
});
}
this.reorderIndicatorUp.style.display = 'none';
this.reorderIndicatorDown.style.display = 'none';
this.draggedColumn.draggable = false;
this.draggedColumn = null;
this.dropPosition = null;
}
}
initColumnReordering() {
this.reorderIndicatorUp = this.domHandler.findSingle(this.el.nativeElement.children[0], 'span.ui-datatable-reorder-indicator-up');
this.reorderIndicatorDown = this.domHandler.findSingle(this.el.nativeElement.children[0], 'span.ui-datatable-reorder-indicator-down');
this.iconWidth = this.domHandler.getHiddenElementOuterWidth(this.reorderIndicatorUp);
this.iconHeight = this.domHandler.getHiddenElementOuterHeight(this.reorderIndicatorUp);
}
findParentHeader(element) {
if(element.nodeName == 'TH') {
return element;
}
else {
let parent = element.parentElement;
while(parent.nodeName != 'TH') {
parent = parent.parentElement;
if(!parent) break;
}
return parent;
}
}
hasFooter() {
if(this.footerColumnGroups && this.footerColumnGroups.first) {
return true;
}
else {
if(this.columns) {
for(let i = 0; i < this.columns.length; i++) {
if(this.columns[i].footer || this.columns[i].footerTemplate) {
return true;
}
}
}
}
return false;
}
isEmpty() {
return !this.dataToRender||(this.dataToRender.length == 0);
}
createLazyLoadMetadata(): LazyLoadEvent {
return {
first: this.first,
rows: this.virtualScroll ? this.rows * 2 : this.rows,
sortField: this.sortField,
sortOrder: this.sortOrder,
filters: this.filters,
globalFilter: this.globalFilter ? this.globalFilter.value : null,
multiSortMeta: this.multiSortMeta
};
}
toggleRow(row: any, event?: Event) {
if(!this.expandedRows) {
this.expandedRows = [];
}
let expandedRowIndex = this.findExpandedRowIndex(row);
if(expandedRowIndex != -1) {
this.expandedRows.splice(expandedRowIndex, 1);
this.onRowCollapse.emit({
originalEvent: event,
data: row
});
}
else {
if(this.rowExpandMode === 'single') {
this.expandedRows = [];
}
this.expandedRows.push(row);
this.onRowExpand.emit({
originalEvent: event,
data: row
});
}
if(event) {
event.preventDefault();
}
}
findExpandedRowIndex(row: any): number {
let index = -1;
if(this.expandedRows) {
for(let i = 0; i < this.expandedRows.length; i++) {
if(this.expandedRows[i] == row) {
index = i;
break;
}
}
}
return index;
}
isRowExpanded(row: any): boolean {
return this.findExpandedRowIndex(row) != -1;
}
findExpandedRowGroupIndex(row: any): number {
let index = -1;
if(this.expandedRowsGroups && this.expandedRowsGroups.length) {
for(let i = 0; i < this.expandedRowsGroups.length; i++) {
let group = this.expandedRowsGroups[i];
let rowGroupField = this.resolveFieldData(row, this.groupField);
if(rowGroupField === group) {
index = i;
break;
}
}
}
return index;
}
isRowGroupExpanded(row: any): boolean {
return this.findExpandedRowGroupIndex(row) != -1;
}
toggleRowGroup(event: Event, row: any): void {
if(!this.expandedRowsGroups) {
this.expandedRowsGroups = [];
}
this.rowGroupToggleClick = true;
let index = this.findExpandedRowGroupIndex(row);
let rowGroupField = this.resolveFieldData(row, this.groupField);
if(index >= 0) {
this.expandedRowsGroups.splice(index, 1);
this.onRowGroupCollapse.emit({
originalEvent: event,
group: rowGroupField
});
}
else {
if(this.rowGroupExpandMode === 'single') {
this.expandedRowsGroups = [];
}
this.expandedRowsGroups.push(rowGroupField);
this.onRowGroupExpand.emit({
originalEvent: event,
group: rowGroupField
});
}
event.preventDefault();
}
public reset() {
this._sortField = null;
this._sortOrder = 1;
this.filteredValue = null;
this.filters = {};
this._first = 0;
this.firstChange.emit(this._first);
this.updateTotalRecords();
if(this.lazy)
this.onLazyLoad.emit(this.createLazyLoadMetadata());
else
this.updateDataToRender(this.value);
}
public exportCSV(options?:any) {
let data = this.filteredValue||this.value;
let csv = '\ufeff';
if(options && options.selectionOnly) {
data = this.selection||[];
}
//headers
for(let i = 0; i < this.columns.length; i++) {
let column = this.columns[i];
if(column.exportable && column.field) {
csv += '"' + (column.header || column.field) + '"';
if(i < (this.columns.length - 1)) {
csv += this.csvSeparator;
}
}
}
//body
data.forEach((record, i) => {
csv += '\n';
for(let i = 0; i < this.columns.length; i++) {
let column = this.columns[i];
if(column.exportable && column.field) {
let cellData = this.resolveFieldData(record, column.field);
if(cellData != null)
cellData = String(cellData).replace(/"/g, '""');
else
cellData = '';
csv += '"' + cellData + '"';
if(i < (this.columns.length - 1)) {
csv += this.csvSeparator;
}
}
}
});
let blob = new Blob([csv],{
type: 'text/csv;charset=utf-8;'
});
if(window.navigator.msSaveOrOpenBlob) {
navigator.msSaveOrOpenBlob(blob, this.exportFilename + '.csv');
}
else {
let link = document.createElement("a");
link.style.display = 'none';
document.body.appendChild(link);
if(link.download !== undefined) {
link.setAttribute('href', URL.createObjectURL(blob));
link.setAttribute('download', this.exportFilename + '.csv');
link.click();
}
else {
csv = 'data:text/csv;charset=utf-8,' + csv;
window.open(encodeURI(csv));
}
document.body.removeChild(link);
}
}
getBlockableElement(): HTMLElement {
return this.el.nativeElement.children[0];
}
getRowStyleClass(rowData: any, rowIndex: number) {
let styleClass = 'ui-widget-content';
if(this.rowStyleClass) {
let rowClass = this.rowStyleClass.call(this, rowData, rowIndex);
if(rowClass) {
styleClass += ' ' + rowClass;
}
}
else if (this.rowStyleMap && this.dataKey) {
let rowClass = this.rowStyleMap[rowData[this.dataKey]];
if (rowClass) {
styleClass += ' ' + rowClass;
}
}
return styleClass;
}
visibleColumns() {
return this.columns ? this.columns.filter(c => !c.hidden): [];
}
get containerWidth() {
if(this.scrollable) {
if(this.scrollWidth) {
return this.scrollWidth;
}
else if(this.frozenWidth && this.unfrozenWidth) {
return parseFloat(this.frozenWidth) + parseFloat(this.unfrozenWidth) + 'px';
}
}
else {
return this.style ? this.style.width : null;
}
}
hasFrozenColumns() {
return this.frozenColumns && this.frozenColumns.length > 0;
}
ngOnDestroy() {
//remove event listener
if(this.globalFilterFunction) {
this.globalFilterFunction();
}
if(this.resizableColumns) {
this.unbindColumnResizeEvents();
}
this.unbindDocumentEditListener();
if(this.columnsSubscription) {
this.columnsSubscription.unsubscribe();
}
if(this.virtualScrollCallback) {
this.virtualScrollCallback = null;
}
}
}
@NgModule({
imports: [CommonModule,SharedModule,PaginatorModule,FormsModule],
exports: [DataTable,SharedModule],
declarations: [DataTable,DTRadioButton,DTCheckbox,ColumnHeaders,ColumnFooters,TableBody,ScrollableView]
})
export class DataTableModule { }