import { InputGroupComponent } from '@farris/ui-input-group';
import { ChangeDetectorRef } from '@angular/core';
/*
* @Author: 疯狂秀才(Lucas Huang)
* @Date: 2019-07-10 11:44:49
* @LastEditors: 疯狂秀才(Lucas Huang)
* @LastEditTime: 2019-11-05 11:44:18
* @QQ: 1055818239
* @Version: v0.0.1
*/
import {
Component, OnInit, ViewChild, ElementRef, OnChanges, Input, SimpleChanges, ContentChild, TemplateRef,
QueryList, AfterContentInit, AfterViewInit, ContentChildren, Output, EventEmitter, ViewEncapsulation, OnDestroy, Inject, NgZone
} from '@angular/core';
import { Subscription, Observable, of } from 'rxjs';
import PerfectScrollbar from 'perfect-scrollbar';
import { PerfectScrollbarComponent } from '@farris/ui-perfect-scrollbar';
import { PaginationInstance, PaginationControlsComponent } from '@farris/ui-pagination';
import { IdService } from '@farris/ui-common';
import { DataColumn } from '@farris/ui-common/column';
import { deepCopy } from './datatable-column';
import { ColumnDirective } from './datatable-column.component';
import { DataTableService } from './datatable.service';
import { DataTableHeaderComponent } from './table/datatable-header.component';
@Component({
selector: 'farrisui-datatable,farris-datatable',
templateUrl: './datatable.component.html',
styleUrls: ['./datatable.scss'],
encapsulation: ViewEncapsulation.None,
providers: [
DataTableService
]
})
export class DataTableComponent implements OnInit, OnChanges, OnDestroy, AfterContentInit, AfterViewInit {
searchButtonText = '';
@Input() keydownEnterEdit = false;
@Input() id: string;
@Input() size: string;
@Input() allColumnsTitle = '所有列';
// table 尺寸
@Input() width;
// 组件级高度包括过滤条高度
@Input() height;
tableHeight: number; // 数据表高度
// 是否填充
@Input() fill = false;
// 默认分页
@Input() pagination = true;
@Input() pagerOnServer = true;
@Input() pageSize = 10;
@Input() pageIndex = 1;
@Input() showPageInfo = true;
@Input() showPageNumber = true;
@Input() showPageList = true;
@Input() pagerViewMode = 'default';
/** 显示表头 */
@Input() showHeader = true;
private _pageList = [10, 20, 30, 50, 100];
get pageList() {
return this._pageList;
}
@Input() set pageList(val) {
this._pageList = val;
if (this.pager) {
this.pager.setPageList(val);
}
}
@Input() total = 0;
// 列 数据
@Input() columns: DataColumn[];
@Input() searchFields: { label: string, value: string }[];
// 可筛选
@Input() showFilterBar = false;
// table 数据
_data = [];
@Input()
get data() {
return this._data;
}
set data(data: Array) {
this._data = data;
}
// 深拷贝data 数据
copyData: any;
@Input() remote = 'client';
/** 启用远端排序 */
@Input() remoteSort = true;
/** 排序字段 */
@Input() sortName: string;
/** 排序方式 asc | desc */
@Input() sortOrder: string;
/** 允许多列排序 */
@Input() multiSort = false;
@Input() beforeSortColumn: (field: string, order: string) => Observable;
// 多选 单选
@Input() singleSelect = true;
@Input() idField = 'id';
// 显示鼠标悬停高亮
@Input() hover = true;
// 斑马线
@Input() striped: boolean;
// 边框
@Input() bordered: boolean;
// 支持添加行 单元格 类样式
@Input() rowClassName: (row: any, index: number) => string;
@Input() cellClassName: (value: any, col: any) => string;
@Input() rowStyler: (args: any) => any;
@Input() cellStyler: (val: any) => any;
@Input()
set selections(v) {
this.dtBody.selections = v ? v : {};
}
get selections() {
return this.dtBody.selections;
}
// 滚动条引用
@ViewChild('scorllableBody') scorllableBody: ElementRef;
@ViewChild('tableHeader') tableHeader: ElementRef;
@ViewChild('tablePager') tablePager: ElementRef;
@ViewChild('dtHeader') dtHeader: DataTableHeaderComponent;
@ViewChild('dtBody') dtBody: any;
@ViewChild('dtLeftBody') dtLeftBody: any;
@ViewChild('dtRightBody') dtRightBody: any;
@ViewChild('dtLeftFixed') dtLeftFixed: ElementRef;
@ViewChild('dtRightFixed') dtRightFixed: ElementRef;
@ViewChild('pager') pager: PaginationControlsComponent;
@ViewChild('inputgroup') inputGroup: InputGroupComponent;
// 分页事件
@Output() pageChanged = new EventEmitter();
@Output() pageSizeChanged = new EventEmitter();
@Output() search = new EventEmitter<{ field: string, value: string }>();
@Output() columnSorted = new EventEmitter();
@Output() rowDblClick = new EventEmitter();
@Output() selectedRow = new EventEmitter();
// 兼容
@Output() selectRows = new EventEmitter();
@Output() unSelectRow = new EventEmitter();
@Output() cellClick = new EventEmitter();
@Output() clearSearchValue = new EventEmitter();
@Output() checkAll = new EventEmitter();
@Output() sortChange = new EventEmitter();
@ContentChildren(ColumnDirective) columnsRef: QueryList;
// 表尾
@ContentChild('footer') footer: TemplateRef;
// 表格可拖拽宽度系列
// 拖拽线
@ViewChild('dragLine') dragLine: ElementRef;
// 是否可拖拽 默认可以
@Input() resizableColumns = true;
/** 针对同一条记录,单选时,多次单击后不取消选中。 */
@Input() keepSelect = true;
// 是否有行模板
hasRowTepml = false;
// 用户获取表头+表格内容的高度 宽度 等
datatableContainer: HTMLDivElement;
// 拖拽线初始化位置
dragLineX: number;
// 设置拖拽停止器
moveable = false;
//
currentColumn: any;
// 设置左固定列
hasFixed: boolean;
fixedLeftWidth: string;
// 设置右固定列
fixedRightWidth: string;
searchData = { field: '*', value: '' };
// 事件订阅存储 便于销毁
subscription: Subscription[] = [];
// 固定列时 同一行的tr hover事件
headerTr: any;
leftFixedHeaderTr: any;
rightFixedHeaderTr: any;
// 原数据
public filter = '';
public maxSize = 7;
public directionLinks = true;
public autoHide = false;
public responsive = true;
public paginationOptions: PaginationInstance = {
id: 'Farris-DataTable-Pagination',
itemsPerPage: this.pageSize,
currentPage: this.pageIndex,
pageList: this.pageList,
totalItems: this.total,
remote: this.pagerOnServer
};
public labels: any = {
previousLabel: ' ',
nextLabel: ' ',
screenReaderPaginationLabel: 'Pagination',
screenReaderPageLabel: 'page',
screenReaderCurrentLabel: `You're on page`
};
private _currentRowIndex = -1;
get currentRowIndex(): number {
return this._currentRowIndex;
}
private _currentRow = undefined;
get currentRow() {
return this._currentRow;
}
@ViewChild('perfectScrollbar') perfectScrollbar: PerfectScrollbarComponent;
scorllableBodyHeight: number;
constructor(private dataService: DataTableService, private idService: IdService, private el: ElementRef,
public cd: ChangeDetectorRef, private ngZone: NgZone) {
this.dataService.selectedRow.subscribe((e: any) => {
if (this.singleSelect) {
this._currentRowIndex = e.rowIndex;
this._currentRow = e.rowData;
} else {
if (this.selections) {
this.dtHeader.isCheckAll = Object.keys(this.selections).length === this.data.length;
}
}
});
this.dataService.unSelectedRow.subscribe((e: any) => {
if (this.singleSelect) {
this._currentRow = undefined;
this._currentRowIndex = -1;
} else {
this.dtHeader.isCheckAll = false;
}
});
}
private ps: PerfectScrollbar;
ngOnInit() {
setTimeout(() => {
this.setBodyHeight();
this.ps = this.perfectScrollbar.directiveRef.ps();
});
if (!this.id) {
this.id = `datatable_${this.idService.generate()}`;
}
this.paginationOptions.id = this.paginationOptions.id + this.id;
this.copyData = deepCopy(this.data);
if (!this.beforeSortColumn) {
this.beforeSortColumn = () => of(true);
}
}
private setBodyHeight() {
this.tableHeight = this.height;
if (this.showFilterBar) {
this.tableHeight = this.height - 46;
}
if (this.showHeader) {
this.scorllableBodyHeight = this.tableHeight - this.tableHeader.nativeElement.clientHeight;
}
if (this.pagination) {
this.scorllableBodyHeight = this.scorllableBodyHeight - 50;
}
if (this.cd && !this.cd['destroyed']) {
this.cd.detectChanges();
}
}
onClearSearchValue() {
this.searchData.value = '';
this.cd.detectChanges();
this.clearSearchValue.emit();
}
trackByRows = (index: number, row: any) => {
return row[this.idField];
}
ngOnChanges(changes: SimpleChanges) {
if (changes.height && !changes.height.isFirstChange()) {
this.setBodyHeight();
}
if (changes.total && !changes.total.isFirstChange()) {
this.paginationOptions.totalItems = changes.total.currentValue;
}
if (changes.pageSize) {
this.paginationOptions.itemsPerPage = changes.pageSize.currentValue;
}
if (changes.pageIndex && !changes.pageIndex.isFirstChange()) {
this.paginationOptions.currentPage = changes.pageIndex.currentValue;
}
if (changes.data && !changes.data.isFirstChange()) {
const rows = changes.data.currentValue;
this.updateCheckboxState(rows);
this.paginationOptions = { ...this.paginationOptions };
this.dataService.loadSuccess.next(changes.data.currentValue);
}
}
ngAfterContentInit() {
// 支持列组件写入
if (!this.columns) {
if (this.columnsRef && this.columnsRef.length) {
this.columns = this.columnsRef.map(col => {
return {
width: col.width,
title: col.title,
field: col.field,
align: col.align,
fixed: col.fixed,
className: col.className,
multipleFilter: col.multipleFilter,
filter: col.filter,
media: col.media,
sortable: col.sortable,
edit: col.edit
};
});
}
}
}
ngOnDestroy() {
this.subscription.forEach(sub => {
sub.unsubscribe();
});
this.subscription = [];
this.pager = null;
}
ngAfterViewInit() {
// 获取表格容器 即表格
this.datatableContainer = this.el.nativeElement.querySelector('.farris-datatable');
// this.headerTr = this.tableHeader.nativeElement.querySelectorAll('tr');
}
updateCheckboxState(rows: any[]) {
const updateCheckAllStatus = (f) => {
if (this.showHeader && this.dtHeader) {
this.dtHeader.isCheckAll = f;
}
};
if (rows && rows.length) {
if (this.selections) {
const keys = Object.keys(this.dtBody._selections);
if (keys.length) {
let count = 0;
const ids = rows.map((row: any) => {
return row[this.idField].toString();
});
keys.forEach(id => {
if (ids.indexOf(id) > -1) {
count++;
}
});
updateCheckAllStatus(ids.length === count);
} else {
updateCheckAllStatus(false);
}
} else {
updateCheckAllStatus(false);
}
} else {
updateCheckAllStatus(false);
}
}
/**
* 获取表格容器的位置 距离左边视口和上边视口的距离 如果页面有滚动条 需要加上滚动条滚动的数值
*/
getContainerOffset() {
const rect = this.datatableContainer.getBoundingClientRect();
return {
left: rect.left + document.body.scrollLeft,
top: rect.top + document.body.scrollTop,
right: rect.right,
bottom: rect.bottom,
};
}
/**
* 鼠标按下 开始记录拖拽线的位置 拖拽线到达当前鼠标位置
* @param e 鼠标对象
*/
beginDrag(e) {
this.dragLineX = e.pageX;
event.preventDefault();
}
/**
* 鼠标移动 移动拖拽线位置变动
* @param e 鼠标对象
*/
moveDrag(e) {
// 获取表格的左边距离
const containerLeft = this.getContainerOffset().left;
// 设置拖拽线的高度 即获取表头+表内容+表尾的高度 此表格结构包含了分页 因此要去掉分页的高度
if (this.tablePager) {
this.dragLine.nativeElement.style.height = this.datatableContainer.offsetHeight -
this.tablePager.nativeElement.offsetHeight + 'px';
} else {
// const headerHeight = this.el.nativeElement.querySelector('.farris-table-header');
// const bodyHeight = this.el.nativeElement.querySelector('.ps-content');
this.dragLine.nativeElement.style.height = this.datatableContainer.offsetHeight + 'px';
}
// 设置拖拽线的高度 拖拽线相对于表格relative定位是absolute,因此是0
this.dragLine.nativeElement.style.top = 0 + 'px';
// 鼠标移动时,拖拽线相对于表格的位置
this.dragLine.nativeElement.style.left = (e.pageX - containerLeft) + 'px';
// 鼠标移动 设置拖拽线总是可见
this.dragLine.nativeElement.style.display = 'block';
}
// 重新计算表格宽度
/**
* 鼠标抬起 重新计算单元格宽度
* 鼠标对象
*/
stopDrag(e, column) {
this.resizeColumn(e, column);
}
resizeColumn(e, column) {
if (this.isCheckBox(column, 'dt-checkbox-cell')) {
this.dragLine.nativeElement.style.display = 'none';
return;
}
// 偏移量
const delta = e.pageX - this.dragLineX;
// 拖拽前列宽
const columnWidth = column.offsetWidth;
// 拖拽后列宽
let newColumnWidth = columnWidth + delta;
// 最小宽度
const minWidth = column.style.minWidth || 30;
// 新宽度大于或等于最小宽度时
newColumnWidth = newColumnWidth > minWidth ? newColumnWidth : minWidth;
// 重新设置宽度
let colIndex = -1;
const othersCol = [];
const cols = this.tableHeader.nativeElement.querySelectorAll('th');
// 父节点宽度
const parentWidth = column.parentElement.offsetWidth;
// 判断是不是有checkbox
const firstElement = column.parentElement.firstElementChild;
const checkBoxWidth = this.isCheckBox(firstElement, 'dt-checkbox-cell') ? firstElement.offsetWidth : 0;
// 其他列宽度
let othersWidth = parentWidth - columnWidth - checkBoxWidth;
let newOthersWidth = parentWidth - newColumnWidth - checkBoxWidth;
for (let i = 0; i < cols.length; i++) {
if (cols[i] === column) {
colIndex = i;
} else {
if (this.isCheckBox(cols[i], 'dt-checkbox-cell')) {
continue;
}
const rate = cols[i].offsetWidth / othersWidth;
if (rate > 1) {
console.log(cols[i]);
}
let colWidth = newOthersWidth * rate;
if (newOthersWidth * rate > 15) {
colWidth = newOthersWidth * rate;
} else {
colWidth = 15;
othersWidth = othersWidth - colWidth;
newOthersWidth = newOthersWidth - colWidth;
newColumnWidth = newColumnWidth - 15 + newOthersWidth * rate;
}
othersCol.push({ colIndex: i, colWidth });
}
}
this.resizeColGroup(this.dtHeader.el.nativeElement, colIndex, newColumnWidth, othersCol);
this.resizeColGroup(this.dtBody.el.nativeElement, colIndex, newColumnWidth, othersCol);
// 计算宽度完毕 设置拖拽线隐藏
this.dragLine.nativeElement.style.display = 'none';
}
isCheckBox(elements, cName) {
return !!elements.className.match(new RegExp('(\\s|^)' + cName + '(\\s|$)'));
}
resizeColGroup(table, resizeColumnIndex, newColumnWidth, othersColumn) {
if (table) {
// 此处要视不同的表格结构来确定 本组件中 header和body结构相同
const colGroup = table.childNodes[0].children[0].nodeName === 'COLGROUP' ?
table.childNodes[0].children[0] : null;
if (colGroup) {
const col = colGroup.children[resizeColumnIndex];
col.style.width = newColumnWidth + 'px';
for (let i = 0; i < othersColumn.length; i++) {
const otherCol = colGroup.children[othersColumn[i].colIndex];
if (otherCol && othersColumn[i].colWidth) {
otherCol.style.width = othersColumn[i].colWidth + 'px';
}
}
} else {
throw new Error('Scrollable tables require a colgroup to support resizable columns');
}
}
}
onScrollX(e: any) {
// 横向滚动 非固定表头滚动
const x = e.srcElement.scrollLeft;
this.tableHeader.nativeElement.scrollTo(x, 0);
}
/**
* 滚动条纵向滚动
*/
onScrollY(e: any) {
if (!this.hasFixed) {
return;
}
const y = e.srcElement.scrollTop;
this.dtLeftFixed.nativeElement.style.top = -y + 'px';
this.dtRightFixed.nativeElement.style.top = -y + 'px';
}
onPageChange(page: { pageIndex: number, pageSize: number }) {
if (this.pageIndex !== page.pageIndex) {
this.pageIndex = page.pageIndex;
this.paginationOptions.currentPage = page.pageIndex;
this.pageChanged.emit({ pageInfo: page, search: this.searchData, sortName: this.sortName, sortOrder: this.sortOrder });
}
}
onPageSizeChange(pageSize: number) {
if (this.pageSize !== pageSize && this.total) {
this.paginationOptions.itemsPerPage = pageSize;
this.pageSize = pageSize;
const total = this.total;
let pageLength = Math.floor(total / pageSize);
if (total % pageSize > 0) {
pageLength += 1;
}
if (pageLength && this.pageIndex > pageLength) {
this.pageIndex = pageLength;
this.paginationOptions.currentPage = this.pageIndex;
}
this.pageSizeChanged.emit({
pageInfo: { pageIndex: this.pageIndex, pageSize },
search: this.searchData, sortName: this.sortName, sortOrder: this.sortOrder
});
}
}
onSearch($event) {
if ($event.originalEvent) {
$event.originalEvent.stopPropagation();
}
this.search.emit(this.searchData);
}
onCheckAll(state: boolean) {
this.dataService.selectedAll.next(state);
this.checkAll.emit(state);
}
onSelectedRow(e: any) {
this.selectedRow.emit({ data: e.rowData, index: e.rowIndex });
// 兼容
this.selectRows.emit({ data: e.rowData, index: e.rowIndex });
}
// tslint:disable-next-line:no-shadowed-variable
resize(size: { width: number, height: number }) {
this.width = size.width;
this.height = size.height;
this.setBodyHeight();
}
loadData(e: { pageSize: number, total: number, data: any, pageIndex: number }) {
this.data = e.data;
if (this.pagination) {
const { total = 0, pageSize = 20, pageIndex = 1 } = { ...e };
this.paginationOptions.totalItems = total;
this.paginationOptions.itemsPerPage = pageSize;
this.paginationOptions.currentPage = pageIndex;
this.paginationOptions.pageList = this.pageList;
this.total = total;
this.pageSize = pageSize;
this.pageIndex = pageIndex;
}
this.cd.detectChanges();
this.updateCheckboxState(this.data);
}
/*
*/
onCellClick(e) {
this.cellClick.emit(e);
}
/* 添加行 */
addRows(dataItem) {
this.data = this.data.concat(dataItem);
}
/* 删除行 */
removeRows() {
const SELECTIONS = this.selections;
if (this.singleSelect) {
for (let i = 0; i < this.data.length; i++) {
if (this.data[i] === SELECTIONS) {
this.data.splice(i, 1);
}
}
} else {
for (let i = 0; i < SELECTIONS.length; i++) {
for (let j = 0; j < this.data.length; j++) {
if (SELECTIONS[i][this.idField] === this.data[j][this.idField]) {
this.data.splice(j, 1);
}
}
}
}
}
checkRow(rid: any, emit = false) {
let rowIndex = -1;
const rowData = this.data.find((n, i) => {
if (n[this.idField] == rid) {
rowIndex = i;
return true;
}
});
if (!rowData) {
return;
}
if (!this.selections) {
this.selections = { [rid]: rowData };
} else {
this.selections = {
[rid]: rowData, ...this.selections.reduce((r, n) => {
r[n[this.idField]] = n;
return r;
}, {})
};
}
this.dataService.selectedRow.next({
rowIndex,
rowData
});
if (emit) {
const p = { data: rowData, index: rowIndex };
this.selectedRow.emit(p);
// 兼容
this.selectRows.emit(p);
}
}
unCheckRow(rid: any, emit = false) {
let rowIndex = -1;
const rowData = this.data.find((n, i) => {
if (n[this.idField] == rid) {
rowIndex = i;
return true;
}
});
if (this.selections) {
this.selections = this.selections.filter(n => n[this.idField] != rid).reduce((r, n) => {
r[n[this.idField]] = n;
return r;
}, {});
this.dataService.unSelectedRow.next({
rowIndex,
rowData
});
if (emit) {
this.unSelectRow.emit({ data: rowData, index: rowIndex });
}
}
}
clearSelections() {
}
onKeydownEvent($event) {
if (!$event || !this.singleSelect || !this.data || !this.data.length) {
return;
}
if (!['ArrowDown', 'ArrowUp'].includes($event.code)) {
return;
}
let newIdx;
if ($event.code === 'ArrowDown') {
newIdx = this._currentRowIndex + 1;
if (newIdx >= this.data.length) {
newIdx = 0;
}
} else if ($event.code === 'ArrowUp') {
if (this._currentRowIndex !== -1) {
newIdx = this._currentRowIndex - 1;
}
}
if (newIdx >= 0) {
this.dtBody.selectedRow($event, newIdx, this.data[newIdx]);
}
setTimeout(() => {
// scroll intoView
const trDoms = this.dtBody.el.nativeElement.querySelectorAll('.ui-table-tbody tr');
if (trDoms && trDoms[this._currentRowIndex]) {
trDoms[this._currentRowIndex].scrollIntoView({ block: 'nearest' });
}
});
}
}