ant-design-vue/components/vc-table/src/Table.jsx

504 lines
14 KiB
React
Raw Normal View History

2018-03-25 10:07:04 +00:00
2018-03-26 15:06:58 +00:00
/* eslint-disable camelcase */
2018-03-25 10:07:04 +00:00
import PropTypes from '../../_util/vue-types'
import { debounce, warningOnce } from './utils'
import shallowequal from 'shallowequal'
import addEventListener from '../../_util/Dom/addEventListener'
import { Provider, create } from '../../_util/store'
import merge from 'lodash/merge'
import ColumnManager from './ColumnManager'
import classes from 'component-classes'
import HeadTable from './HeadTable'
import BodyTable from './BodyTable'
import ExpandableTable from './ExpandableTable'
import { initDefaultProps, getOptionProps } from '../../_util/props-util'
import BaseMixin from '../../_util/BaseMixin'
export default {
name: 'Table',
mixins: [BaseMixin],
props: initDefaultProps({
data: PropTypes.array,
useFixedHeader: PropTypes.bool,
columns: PropTypes.array,
prefixCls: PropTypes.string,
bodyStyle: PropTypes.object,
rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
2018-03-31 09:46:35 +00:00
customRow: PropTypes.func,
customHeaderRow: PropTypes.func,
2018-03-25 10:07:04 +00:00
// onRowClick: PropTypes.func,
// onRowDoubleClick: PropTypes.func,
// onRowContextMenu: PropTypes.func,
// onRowMouseEnter: PropTypes.func,
// onRowMouseLeave: PropTypes.func,
showHeader: PropTypes.bool,
title: PropTypes.func,
id: PropTypes.string,
footer: PropTypes.func,
emptyText: PropTypes.any,
scroll: PropTypes.object,
rowRef: PropTypes.func,
getBodyWrapper: PropTypes.func,
components: PropTypes.shape({
table: PropTypes.any,
header: PropTypes.shape({
wrapper: PropTypes.any,
row: PropTypes.any,
cell: PropTypes.any,
}),
body: PropTypes.shape({
wrapper: PropTypes.any,
row: PropTypes.any,
cell: PropTypes.any,
}),
}),
expandIconAsCell: PropTypes.bool,
expandedRowKeys: PropTypes.array,
expandedRowClassName: PropTypes.func,
defaultExpandAllRows: PropTypes.bool,
defaultExpandedRowKeys: PropTypes.array,
expandIconColumnIndex: PropTypes.number,
expandedRowRender: PropTypes.func,
childrenColumnName: PropTypes.string,
indentSize: PropTypes.number,
2018-03-26 15:05:29 +00:00
expandRowByClick: PropTypes.bool,
2018-03-25 10:07:04 +00:00
}, {
data: [],
useFixedHeader: false,
rowKey: 'key',
rowClassName: () => '',
prefixCls: 'rc-table',
bodyStyle: {},
showHeader: true,
scroll: {},
rowRef: () => null,
emptyText: () => 'No Data',
2018-03-31 09:46:35 +00:00
customHeaderRow: () => {},
2018-03-25 10:07:04 +00:00
}),
// static childContextTypes = {
// table: PropTypes.any,
// components: PropTypes.any,
// },
created () {
[
'rowClick',
'rowDoubleclick',
'rowContextmenu',
'rowMouseenter',
'rowMouseleave',
].forEach(name => {
warningOnce(
this.$listeners[name] === undefined,
2018-03-31 09:46:35 +00:00
`${name} is deprecated, please use customRow instead.`,
2018-03-25 10:07:04 +00:00
)
})
warningOnce(
this.getBodyWrapper === undefined,
'getBodyWrapper is deprecated, please use custom components instead.',
)
// this.columnManager = new ColumnManager(this.columns, this.$slots.default)
this.store = create({
currentHoverKey: null,
fixedColumnsHeadRowsHeight: [],
fixedColumnsBodyRowsHeight: [],
})
this.setScrollPosition('left')
this.debouncedWindowResize = debounce(this.handleWindowResize, 150)
},
data () {
this.preData = [...this.data]
return {
2018-03-29 10:18:41 +00:00
columnManager: new ColumnManager(this.columns),
2018-03-25 10:07:04 +00:00
sComponents: merge({
table: 'table',
header: {
wrapper: 'thead',
row: 'tr',
cell: 'th',
},
body: {
wrapper: 'tbody',
row: 'tr',
cell: 'td',
},
}, this.components),
}
},
provide () {
return {
table: this,
}
},
watch: {
components (val) {
this._components = merge({
table: 'table',
header: {
wrapper: 'thead',
row: 'tr',
cell: 'th',
},
body: {
wrapper: 'tbody',
row: 'tr',
cell: 'td',
},
}, this.components)
},
columns (val) {
if (val) {
this.columnManager.reset(val)
}
},
data (val) {
if (val.length === 0 && this.hasScrollX()) {
this.$nextTick(() => {
this.resetScrollX()
})
}
},
},
mounted () {
this.$nextTick(() => {
if (this.columnManager.isAnyColumnsFixed()) {
this.handleWindowResize()
this.resizeEvent = addEventListener(
window, 'resize', this.debouncedWindowResize
)
}
})
},
updated (prevProps) {
if (this.columnManager.isAnyColumnsFixed()) {
this.handleWindowResize()
if (!this.resizeEvent) {
this.resizeEvent = addEventListener(
window, 'resize', this.debouncedWindowResize
)
}
}
},
beforeDestroy () {
if (this.resizeEvent) {
this.resizeEvent.remove()
}
if (this.debouncedWindowResize) {
this.debouncedWindowResize.cancel()
}
},
methods: {
getRowKey (record, index) {
const rowKey = this.rowKey
const key = (typeof rowKey === 'function')
? rowKey(record, index) : record[rowKey]
warningOnce(
key !== undefined,
'Each record in table should have a unique `key` prop,' +
'or set `rowKey` to an unique primary key.'
)
return key === undefined ? index : key
},
setScrollPosition (position) {
this.scrollPosition = position
2018-03-26 15:05:29 +00:00
if (this.$refs.tableNode) {
2018-03-25 10:07:04 +00:00
const { prefixCls } = this
if (position === 'both') {
2018-03-26 15:05:29 +00:00
classes(this.$refs.tableNode)
2018-03-25 10:07:04 +00:00
.remove(new RegExp(`^${prefixCls}-scroll-position-.+$`))
.add(`${prefixCls}-scroll-position-left`)
.add(`${prefixCls}-scroll-position-right`)
} else {
2018-03-26 15:05:29 +00:00
classes(this.$refs.tableNode)
2018-03-25 10:07:04 +00:00
.remove(new RegExp(`^${prefixCls}-scroll-position-.+$`))
.add(`${prefixCls}-scroll-position-${position}`)
}
}
},
setScrollPositionClassName () {
2018-03-26 15:05:29 +00:00
const node = this.ref_bodyTable
2018-03-25 10:07:04 +00:00
const scrollToLeft = node.scrollLeft === 0
const scrollToRight = node.scrollLeft + 1 >=
node.children[0].getBoundingClientRect().width -
node.getBoundingClientRect().width
if (scrollToLeft && scrollToRight) {
this.setScrollPosition('both')
} else if (scrollToLeft) {
this.setScrollPosition('left')
} else if (scrollToRight) {
this.setScrollPosition('right')
} else if (this.scrollPosition !== 'middle') {
this.setScrollPosition('middle')
}
},
handleWindowResize () {
this.syncFixedTableRowHeight()
this.setScrollPositionClassName()
},
syncFixedTableRowHeight () {
2018-03-26 15:05:29 +00:00
const tableRect = this.$refs.tableNode.getBoundingClientRect()
2018-03-25 10:07:04 +00:00
// If tableNode's height less than 0, suppose it is hidden and don't recalculate rowHeight.
// see: https://github.com/ant-design/ant-design/issues/4836
if (tableRect.height !== undefined && tableRect.height <= 0) {
return
}
2018-03-26 15:05:29 +00:00
const { prefixCls } = this
const headRows = this.ref_headTable
? this.ref_headTable.querySelectorAll('thead')
: this.ref_bodyTable.querySelectorAll('thead')
const bodyRows = this.ref_bodyTable.querySelectorAll(`.${prefixCls}-row`) || []
2018-03-25 10:07:04 +00:00
const fixedColumnsHeadRowsHeight = [].map.call(
headRows, row => row.getBoundingClientRect().height || 'auto'
)
const fixedColumnsBodyRowsHeight = [].map.call(
bodyRows, row => row.getBoundingClientRect().height || 'auto'
)
const state = this.store.getState()
if (shallowequal(state.fixedColumnsHeadRowsHeight, fixedColumnsHeadRowsHeight) &&
shallowequal(state.fixedColumnsBodyRowsHeight, fixedColumnsBodyRowsHeight)) {
return
}
this.store.setState({
fixedColumnsHeadRowsHeight,
fixedColumnsBodyRowsHeight,
})
},
resetScrollX () {
2018-03-26 15:05:29 +00:00
if (this.ref_headTable) {
this.ref_headTable.scrollLeft = 0
2018-03-25 10:07:04 +00:00
}
2018-03-26 15:05:29 +00:00
if (this.ref_bodyTable) {
this.ref_bodyTable.scrollLeft = 0
2018-03-25 10:07:04 +00:00
}
},
hasScrollX () {
const { scroll = {}} = this
return 'x' in scroll
},
handleBodyScrollLeft (e) {
// Fix https://github.com/ant-design/ant-design/issues/7635
if (e.currentTarget !== e.target) {
return
}
const target = e.target
const { scroll = {}} = this
2018-03-26 15:05:29 +00:00
const { ref_headTable, ref_bodyTable } = this
2018-03-25 10:07:04 +00:00
if (target.scrollLeft !== this.lastScrollLeft && scroll.x) {
2018-03-26 15:05:29 +00:00
if (target === ref_bodyTable && ref_headTable) {
ref_headTable.scrollLeft = target.scrollLeft
} else if (target === ref_headTable && ref_bodyTable) {
ref_bodyTable.scrollLeft = target.scrollLeft
2018-03-25 10:07:04 +00:00
}
this.setScrollPositionClassName()
}
// Remember last scrollLeft for scroll direction detecting.
this.lastScrollLeft = target.scrollLeft
},
handleBodyScrollTop (e) {
const target = e.target
const { scroll = {}} = this
2018-03-27 11:43:22 +00:00
const { ref_headTable, ref_bodyTable, ref_fixedColumnsBodyLeft, ref_fixedColumnsBodyRight } = this
2018-03-26 15:05:29 +00:00
if (target.scrollTop !== this.lastScrollTop && scroll.y && target !== ref_headTable) {
2018-03-25 10:07:04 +00:00
const scrollTop = target.scrollTop
2018-03-27 11:43:22 +00:00
if (ref_fixedColumnsBodyLeft && target !== ref_fixedColumnsBodyLeft) {
ref_fixedColumnsBodyLeft.scrollTop = scrollTop
2018-03-25 10:07:04 +00:00
}
2018-03-27 11:43:22 +00:00
if (ref_fixedColumnsBodyRight && target !== ref_fixedColumnsBodyRight) {
ref_fixedColumnsBodyRight.scrollTop = scrollTop
2018-03-25 10:07:04 +00:00
}
2018-03-26 15:05:29 +00:00
if (ref_bodyTable && target !== ref_bodyTable) {
ref_bodyTable.scrollTop = scrollTop
2018-03-25 10:07:04 +00:00
}
}
// Remember last scrollTop for scroll direction detecting.
this.lastScrollTop = target.scrollTop
},
handleBodyScroll (e) {
this.handleBodyScrollLeft(e)
this.handleBodyScrollTop(e)
},
2018-03-26 15:05:29 +00:00
saveChildrenRef (name, node) {
this[`ref_${name}`] = node
},
2018-03-25 10:07:04 +00:00
renderMainTable () {
const { scroll, prefixCls } = this
const isAnyColumnsFixed = this.columnManager.isAnyColumnsFixed()
const scrollable = isAnyColumnsFixed || scroll.x || scroll.y
const table = [
this.renderTable({
columns: this.columnManager.groupedColumns(),
isAnyColumnsFixed,
}),
this.renderEmptyText(),
this.renderFooter(),
]
return scrollable ? (
<div class={`${prefixCls}-scroll`}>{table}</div>
) : table
},
renderLeftFixedTable () {
const { prefixCls } = this
return (
<div class={`${prefixCls}-fixed-left`}>
{this.renderTable({
columns: this.columnManager.leftColumns(),
fixed: 'left',
})}
</div>
)
},
renderRightFixedTable () {
const { prefixCls } = this
return (
<div class={`${prefixCls}-fixed-right`}>
{this.renderTable({
columns: this.columnManager.rightColumns(),
fixed: 'right',
})}
</div>
)
},
renderTable (options) {
const { columns, fixed, isAnyColumnsFixed } = options
const { prefixCls, scroll = {}} = this
const tableClassName = (scroll.x || fixed) ? `${prefixCls}-fixed` : ''
const headTable = (
<HeadTable
key='head'
columns={columns}
fixed={fixed}
tableClassName={tableClassName}
handleBodyScrollLeft={this.handleBodyScrollLeft}
expander={this.expander}
/>
)
const bodyTable = (
<BodyTable
key='body'
columns={columns}
fixed={fixed}
tableClassName={tableClassName}
getRowKey={this.getRowKey}
handleBodyScroll={this.handleBodyScroll}
expander={this.expander}
isAnyColumnsFixed={isAnyColumnsFixed}
/>
)
return [headTable, bodyTable]
},
renderTitle () {
2018-03-26 15:05:29 +00:00
const { title, prefixCls, data } = this
2018-03-25 10:07:04 +00:00
return title ? (
<div class={`${prefixCls}-title`} key='title'>
2018-03-26 15:05:29 +00:00
{title(data)}
2018-03-25 10:07:04 +00:00
</div>
) : null
},
renderFooter () {
2018-03-26 15:05:29 +00:00
const { footer, prefixCls, data } = this
2018-03-25 10:07:04 +00:00
return footer ? (
<div class={`${prefixCls}-footer`} key='footer'>
2018-03-26 15:05:29 +00:00
{footer(data)}
2018-03-25 10:07:04 +00:00
</div>
) : null
},
renderEmptyText () {
const { emptyText, prefixCls, data } = this
if (data.length) {
return null
}
const emptyClassName = `${prefixCls}-placeholder`
return (
<div class={emptyClassName} key='emptyText'>
{(typeof emptyText === 'function') ? emptyText() : emptyText}
</div>
)
},
},
render () {
const props = getOptionProps(this)
const { $listeners, columnManager, getRowKey } = this
const prefixCls = props.prefixCls
let className = props.prefixCls
if (props.useFixedHeader || (props.scroll && props.scroll.y)) {
className += ` ${prefixCls}-fixed-header`
}
if (this.scrollPosition === 'both') {
className += ` ${prefixCls}-scroll-position-left ${prefixCls}-scroll-position-right`
} else {
className += ` ${prefixCls}-scroll-position-${this.scrollPosition}`
}
const hasLeftFixed = columnManager.isAnyColumnsLeftFixed()
const hasRightFixed = columnManager.isAnyColumnsRightFixed()
const expandableTableProps = {
props: {
...props,
columnManager,
getRowKey,
},
on: { ...$listeners },
scopedSlots: {
default: (expander) => {
this.expander = expander
return (
<div
ref='tableNode'
class={className}
// style={props.style}
// id={props.id}
>
{this.renderTitle()}
<div class={`${prefixCls}-content`}>
{this.renderMainTable()}
{hasLeftFixed && this.renderLeftFixedTable()}
{hasRightFixed && this.renderRightFixedTable()}
</div>
</div>
)
},
},
}
return (
<Provider store={this.store}>
<ExpandableTable
{...expandableTableProps}
/>
</Provider>
)
},
}