vue-fantable
Version:
Vue table component for huge data
1,769 lines (1,624 loc) • 119 kB
JSX
import {
cloneDeep, debounce, getValByUnit,
isFunction,
isNumber,
scrollTo,
isEmptyValue,
isEmptyArray,
isBoolean,
isDefined,
} from '@P/src/utils/index.js'
import {
initGroupColumns,
clsName,
getNotFixedTotalWidthByColumnKey,
recursiveRemoveColumnByKey,
setHeaderContextmenuOptions,
setBodyContextmenuOptions,
createEmptyRowData,
isContextmenuPanelClicked,
getRowKey,
getColKeysByHeaderColumn,
getColumnByColkey,
getLeftmostColKey,
isCellInSelectionRange,
isClearSelectionByBodyCellRightClick,
cellAutofill,
isOperationColumn,
getSelectionRangeData,
getSelectionRangeKeys,
getSelectionRangeIndexes,
setColumnFixed,
cancelColumnFixed
} from './util/index'
import {
onBeforeCopy,
onAfterCopy,
onBeforePaste,
onAfterPaste,
onBeforeCut,
onAfterCut,
onBeforeDelete,
onAfterDelete
} from './util/clipboard'
import { createLocale } from '@P/src/locale/index'
import { KEY_CODES, MOUSE_EVENT_CLICK_TYPE } from '@P/src/utils/constant'
import { getScrollbarWidth } from '@P/src/utils/scroll-bar'
import {
requestAnimationTimeout,
cancelAnimationTimeout
} from '@P/src/utils/request-animation-timeout'
import { isInputKeyCode } from '@P/src/utils/event-key-codes'
import Hooks from '@P/src/utils/hooks-manager'
import { getMouseEventClickType } from '@P/src/utils/mouse-event'
import { GLOBAL_EVENT } from '@P/events/global-events'
import {
COMPS_NAME,
HOOKS_NAME,
COMPS_CUSTOM_ATTRS,
INSTANCE_METHODS,
CELL_SELECTION_DIRECTION,
LOCALE_COMP_NAME,
CONTEXTMENU_TYPES,
CONTEXTMENU_NODE_TYPES,
AUTOFILLING_DIRECTION,
CURRENT_CELL_SELECTION_TYPES,
COLUMN_FIXED_TYPE
} from './util/constant'
import ColGroup from './colgroup/index.jsx'
import TableHeader from './header/index.jsx'
import TableBody from './body/index.jsx'
import TableFooter from './footer/index.jsx'
import EditInput from './editor/index.jsx'
import Selection from './selection/index.jsx'
import clickoutside from '@P/src/directives/clickoutside'
import VueDomResizeObserver from '@P/src/components/resize-observer/index'
import VeContextmenu from '@P/ve-contextmenu/ve-contextmenu.js'
import ColumnResizer from './column-resizer/index.jsx'
// import eventCenter from '@P/events/event-center'
import mitt from 'mitt'
const $t = createLocale(LOCALE_COMP_NAME)
export default {
name: COMPS_NAME.FAN_TABLE,
directives: {
'click-outside': clickoutside,
},
components: {
VueDomResizeObserver, ColumnResizer, ColGroup, TableHeader, TableBody, TableFooter, EditInput, Selection, VeContextmenu
},
provide() {
return {
eventCenter: this.eventCenter
}
},
props: {
tableData: {
required: true,
type: Array,
},
footerData: {
type: Array,
default: function () {
return []
},
},
showHeader: {
type: Boolean,
default: true,
},
columns: {
type: Array,
required: true,
},
// row key field for row expand、row selection
rowKeyFieldName: {
type: String,
default: null,
},
// table scroll width
scrollWidth: {
type: [Number, String],
default: null,
},
// table max height
maxHeight: {
type: [Number, String],
default: null,
},
// fixed header
fixedHeader: {
type: Boolean,
default: true,
},
// fixed footer
fixedFooter: {
type: Boolean,
default: true,
},
// border around
borderAround: {
type: Boolean,
default: true,
},
// border horizontal
borderX: {
type: Boolean,
default: true,
},
// border vertical
borderY: {
type: Boolean,
default: false,
},
// event custom option
eventCustomOption: {
type: Object,
default: function () {
return null
},
},
// cell style option
cellStyleOption: {
type: Object,
default: function () {
return null
},
},
// cell span option
cellSpanOption: {
type: Object,
default: function () {
return null
},
},
// row style option
rowStyleOption: {
type: Object,
default: function () {
return null
},
},
/*
virtual scroll option
{
enable:true,
bufferCount:10, // 缓冲的数据
minRowHeight:40,
scrolling:(startRowIndex,visibleStartIndex,visibleEndIndex,visibleAboveCount,visibleBelowCount)=>{}
}
*/
virtualScrollOption: {
type: Object,
default: null,
},
// sort option
sortOption: {
type: Object,
default: function () {
return null
},
},
// expand row option
expandOption: {
type: Object,
default: function () {
return null
},
},
// checkbox option
checkboxOption: {
type: Object,
default: function () {
return null
},
},
// radio option
radioOption: {
type: Object,
default: function () {
return null
},
},
// cell selection option
cellSelectionOption: {
type: Object,
default: function () {
return null
},
},
// cell autofill option
cellAutofillOption: {
type: [Object, Boolean],
default: function () {
return null
},
},
// edit option
editOption: {
type: Object,
default: function () {
return null
},
},
// column hidden option
columnHiddenOption: {
type: Object,
default: function () {
return null
},
},
// contextmenu header option
contextmenuHeaderOption: {
type: Object,
default: function () {
return null
},
},
// contextmenu body option
contextmenuBodyOption: {
type: Object,
default: function () {
return null
},
},
// clipboard option
clipboardOption: {
type: Object,
default: function () {
return null
},
},
// column width resize option
columnWidthResizeOption: {
type: Object,
default: function () {
return null
},
},
},
data() {
return {
// Hooks instance
eventCenter: mitt(),
hooks: {},
// is parent rendered
parentRendered: false,
// table viewport width except scroll bar width
tableViewportWidth: 0,
/*
列配置变化次数
依赖columns 配置渲染,都需要重新计算:粘性布局时,重新触发 on-dom-resize-change 事件
*/
columnsOptionResetTime: 0,
tableRootRef: 'tableRootRef',
tableContainerWrapperRef: 'tableContainerWrapperRef',
tableContainerRef: 'tableContainerRef',
tableRef: 'tableRef',
tableBodyRef: 'tableBodyRef',
tableContentWrapperRef: 'tableContentWrapperRef',
virtualPhantomRef: 'virtualPhantomRef',
editInputRef: 'editInputRef',
cellSelectionRef: 'cellSelectionRef',
contextmenuRef: 'contextmenuRef',
cloneColumns: [],
// is group header
isGroupHeader: false,
/*
header rows created by groupColumns
*/
headerRows: [
/* {
rowHeight:40
} */
],
/*
footer rows created by footerData
*/
footerRows: [
/* {
rowHeight:40
} */
],
// colGroups
colgroups: [],
// groupColumns
groupColumns: [],
/*
存储当前隐藏列信息
hidden columns
*/
hiddenColumns: [],
/*
// virtual scroll positions(非响应式)
virtualScrollPositions = [
{
rowKey: "", // 当前行数据 rowKey
top: 0, // 距离上一个项的高度
bottom: 100, // 距离下一个项的高度
height: 100 // 自身高度
}
],
*/
// virtual scroll visible data
virtualScrollVisibleData: [],
// virtual scroll visible indexs
virtualScrollVisibleIndexs: {
start: -1,
end: -1,
},
// default virtual scroll buffer scale
defaultVirtualScrollBufferScale: 1,
// default virtual scroll min row height
defaultVirtualScrollMinRowHeight: 40,
// default placeholder per scrolling row count
defaultPlaceholderPerScrollingRowCount: 8,
// 起始索引
virtualScrollStartIndex: 0,
// preview virtual scroll start index
previewVirtualScrollStartIndex: 0,
// 结束索引
virtualScrollEndIndex: 0,
// is scrolling
showVirtualScrollingPlaceholder: false,
// disable pointer events timeout id
disablePointerEventsTimeoutId: null,
// is scrolling left
isLeftScrolling: false,
// is scrolling right
isRightScrolling: false,
// is scrolling vertically
isVerticalScrolling: false,
// has horizontal scroll bar
hasXScrollBar: false,
// has vertical scroll bar
hasYScrollBar: false,
// scroll bar width
scrollBarWidth: 0,
// preview table container scrollLeft (处理左列或右列固定效果)
previewTableContainerScrollLeft: null,
// header cell selection colKeys
headerIndicatorColKeys: {
startColKey: '',
startColKeyIndex: -1,
endColKey: '',
endColKeyIndex: -1,
},
// body indicator rowKeys
bodyIndicatorRowKeys: {
startRowKey: '',
startRowKeyIndex: -1,
endRowKey: '',
endRowKeyIndex: -1,
},
// cell selection data
cellSelectionData: {
currentCell: {
rowKey: '',
colKey: '',
rowIndex: -1,
},
normalEndCell: {
rowKey: '',
colKey: '',
rowIndex: -1,
},
autoFillEndCell: {
rowKey: '',
colKey: '',
},
},
// cell selection range data
cellSelectionRangeData: {
leftColKey: '',
rightColKey: '',
topRowKey: '',
bottomRowKey: '',
},
// is header cell mousedown
isHeaderCellMousedown: false,
// is body cell mousedown
isBodyCellMousedown: false,
// is body operation column mousedown
isBodyOperationColumnMousedown: false,
// is cell selection corner mousedown
isAutofillStarting: false,
// autofilling direction
autofillingDirection: null,
// current cell selection type
currentCellSelectionType: '',
/*
table offest height(开启虚拟滚动时使用)
1、当 :max-height="500" 时使用 max-height
2、当 max-height="calc(100vh - 210px)" 或者 max-height="80%" 时使用 tableOffestHeight
*/
tableOffestHeight: 0,
// table height
tableHeight: 0,
// highlight row key
highlightRowKey: '',
/*
editing cell
*/
editingCell: {
rowKey: '',
colKey: '',
row: null,
column: null,
},
// 编辑单元格每次开始编辑前的初始值
editorInputStartValue: '',
/*
是否允许按下方向键时,停止编辑并移动选中单元格。当双击可编辑单元格或者点击输入文本框时设置为false值
像excel一样:如果直接在可编辑单元格上输入内容后,按下上、下、左、右按键可以直接选中其他单元格,并停止当前单元格编辑状态
like Excel:If you directly enter content in an editable cell, press the up, down, left and right buttons to directly select other cells and stop editing the current cell
*/
enableStopEditing: true,
// contextmenu event target
contextmenuEventTarget: '',
// contextmenu options
contextmenuOptions: [],
// column resize cursor
isColumnResizerHover: false,
// is column resizing
isColumnResizing: false,
}
},
computed: {
// actual render table data
actualRenderTableData() {
return this.isVirtualScroll
? this.virtualScrollVisibleData
: this.tableData
},
// return row keys
allRowKeys() {
let result = []
const { tableData, rowKeyFieldName } = this
if (rowKeyFieldName) {
result = tableData.map((x) => {
return x[rowKeyFieldName]
})
}
return result
},
// virtual scroll buffer count
virtualScrollBufferCount() {
let result = 0
const {
virtualScrollOption,
defaultVirtualScrollBufferScale,
virtualScrollVisibleCount,
} = this
if (virtualScrollOption) {
const { bufferScale } = virtualScrollOption
const realBufferScale =
isNumber(bufferScale) && bufferScale > 0
? bufferScale
: defaultVirtualScrollBufferScale
result = realBufferScale * virtualScrollVisibleCount
}
return result
},
// virtual scroll visible count
virtualScrollVisibleCount() {
let result = 0
const {
isVirtualScroll,
virtualScrollOption,
defaultVirtualScrollMinRowHeight,
maxHeight,
tableOffestHeight,
} = this
if (isVirtualScroll && maxHeight) {
const minRowHeight = isNumber(virtualScrollOption.minRowHeight)
? virtualScrollOption.minRowHeight
: defaultVirtualScrollMinRowHeight
if (isNumber(maxHeight)) {
result = Math.ceil(maxHeight / minRowHeight)
} else if (tableOffestHeight) {
// 修复当动态高度 当 max-height="calc(100vh - 210px)" 或者 max-height="80%" 时无法计算的问题
result = Math.ceil(tableOffestHeight / minRowHeight)
}
}
return result
},
// table container wrapper style
tableContainerWrapperStyle() {
return {
width: '100%',
}
},
// table container style
tableContainerStyle() {
const maxHeight = getValByUnit(this.maxHeight)
let tableContainerHeight = null
if (this.isVirtualScroll) {
if (maxHeight) {
tableContainerHeight = maxHeight
} else {
console.error(
"maxHeight prop is required when 'virtualScrollOption.enable = true'",
)
}
} else {
/*
fixed:虚拟滚动表格行展开的 fan-table 存在固定头时(sticky 冲突),表格样式错乱的问题
fixed:When there is a fixed header in the fan-table expanded by the row of the virtual rolling table(header sticky conflict),Incorrect table presentation
*/
const { tableHeight, hasXScrollBar } = this
tableContainerHeight = tableHeight
/*
有横向滚动条时,表格高度需要加上滚动条的宽度
When there is a horizontal scroll bar, the table height needs to be added with the width of the scroll bar
*/
if (hasXScrollBar) {
tableContainerHeight += this.getScrollBarWidth()
}
tableContainerHeight = getValByUnit(tableContainerHeight)
}
return {
'max-height': maxHeight,
// if virtual scroll
height: tableContainerHeight,
}
},
// table style
tableStyle() {
return {
width: getValByUnit(this.scrollWidth),
}
},
// table class
tableClass() {
const borderY = clsName('border-y')
return {
[clsName('border-x')]: this.borderX,
[borderY]: this.borderY,
}
},
// table container class
tableContainerClass() {
const {
isVirtualScroll,
isLeftScrolling,
isRightScrolling,
isVerticalScrolling,
isCellEditing,
isAutofillStarting,
enableCellSelection,
} = this
const virtualScroll = clsName('virtual-scroll')
const leftScrolling = clsName('container-left-scrolling')
const rightScrolling = clsName('container-right-scrolling')
const verticalScroll = clsName('container-vertical-scrolling')
const cellEditing = clsName('is-cell-editing')
const autofilling = clsName('autofilling')
const cellSelection = clsName('enable-cell-selection')
const result = {
[clsName('container')]: true,
[virtualScroll]: isVirtualScroll,
[leftScrolling]: isLeftScrolling,
[rightScrolling]: isRightScrolling,
[verticalScroll]: isVerticalScrolling,
[cellEditing]: isCellEditing,
[autofilling]: isAutofillStarting,
// 如果开启单元格选择,则关闭 user-select
[cellSelection]: enableCellSelection,
}
return result
},
// table body class
tableBodyClass() {
let result = null
const { rowStyleOption } = this
let hoverHighlight = true
let clickHighlight = true
let stripe = false
if (rowStyleOption) {
hoverHighlight = rowStyleOption.hoverHighlight
clickHighlight = rowStyleOption.clickHighlight
stripe = rowStyleOption.stripe
}
const clsStripe = clsName('stripe')
const rowHover = clsName('row-hover')
result = {
[clsStripe]: stripe === true, // 默认不开启
[rowHover]: hoverHighlight !== false, // 默认开启
[clsName('row-highlight')]: clickHighlight !== false, // 默认开启
}
return result
},
// is virtual scroll
isVirtualScroll() {
const { virtualScrollOption } = this
return virtualScrollOption && virtualScrollOption.enable
},
// has fixed column
hasFixedColumn() {
return this.colgroups.some(
(x) =>
x.fixed === COLUMN_FIXED_TYPE.LEFT ||
x.fixed === COLUMN_FIXED_TYPE.RIGHT,
)
},
// has left fixed column
hasLeftFixedColumn() {
return this.colgroups.some(
(x) => x.fixed === COLUMN_FIXED_TYPE.LEFT,
)
},
// has right fixed column
hasRightFixedColumn() {
return this.colgroups.some(
(x) => x.fixed === COLUMN_FIXED_TYPE.RIGHT,
)
},
// is editing cell
isCellEditing() {
const { editingCell } = this
return (
!isEmptyValue(editingCell.rowKey) &&
!isEmptyValue(editingCell.colKey)
)
},
// has edit column
hasEditColumn() {
return this.colgroups.some((x) => x.edit)
},
// enable header contextmenu
enableHeaderContextmenu() {
let result = false
const { contextmenuHeaderOption } = this
if (contextmenuHeaderOption) {
const { contextmenus } = contextmenuHeaderOption
if (Array.isArray(contextmenus) && contextmenus.length) {
result = true
}
}
return result
},
// enable body contextmenu
enableBodyContextmenu() {
let result = false
const { contextmenuBodyOption } = this
if (contextmenuBodyOption) {
const { contextmenus } = contextmenuBodyOption
if (Array.isArray(contextmenus) && contextmenus.length) {
result = true
}
}
return result
},
// contextmenu type
contextMenuType() {
if (this.headerIndicatorColKeys.startColKeyIndex > -1) {
return CONTEXTMENU_TYPES.HEADER_CONTEXTMENU
} else {
return CONTEXTMENU_TYPES.BODY_CONTEXTMENU
}
},
/*
enable cell selection
单元格编辑、剪贴板都依赖单元格选择
*/
enableCellSelection() {
let result = true
const { cellSelectionOption, rowKeyFieldName } = this
if (isEmptyValue(rowKeyFieldName)) {
result = false
} else if (
cellSelectionOption &&
isBoolean(cellSelectionOption.enable) &&
cellSelectionOption.enable === false
) {
result = false
}
return result
},
// enable clipboard
enableClipboard() {
return this.rowKeyFieldName
},
// eanble width resize
enableColumnResize() {
let result = false
const { columnWidthResizeOption } = this
if (columnWidthResizeOption) {
const { enable } = columnWidthResizeOption
if (isBoolean(enable)) {
result = enable
}
}
return result
},
// header total height
headerTotalHeight() {
let result = 0
if (this.showHeader) {
result = this.headerRows.reduce((total, currentVal) => {
return currentVal.rowHeight + total
}, 0)
}
return result
},
// footer total height
footerTotalHeight() {
return this.footerRows.reduce((total, currentVal) => {
return currentVal.rowHeight + total
}, 0)
},
},
watch: {
// watch clone table data
tableData: {
handler(newVal, oldVal) {
this.initVirtualScrollPositions()
// 第一次不需要触发,仅数据变更触发
if (oldVal) {
this.initVirtualScroll()
}
},
deep: true,
immediate: true,
},
allRowKeys: {
handler(newVal) {
if (Array.isArray(newVal)) {
const { currentCell } = this.cellSelectionData
// 行被移除,清空单元格选中
if (currentCell.rowIndex > -1) {
if (newVal.indexOf(currentCell.rowKey) === -1) {
this.clearCellSelectionCurrentCell()
}
}
}
},
immediate: false,
},
columns: {
handler(newVal, oldVal) {
this.initColumns()
this.initGroupColumns()
this.initColumnWidthByColumnResize()
// 排除首次
if (newVal !== oldVal && oldVal) {
this.columnsOptionResetTime++
// 需要等待 initColumns 和 initGroupColumns 先执行
this.initScrolling()
}
},
immediate: true,
},
cloneColumns: {
handler() {
this.initGroupColumns()
// 右键(取消)固定列会操作 cloneColumns
this.initColumnWidthByColumnResize()
this.columnsOptionResetTime++
// 需要等待 initColumns 和 initGroupColumns 先执行
this.initScrolling()
},
immediate: false,
},
// group columns change watch
groupColumns: {
handler(val) {
if (!isEmptyArray(val)) {
this.initHeaderRows()
}
},
immediate: true,
},
// footer data
footerData: {
handler(val) {
if (!isEmptyArray(val)) {
this.initFooterRows()
}
},
immediate: true,
},
/*
watch virtualScrollOption enable
允许按需开启虚拟滚动
*/
'virtualScrollOption.enable': {
handler(newVal) {
// enable virtual scroll
if (newVal) {
this.initVirtualScrollPositions()
this.initVirtualScroll()
} else { // disable virtual scroll
// clear table content top value
this.setTableContentTopValue({ top: 0 })
}
},
immediate: false,
},
// is auto fill starting
isAutofillStarting: {
handler(val) {
if (!val) {
this.setCellSelectionByAutofill()
this.clearCellSelectionAutofillEndCell()
}
},
},
// watch current cell
'cellSelectionData.currentCell': {
handler: function () {
this.setCurrentCellSelectionType()
},
deep: true,
immediate: true,
},
// watch normal end cell
'cellSelectionData.normalEndCell': {
handler: function () {
this.setCurrentCellSelectionType()
},
deep: true,
immediate: true,
},
// watch header indicator colKeys
headerIndicatorColKeys: {
handler: function () {
this.setRangeCellSelectionByHeaderIndicator()
},
deep: true,
},
// watch body indicator rowKeys
bodyIndicatorRowKeys: {
handler: function () {
this.setRangeCellSelectionByBodyIndicator()
},
deep: true,
},
},
created() {
// bug fixed #467
this.debouncedBodyCellWidthChange = debounce(
this.bodyCellWidthChange,
0,
)
},
mounted() {
this.parentRendered = true
// set contextmenu event target
this.contextmenuEventTarget = this.$el.querySelector(
`.${clsName('content')}`,
)
// create hook instance
this.hooks = new Hooks()
// receive sort change
this.eventCenter.on(GLOBAL_EVENT.SORT_CHANGE_AFTER, (params) => {
this.updateColgroupsBySortChange(params)
})
// receive row selected change
this.eventCenter.on(GLOBAL_EVENT.CHECKBOX_SELECTED_ALL_CHANGE_TABLE, (params) => {
this.selectedAllChange(params)
})
// receive selected all info
this.eventCenter.on(GLOBAL_EVENT.CHECKBOX_SELECTED_ALL_INFO, (params) => {
this.setSelectedAllInfo(params)
})
// receive multiple header row height change
this.eventCenter.on(GLOBAL_EVENT.HEADER_ROW_HEIGHT_CHANGE,
({ rowIndex, height }) => {
this.headerRowHeightChange({ rowIndex, height })
},
)
// receive virtual scroll row height change
this.eventCenter.on(GLOBAL_EVENT.BODY_ROW_HEIGHT_CHANGE, ({ rowKey, height }) => {
this.bodyRowHeightChange({ rowKey, height })
})
// receive footer row height change
this.eventCenter.on(GLOBAL_EVENT.FOOTER_ROW_HEIGHT_CHANGE,
({ rowIndex, height }) => {
this.footRowHeightChange({ rowIndex, height })
},
)
// receive body cell click
this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_CLICK, (params) => {
this.bodyCellClick(params)
})
// receive body cell mouseover
this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_MOUSEOVER, (params) => {
this.bodyCellMouseover(params)
})
// receive body cell mousedown
this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_MOUSEDOWN, (params) => {
this.bodyCellMousedown(params)
})
// receive body cell mousemove
this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_MOUSEMOVE, (params) => {
this.bodyCellMousemove(params)
})
// receive body cell mouseup
this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_MOUSEUP, (params) => {
this.bodyCellMouseup(params)
})
// receive selection corner mousedown
this.eventCenter.on(GLOBAL_EVENT.SELECTION_CORNER_MOUSEDOWN, (params) => {
this.cellSelectionCornerMousedown(params)
})
// receive selection corner mouseup
this.eventCenter.on(GLOBAL_EVENT.SELECTION_CORNER_MOUSEUP, (params) => {
this.cellSelectionCornerMouseup(params)
})
// autofilling direction change
this.eventCenter.on(GLOBAL_EVENT.AUTOFILLING_DIRECTION_CHANGE, (params) => {
this.autofillingDirectionChange(params)
})
// receive body cell contextmenu(right click)
this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_CONTEXTMENU, (params) => {
this.bodyCellContextmenu(params)
})
// receive body cell double click
this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_DOUBLE_CLICK, (params) => {
this.bodyCellDoubleClick(params)
})
// receive header cell contextmenu(right click)
this.eventCenter.on(GLOBAL_EVENT.HEADER_CELL_CLICK, (params) => {
this.headerCellClick(params)
})
// receive header cell contextmenu(right click)
this.eventCenter.on(GLOBAL_EVENT.HEADER_CELL_CONTEXTMENU, (params) => {
this.headerCellContextmenu(params)
})
// receive header cell mousedown
this.eventCenter.on(GLOBAL_EVENT.HEADER_CELL_MOUSEDOWN, (params) => {
this.headerCellMousedown(params)
})
// receive header cell mouseover
this.eventCenter.on(GLOBAL_EVENT.HEADER_CELL_MOUSEOVER, (params) => {
this.headerCellMouseover(params)
})
// receive header cell mousemove
this.eventCenter.on(GLOBAL_EVENT.HEADER_CELL_MOUSEMOVE, (params) => {
this.headerCellMousemove(params)
})
// receive header cell mouseleave
this.eventCenter.on(GLOBAL_EVENT.HEADER_CELL_MOUSELEAVE, (params) => {
this.headerCellMouseleave(params)
})
// add key down event listener
document.addEventListener('keydown', this.dealKeydownEvent)
// init scrolling
this.initScrolling()
},
unmounted() {
// remove key down event listener
document.removeEventListener('keydown', this.dealKeydownEvent)
},
methods: {
// int header rows
initHeaderRows() {
const { groupColumns } = this
if (Array.isArray(groupColumns)) {
this.headerRows = groupColumns.map(() => {
return { rowHeight: 0 }
})
}
},
// int footer rows
initFooterRows() {
const { footerData } = this
if (Array.isArray(footerData)) {
this.footerRows = footerData.map(() => {
return { rowHeight: 0 }
})
}
},
// header tr height resize
headerRowHeightChange({ rowIndex, height }) {
this.headerRows.splice(rowIndex, 1, { rowHeight: height })
},
// footer row height resize
footRowHeightChange({ rowIndex, height }) {
this.footerRows.splice(rowIndex, 1, { rowHeight: height })
},
// body cell width change
bodyCellWidthChange(colWidths) {
this.colgroups = this.colgroups.map((item) => {
item._realTimeWidth = colWidths.get(item.key)
return item
})
this.hooks.triggerHook(HOOKS_NAME.TABLE_CELL_WIDTH_CHANGE)
},
// set column width for column resize
setColumnWidth({ colKey, width }) {
this.colgroups = this.colgroups.map((item) => {
if (item.key === colKey) {
item._columnResizeWidth = width
}
return item
})
this.$nextTick(() => {
this.setScrollBarStatus()
})
this.hooks.triggerHook(HOOKS_NAME.TABLE_CELL_WIDTH_CHANGE)
},
// update colgroups by sort change
updateColgroupsBySortChange(sortColumns) {
this.colgroups = this.colgroups.map((item) => {
// update colgroups by sort columns
if (Object.keys(sortColumns).indexOf(item.field) > -1) {
item.sortBy = sortColumns[item.field]
}
return item
})
},
// init column width by column resize
initColumnWidthByColumnResize() {
const { enableColumnResize } = this
const columnDefaultWidth = 50
if (enableColumnResize) {
this.colgroups = this.colgroups.map((item) => {
let columnWidth = columnDefaultWidth
if (isNumber(item.width)) {
columnWidth = item.width
}
item._columnResizeWidth = columnWidth
return item
})
}
},
// init columns
initColumns() {
const { columnHiddenOption } = this
if (columnHiddenOption) {
const { defaultHiddenColumnKeys } = columnHiddenOption
if (!isEmptyArray(defaultHiddenColumnKeys)) {
this.hiddenColumns = defaultHiddenColumnKeys
}
}
this.showOrHideColumns()
},
// show or hide columns
showOrHideColumns() {
let cloneColumns = cloneDeep(this.columns)
cloneColumns = cloneColumns.map((col) => {
// 操作列默认左固定
if (col.operationColumn) {
col.fixed = COLUMN_FIXED_TYPE.LEFT
}
return col
})
const { hiddenColumns } = this
if (!isEmptyArray(hiddenColumns)) {
// recursive remove column key
hiddenColumns.forEach((key) => {
cloneColumns = recursiveRemoveColumnByKey(
cloneColumns,
key,
)
})
}
this.cloneColumns = cloneColumns
},
// 初始化分组表头
initGroupColumns() {
const result = initGroupColumns(this.cloneColumns)
// set is group header
this.isGroupHeader = result.isGroupHeader
// set colgroups
this.colgroups = result.colgroups
// set groupColumns
this.groupColumns = result.groupColumns
},
// scroll bar width
getScrollBarWidth() {
let result = 0
const { scrollBarWidth } = this
if (scrollBarWidth) {
result = scrollBarWidth
} else {
result = getScrollbarWidth()
this.scrollBarWidth = result
}
return result
},
/*
* @selectedAllChange
* @desc selected all change
* @param {bool} isSelected - is selected
*/
selectedAllChange({ isSelected }) {
this.eventCenter.emit(GLOBAL_EVENT.CHECKBOX_SELECTED_ALL_CHANGE_BODY,
{
isSelected,
},
)
},
/*
* @setSelectedAllInfo
* @desc set selected all info
* @param {bool} isSelected - is selected
* @param {bool} isIndeterminate - is indeterminate
*/
setSelectedAllInfo({ isSelected, isIndeterminate }) {
this.eventCenter.emit(GLOBAL_EVENT.CHECKBOX_SELECTED_ALL_INFO_CHECKBOX,
{
isSelected,
isIndeterminate,
},
)
},
// cell selection current cell change
cellSelectionCurrentCellChange({ rowKey, colKey }) {
this.cellSelectionData.currentCell.colKey = colKey
this.cellSelectionData.currentCell.rowKey = rowKey
this.cellSelectionData.currentCell.rowIndex =
this.allRowKeys.indexOf(rowKey)
},
// cell selection end cell change
cellSelectionNormalEndCellChange({ rowKey, colKey }) {
this.cellSelectionData.normalEndCell.colKey = colKey
this.cellSelectionData.normalEndCell.rowKey = rowKey
this.cellSelectionData.normalEndCell.rowIndex =
this.allRowKeys.indexOf(rowKey)
},
// cell selection auto fill cell change
cellSelectionAutofillCellChange({ rowKey, colKey }) {
this.cellSelectionData.autoFillEndCell.colKey = colKey
this.cellSelectionData.autoFillEndCell.rowKey = rowKey
},
// clear cell selection current cell
clearCellSelectionCurrentCell() {
this.cellSelectionCurrentCellChange({
rowKey: '',
colKey: '',
rowIndex: -1,
})
},
// clear cell selection normal end cell
clearCellSelectionNormalEndCell() {
this.cellSelectionNormalEndCellChange({
rowKey: '',
colKey: '',
rowIndex: -1,
})
},
// clear cell selection autofill end cell
clearCellSelectionAutofillEndCell() {
this.cellSelectionAutofillCellChange({ rowKey: '', colKey: '' })
},
// header indicator colKeys change
headerIndicatorColKeysChange({ startColKey, endColKey }) {
const { colgroups } = this
this.headerIndicatorColKeys.startColKey = startColKey
this.headerIndicatorColKeys.startColKeyIndex = colgroups.findIndex(
(x) => x.key === startColKey,
)
this.headerIndicatorColKeys.endColKey = endColKey
this.headerIndicatorColKeys.endColKeyIndex = colgroups.findIndex(
(x) => x.key === endColKey,
)
},
// clear header indicator colKeys
clearHeaderIndicatorColKeys() {
this.headerIndicatorColKeys.startColKey = ''
this.headerIndicatorColKeys.startColKeyIndex = -1
this.headerIndicatorColKeys.endColKey = ''
this.headerIndicatorColKeys.endColKeyIndex = -1
},
// body indicator rowKeys change
bodyIndicatorRowKeysChange({ startRowKey, endRowKey }) {
const { allRowKeys } = this
this.bodyIndicatorRowKeys.startRowKey = startRowKey
this.bodyIndicatorRowKeys.startRowKeyIndex =
allRowKeys.indexOf(startRowKey)
this.bodyIndicatorRowKeys.endRowKey = endRowKey
this.bodyIndicatorRowKeys.endRowKeyIndex =
allRowKeys.indexOf(endRowKey)
},
// clear body indicator RowKeys
clearBodyIndicatorRowKeys() {
this.bodyIndicatorRowKeys.startRowKey = ''
this.bodyIndicatorRowKeys.startRowKeyIndex = -1
this.bodyIndicatorRowKeys.endRowKey = ''
this.bodyIndicatorRowKeys.endRowKeyIndex = -1
},
// set cell selection by autofill
setCellSelectionByAutofill() {
const {
cellAutofillOption,
cellSelectionRangeData,
colgroups,
allRowKeys,
autofillingDirection,
currentCellSelectionType,
} = this
const { autoFillEndCell, currentCell } = this.cellSelectionData
const { rowKey, colKey } = autoFillEndCell
if (isEmptyValue(rowKey) || isEmptyValue(colKey)) {
return false
}
let currentCellData = {}
let normalEndCellData = {}
const { leftColKey, rightColKey, topRowKey, bottomRowKey } =
cellSelectionRangeData
// cell selection range auto fill
if (
currentCellSelectionType === CURRENT_CELL_SELECTION_TYPES.RANGE
) {
if (
!isCellInSelectionRange({
cellData: autoFillEndCell,
cellSelectionRangeData,
colgroups,
allRowKeys,
})
) {
if (autofillingDirection === AUTOFILLING_DIRECTION.RIGHT) {
currentCellData = {
rowKey: topRowKey,
colKey: leftColKey,
}
normalEndCellData = { rowKey: bottomRowKey, colKey }
} else if (
autofillingDirection === AUTOFILLING_DIRECTION.DOWN
) {
currentCellData = {
rowKey: topRowKey,
colKey: leftColKey,
}
normalEndCellData = { rowKey, colKey: rightColKey }
} else if (
autofillingDirection === AUTOFILLING_DIRECTION.UP
) {
currentCellData = {
rowKey,
colKey: leftColKey,
}
normalEndCellData = {
rowKey: bottomRowKey,
colKey: rightColKey,
}
} else if (
autofillingDirection === AUTOFILLING_DIRECTION.LEFT
) {
currentCellData = { rowKey: topRowKey, colKey }
normalEndCellData = {
rowKey: bottomRowKey,
colKey: rightColKey,
}
}
} else {
// return if within the range
return false
}
} else if ( // cell selection single auto fill
currentCellSelectionType === CURRENT_CELL_SELECTION_TYPES.SINGLE
) {
if (
currentCell.rowKey !== rowKey ||
currentCell.colKey !== colKey
) {
if (autofillingDirection === AUTOFILLING_DIRECTION.RIGHT) {
currentCellData = {
rowKey,
colKey: leftColKey,
}
normalEndCellData = {
rowKey,
colKey,
}
} else if (
autofillingDirection === AUTOFILLING_DIRECTION.DOWN
) {
currentCellData = {
rowKey: topRowKey,
colKey: leftColKey,
}
normalEndCellData = {
rowKey,
colKey: leftColKey,
}
} else if (
autofillingDirection === AUTOFILLING_DIRECTION.UP
) {
currentCellData = {
rowKey,
colKey: leftColKey,
}
normalEndCellData = {
rowKey: bottomRowKey,
colKey: leftColKey,
}
} else if (
autofillingDirection === AUTOFILLING_DIRECTION.LEFT
) {
currentCellData = {
rowKey,
colKey,
}
normalEndCellData = {
rowKey,
colKey: rightColKey,
}
}
} else {
// return if within the range
return false
}
}
const cellAutofillParams = {
tableData: this.tableData,
allRowKeys: this.allRowKeys,
colgroups: this.colgroups,
rowKeyFieldName: this.rowKeyFieldName,
direction: autofillingDirection,
currentCellSelectionType,
cellSelectionRangeData,
nextCurrentCell: currentCellData,
nextNormalEndCell: normalEndCellData,
}
if (cellAutofillOption) {
const { beforeAutofill, afterAutofill } = cellAutofillOption
if (isFunction(beforeAutofill)) {
// before autofill
const autofillResponse = cellAutofill({
isReplaceData: false,
...cellAutofillParams,
})
const callback = beforeAutofill(autofillResponse)
if (isBoolean(callback) && !callback) {
return false
}
}
// after autofill
const autofillResponse = cellAutofill({
isReplaceData: true,
...cellAutofillParams,
})
if (isFunction(afterAutofill)) {
afterAutofill(autofillResponse)
}
}
if (!isEmptyValue(currentCellData.rowKey)) {
this.cellSelectionCurrentCellChange({
rowKey: currentCellData.rowKey,
colKey: currentCellData.colKey,
})
}
if (!isEmptyValue(normalEndCellData.rowKey)) {
this.cellSelectionNormalEndCellChange({
rowKey: normalEndCellData.rowKey,
colKey: normalEndCellData.colKey,
})
}
},
// cell selection range data change
cellSelectionRangeDataChange(newData) {
this.cellSelectionRangeData = Object.assign(
this.cellSelectionRangeData,
newData,
)
},
// autofilling direction change
autofillingDirectionChange(direction) {
this.autofillingDirection = direction
},
// set current cell selection type
setCurrentCellSelectionType() {
const { currentCell, normalEndCell } = this.cellSelectionData
let result
if (
isEmptyValue(currentCell.rowKey) ||
isEmptyValue(currentCell.colKey)
) {
result = ''
} else {
if (
!isEmptyValue(normalEndCell.rowKey) &&
!isEmptyValue(normalEndCell.colKey)
) {
result = CURRENT_CELL_SELECTION_TYPES.RANGE
} else {
result = CURRENT_CELL_SELECTION_TYPES.SINGLE
}
}
this.currentCellSelectionType = result
},
// deal keydown event
dealKeydownEvent(event) {
const {
colgroups,
cellSelectionData,
enableStopEditing,
isCellEditing,
} = this
const { keyCode, ctrlKey, shiftKey, altKey } = event
const { rowKey, colKey } = cellSelectionData.currentCell
const currentColumn = colgroups.find((x) => x.key === colKey)
if (!isEmptyValue(rowKey) && !isEmptyValue(colKey)) {
switch (keyCode) {
case KEY_CODES.TAB: {
let direction
if (shiftKey) {
direction = CELL_SELECTION_DIRECTION.LEFT
} else {
direction = CELL_SELECTION_DIRECTION.RIGHT
}
this.selectCellByDirection({
direction,
})
this.clearCellSelectionNormalEndCell()
this[INSTANCE_METHODS.STOP_EDITING_CELL]()
event.preventDefault()
break
}
case KEY_CODES.ARROW_LEFT: {
const direction = CELL_SELECTION_DIRECTION.LEFT
if (enableStopEditing) {
this.selectCellByDirection({
direction,
})
this.clearCellSelectionNormalEndCell()
this[INSTANCE_METHODS.STOP_EDITING_CELL]()
event.preventDefault()
}
break
}
case KEY_CODES.ARROW_RIGHT: {
const direction = CELL_SELECTION_DIRECTION.RIGHT
if (enableStopEditing) {
this.selectCellByDirection({
direction,
})
this.clearCellSelectionNormalEndCell()
this[INSTANCE_METHODS.STOP_EDITING_CELL]()
event.preventDefault()
}
break
}
case KEY_CODES.ARROW_UP: {
const direction = CELL_SELECTION_DIRECTION.UP
if (enableStopEditing) {
this.selectCellByDirection({
direction,
})
this.clearCellSelectionNormalEndCell()
this[INSTANCE_METHODS.STOP_EDITING_CELL]()
event.preventDefault()
}
break
}
case KEY_CODES.ARROW_DOWN: {
const direction = CELL_SELECTION_DIRECTION.DOWN
if (enableStopEditing) {
this.selectCellByDirection({
direction,
})
this.clearCellSelectionNormalEndCell()
this[INSTANCE_METHODS.STOP_EDITING_CELL]()
event.preventDefault()
}
break
}
case KEY_CODES.ENTER: {
let direction
// add new line
if (altKey) {
const editInputEditor =
this.$refs[this.editInputRef]
editInputEditor.textareaAddNewLine()
} else if (shiftKey) { // direction up
direction = CELL_SELECTION_DIRECTION.UP
this[INSTANCE_METHODS.STOP_EDITING_CELL]()
} else if (ctrlKey) { // stop editing and stay in current cell
this[INSTANCE_METHODS.STOP_EDITING_CELL]()
} else { // direction down
direction = CELL_SELECTION_DIRECTION.DOWN
this[INSTANCE_METHODS.STOP_EDITING_CELL]()
}
if (direction) {
this.clearCellSelectionNormalEndCell()
this.selectCellByDirection({
direction,
})
}
event.preventDefault()
break
}
case KEY_CODES.SPACE: {
if (!isCellEditing) {
// start editing and enter a space
this[INSTANCE_METHODS.START_EDITING_CELL]({
rowKey,
colKey,
defaultValue: ' ',
})
event.preventDefault()
}
break
}
case KEY_CODES.BACK_SPACE: {
if (!isCellEditing) {
// start editing and clear value
this[INSTANCE_METHODS.START_EDITING_CELL]({
rowKey,
colKey,
defaultValue: '',
})
event.preventDefault()
}
break
}
case KEY_CODES.DELETE: {
if (!isCellEditing) {
// delete cell selection range value
this.deleteCellSelectionRangeValue()
event.preventDefault()
}
break
}
case KEY_CODES.F2: {
if (!isCellEditing) {
if (currentColumn.edit) {
// start editing cell and don't allow stop eidting by direction key
this.enableStopEditing = false
this[INSTANCE_METHODS.START_EDITING_CELL]({
rowKey,
colKey,
})
}
event.preventDefault()
}
break
}
default: {
// enter text directly
if (isInputKeyCode(event)) {
this[INSTANCE_METHODS.START_EDITING_CELL]({
rowKey,
colKey,
defaultValue: '',
})
}
break
}
}
}
},
// select cell by direction
selectCellByDirection({ direction }) {
const { colgroups, allRowKeys, cellSelectionData } = this
const { rowKey, colKey } = cellSelectionData.currentCell
const columnIndex = colgroups.findIndex((x) => x.key === colKey)
const rowIndex = allRowKeys.indexOf(rowKey)
if (direction === CELL_SELECTION_DIRECTION.LEFT) {
if (columnIndex > 0) {
const nextColumn = colgroups[columnIndex - 1]
this.cellSelectionData.currentCell.colKey = nextColumn.key
this.columnToVisible(nextColumn)
}
} else if (direction === CELL_SELECTION_DIRECTION.RIGHT) {
if (columnIndex < colgroups.length - 1) {
const nextColumn = colgroups[columnIndex + 1]
this.cellSelectionData.currentCell.colKey = nextColumn.key
this.columnToVisible(nextColumn)
}
} else if (direction === CELL_SELECTION_DIRECTION.UP) {
if (rowIndex > 0) {
const nextRowKey = allRowKeys[rowIndex - 1]
this.rowToVisible(KEY_CODES.ARROW_UP, nextRowKey)
}
} else if (direction === CELL_SELECTION_DIRECTION.DOWN) {
if (rowIndex < allRowKeys.length - 1) {
const nextRowKey = allRowKeys[rowIndex + 1]
this.rowToVisible(KEY_CODES.ARROW_DOWN, nextRowKey)
}
}
},
/*
* @columnToVisible
* @desc column to visible
* @param {object} nextColumn - next column
*/
columnToVisible(nextColumn) {
const { hasXScrollBar, colgroups } = this
// if (!nextColumn || nextColumn) return
if (!hasXScrollBar) {