This commit is contained in:
wangxueliang 2018-04-01 19:25:02 +08:00
commit 0f0fdfaa5f
24 changed files with 1595 additions and 1515 deletions

View File

@ -132,6 +132,11 @@ import DatePicker from './date-picker'
const { MonthPicker, RangePicker, WeekPicker } = DatePicker
export { DatePicker, MonthPicker, RangePicker, WeekPicker }
import Table from './table'
const { Column: TableColumn, ColumnGroup: TableColumnGroup } = Table
export { Table, TableColumn, TableColumnGroup }
export { default as version } from './version'
export { default as Slider } from './slider'

View File

@ -34,4 +34,8 @@ import './steps/style'
import './breadcrumb/style'
import './calendar/style'
import './date-picker/style'
<<<<<<< HEAD
import './slider/style'
=======
import './table/style'
>>>>>>> f1e62240e0d804ac03d8ca28616621f3c312f31f

View File

@ -0,0 +1,75 @@
import Checkbox from '../checkbox'
import Radio from '../radio'
import { SelectionBoxProps } from './interface'
import BaseMixin from '../_util/BaseMixin'
import { getOptionProps } from '../_util/props-util'
export default {
mixins: [BaseMixin],
name: 'SelectionBox',
props: SelectionBoxProps,
data () {
return {
checked: this.getCheckState(this.$props),
}
},
mounted () {
this.subscribe()
},
beforeDestroy () {
if (this.unsubscribe) {
this.unsubscribe()
}
},
methods: {
subscribe () {
const { store } = this
this.unsubscribe = store.subscribe(() => {
const checked = this.getCheckState(this.$props)
this.setState({ checked })
})
},
getCheckState (props) {
const { store, defaultSelection, rowIndex } = props
let checked = false
if (store.getState().selectionDirty) {
checked = store.getState().selectedRowKeys.indexOf(rowIndex) >= 0
} else {
checked = (store.getState().selectedRowKeys.indexOf(rowIndex) >= 0 ||
defaultSelection.indexOf(rowIndex) >= 0)
}
return checked
},
},
render () {
const { type, rowIndex, ...rest } = getOptionProps(this)
const { checked, $attrs, $listeners } = this
const checkboxProps = {
props: {
checked,
...rest,
},
attrs: $attrs,
on: $listeners,
}
if (type === 'radio') {
checkboxProps.props.value = rowIndex
return (
<Radio
{...checkboxProps}
/>
)
} else {
return (
<Checkbox
{...checkboxProps}
/>
)
}
},
}

View File

@ -1,68 +0,0 @@
import * as React from 'react';
import Checkbox from '../checkbox';
import Radio from '../radio';
import { SelectionBoxProps, SelectionBoxState } from './interface';
export default class SelectionBox extends React.Component<SelectionBoxProps, SelectionBoxState> {
unsubscribe: () => void;
constructor(props: SelectionBoxProps) {
super(props);
this.state = {
checked: this.getCheckState(props),
};
}
componentDidMount() {
this.subscribe();
}
componentWillUnmount() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
subscribe() {
const { store } = this.props;
this.unsubscribe = store.subscribe(() => {
const checked = this.getCheckState(this.props);
this.setState({ checked });
});
}
getCheckState(props: SelectionBoxProps) {
const { store, defaultSelection, rowIndex } = props;
let checked = false;
if (store.getState().selectionDirty) {
checked = store.getState().selectedRowKeys.indexOf(rowIndex) >= 0;
} else {
checked = (store.getState().selectedRowKeys.indexOf(rowIndex) >= 0 ||
defaultSelection.indexOf(rowIndex) >= 0);
}
return checked;
}
render() {
const { type, rowIndex, ...rest } = this.props;
const { checked } = this.state;
if (type === 'radio') {
return (
<Radio
checked={checked}
value={rowIndex}
{...rest}
/>
);
} else {
return (
<Checkbox
checked={checked}
{...rest}
/>
);
}
}
}

View File

@ -0,0 +1,182 @@
import Checkbox from '../checkbox'
import Dropdown from '../dropdown'
import Menu from '../menu'
import Icon from '../icon'
import classNames from 'classnames'
import { SelectionCheckboxAllProps } from './interface'
import BaseMixin from '../_util/BaseMixin'
export default {
props: SelectionCheckboxAllProps,
name: 'SelectionCheckboxAll',
mixins: [BaseMixin],
data () {
const { $props: props } = this
this.defaultSelections = props.hideDefaultSelections ? [] : [{
key: 'all',
text: props.locale.selectAll,
onSelect: () => {},
}, {
key: 'invert',
text: props.locale.selectInvert,
onSelect: () => {},
}]
this.state = {
checked: this.getCheckState(props),
indeterminate: this.getIndeterminateState(props),
}
},
mounted () {
this.subscribe()
},
componentWillReceiveProps (nextProps) {
this.setCheckState()
},
beforeDestroy () {
if (this.unsubscribe) {
this.unsubscribe()
}
},
methods: {
subscribe () {
const { store } = this
this.unsubscribe = store.subscribe(() => {
this.setCheckState()
})
},
checkSelection (data, type, byDefaultChecked) {
const { store, getCheckboxPropsByItem, getRecordKey } = this
// type should be 'every' | 'some'
if (type === 'every' || type === 'some') {
return (
byDefaultChecked
? data[type]((item, i) => getCheckboxPropsByItem(item, i).defaultChecked)
: data[type]((item, i) =>
store.getState().selectedRowKeys.indexOf(getRecordKey(item, i)) >= 0)
)
}
return false
},
setCheckState () {
const checked = this.getCheckState()
const indeterminate = this.getIndeterminateState()
if (checked !== this.checked) {
this.setState({ checked })
}
if (indeterminate !== this.indeterminate) {
this.setState({ indeterminate })
}
},
getCheckState () {
const { store, data } = this
let checked
if (!data.length) {
checked = false
} else {
checked = store.getState().selectionDirty
? this.checkSelection(data, 'every', false)
: (
this.checkSelection(data, 'every', false) ||
this.checkSelection(data, 'every', true)
)
}
return checked
},
getIndeterminateState () {
const { store, data } = this
let indeterminate
if (!data.length) {
indeterminate = false
} else {
indeterminate = store.getState().selectionDirty
? (
this.checkSelection(data, 'some', false) &&
!this.checkSelection(data, 'every', false)
)
: ((this.checkSelection(data, 'some', false) &&
!this.checkSelection(data, 'every', false)) ||
(this.checkSelection(data, 'some', true) &&
!this.checkSelection(data, 'every', true))
)
}
return indeterminate
},
handleSelectAllChagne (e) {
const checked = e.target.checked
this.$emit('select', checked ? 'all' : 'removeAll', 0, null)
},
renderMenus (selections) {
return selections.map((selection, index) => {
return (
<Menu.Item
key={selection.key || index}
>
<div
onClick={() => { this.$emit('select', selection.key, index, selection.onSelect) }}
>
{selection.text}
</div>
</Menu.Item>
)
})
},
},
render () {
const { disabled, prefixCls, selections, getPopupContainer, checked, indeterminate } = this
const selectionPrefixCls = `${prefixCls}-selection`
let customSelections = null
if (selections) {
const newSelections = Array.isArray(selections) ? this.defaultSelections.concat(selections)
: this.defaultSelections
const menu = (
<Menu
class={`${selectionPrefixCls}-menu`}
selectedKeys={[]}
>
{this.renderMenus(newSelections)}
</Menu>
)
customSelections = newSelections.length > 0 ? (
<Dropdown
getPopupContainer={getPopupContainer}
>
<template slot='overlay'>
{menu}
</template>
<div class={`${selectionPrefixCls}-down`}>
<Icon type='down' />
</div>
</Dropdown>
) : null
}
return (
<div class={selectionPrefixCls}>
<Checkbox
class={classNames({ [`${selectionPrefixCls}-select-all-custom`]: customSelections })}
checked={checked}
indeterminate={indeterminate}
disabled={disabled}
onChange={this.handleSelectAllChagne}
/>
{customSelections}
</div>
)
},
}

View File

@ -1,183 +0,0 @@
import * as React from 'react';
import Checkbox from '../checkbox';
import Dropdown from '../dropdown';
import Menu from '../menu';
import Icon from '../icon';
import classNames from 'classnames';
import { SelectionCheckboxAllProps, SelectionCheckboxAllState, SelectionItem } from './interface';
export default class SelectionCheckboxAll<T> extends
React.Component<SelectionCheckboxAllProps<T>, SelectionCheckboxAllState> {
unsubscribe: () => void;
defaultSelections: SelectionItem[];
constructor(props: SelectionCheckboxAllProps<T>) {
super(props);
this.defaultSelections = props.hideDefaultSelections ? [] : [{
key: 'all',
text: props.locale.selectAll,
onSelect: () => {},
}, {
key: 'invert',
text: props.locale.selectInvert,
onSelect: () => {},
}];
this.state = {
checked: this.getCheckState(props),
indeterminate: this.getIndeterminateState(props),
};
}
componentDidMount() {
this.subscribe();
}
componentWillReceiveProps(nextProps: SelectionCheckboxAllProps<T>) {
this.setCheckState(nextProps);
}
componentWillUnmount() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
subscribe() {
const { store } = this.props;
this.unsubscribe = store.subscribe(() => {
this.setCheckState(this.props);
});
}
checkSelection(data: T[], type: string, byDefaultChecked: boolean) {
const { store, getCheckboxPropsByItem, getRecordKey } = this.props;
// type should be 'every' | 'some'
if (type === 'every' || type === 'some') {
return (
byDefaultChecked
? data[type]((item, i) => getCheckboxPropsByItem(item, i).defaultChecked)
: data[type]((item, i) =>
store.getState().selectedRowKeys.indexOf(getRecordKey(item, i)) >= 0)
);
}
return false;
}
setCheckState(props: SelectionCheckboxAllProps<T>) {
const checked = this.getCheckState(props);
const indeterminate = this.getIndeterminateState(props);
if (checked !== this.state.checked) {
this.setState({ checked });
}
if (indeterminate !== this.state.indeterminate) {
this.setState({ indeterminate });
}
}
getCheckState(props: SelectionCheckboxAllProps<T>) {
const { store, data } = props;
let checked;
if (!data.length) {
checked = false;
} else {
checked = store.getState().selectionDirty
? this.checkSelection(data, 'every', false)
: (
this.checkSelection(data, 'every', false) ||
this.checkSelection(data, 'every', true)
);
}
return checked;
}
getIndeterminateState(props: SelectionCheckboxAllProps<T>) {
const { store, data } = props;
let indeterminate;
if (!data.length) {
indeterminate = false;
} else {
indeterminate = store.getState().selectionDirty
? (
this.checkSelection(data, 'some', false) &&
!this.checkSelection(data, 'every', false)
)
: ((this.checkSelection(data, 'some', false) &&
!this.checkSelection(data, 'every', false)) ||
(this.checkSelection(data, 'some', true) &&
!this.checkSelection(data, 'every', true))
);
}
return indeterminate;
}
handleSelectAllChagne = (e: React.ChangeEvent<HTMLInputElement>) => {
let checked = e.target.checked;
this.props.onSelect(checked ? 'all' : 'removeAll', 0, null);
}
renderMenus(selections: SelectionItem[]) {
return selections.map((selection, index) => {
return (
<Menu.Item
key={selection.key || index}
>
<div
onClick={() => {this.props.onSelect(selection.key, index, selection.onSelect); }}
>
{selection.text}
</div>
</Menu.Item>
);
});
}
render() {
const { disabled, prefixCls, selections, getPopupContainer } = this.props;
const { checked, indeterminate } = this.state;
let selectionPrefixCls = `${prefixCls}-selection`;
let customSelections: React.ReactNode = null;
if (selections) {
let newSelections = Array.isArray(selections) ? this.defaultSelections.concat(selections)
: this.defaultSelections;
const menu = (
<Menu
className={`${selectionPrefixCls}-menu`}
selectedKeys={[]}
>
{this.renderMenus(newSelections)}
</Menu>
);
customSelections = newSelections.length > 0 ? (
<Dropdown
overlay={menu}
getPopupContainer={getPopupContainer}
>
<div className={`${selectionPrefixCls}-down`}>
<Icon type="down" />
</div>
</Dropdown>
) : null;
}
return (
<div className={selectionPrefixCls}>
<Checkbox
className={classNames({ [`${selectionPrefixCls}-select-all-custom`]: customSelections })}
checked={checked}
indeterminate={indeterminate}
disabled={disabled}
onChange={this.handleSelectAllChagne}
/>
{customSelections}
</div>
);
}
}

995
components/table/Table.jsx Executable file
View File

@ -0,0 +1,995 @@
import cloneDeep from 'lodash/cloneDeep'
import VcTable from '../vc-table'
import classNames from 'classnames'
import Pagination from '../pagination'
import Icon from '../icon'
import Spin from '../spin'
import LocaleReceiver from '../locale-provider/LocaleReceiver'
import defaultLocale from '../locale-provider/default'
import warning from '../_util/warning'
import FilterDropdown from './filterDropdown'
import createStore from './createStore'
import SelectionBox from './SelectionBox'
import SelectionCheckboxAll from './SelectionCheckboxAll'
import Column from './Column'
import ColumnGroup from './ColumnGroup'
import createBodyRow from './createBodyRow'
import { flatArray, treeMap, flatFilter } from './util'
import { initDefaultProps, mergeProps, getOptionProps } from '../_util/props-util'
import BaseMixin from '../_util/BaseMixin'
import {
TableProps,
} from './interface'
function noop () {
}
function stopPropagation (e) {
e.stopPropagation()
if (e.nativeEvent.stopImmediatePropagation) {
e.nativeEvent.stopImmediatePropagation()
}
}
const defaultPagination = {
on: {
change: noop,
showSizeChange: noop,
},
}
/**
* Avoid creating new object, so that parent component's shouldComponentUpdate
* can works appropriately
*/
const emptyObject = {}
export default {
name: 'Table',
Column,
ColumnGroup,
mixins: [BaseMixin],
props: initDefaultProps(TableProps, {
dataSource: [],
prefixCls: 'ant-table',
useFixedHeader: false,
// rowSelection: null,
size: 'large',
loading: false,
bordered: false,
indentSize: 20,
locale: {},
rowKey: 'key',
showHeader: true,
}),
// CheckboxPropsCache: {
// [key: string]: any;
// };
// store: Store;
// columns: ColumnProps<T>[];
// components: TableComponents;
data () {
// this.columns = props.columns || normalizeColumns(props.children)
this.createComponents(this.components)
this.CheckboxPropsCache = {}
this.store = createStore({
selectedRowKeys: (this.rowSelection || {}).selectedRowKeys || [],
selectionDirty: false,
})
return {
...this.getDefaultSortOrder(this.columns),
//
sFilters: this.getFiltersFromColumns(),
sPagination: this.getDefaultPagination(this.$props),
}
},
watch: {
pagination (val) {
this.setState(previousState => {
const newPagination = mergeProps(
defaultPagination,
previousState.sPagination,
val,
)
newPagination.props = newPagination.props || {}
newPagination.props.current = newPagination.props.current || 1
newPagination.props.pageSize = newPagination.props.pageSize || 10
return { sPagination: val !== false ? newPagination : emptyObject }
})
},
rowSelection: {
handler: (val) => {
if (val &&
'selectedRowKeys' in val) {
this.store.setState({
selectedRowKeys: val.selectedRowKeys || [],
})
const { rowSelection } = this.props
if (rowSelection && (
val.getCheckboxProps !== rowSelection.getCheckboxProps
)) {
this.CheckboxPropsCache = {}
}
}
},
deep: true,
},
dataSource (val) {
this.store.setState({
selectionDirty: false,
})
this.CheckboxPropsCache = {}
},
columns (val) {
if (this.getSortOrderColumns(val).length > 0) {
const sortState = this.getSortStateFromColumns(val)
if (sortState.sSortColumn !== this.sSortColumn ||
sortState.sSortOrder !== this.sSortOrder) {
this.setState(sortState)
}
}
const filteredValueColumns = this.getFilteredValueColumns(val)
if (filteredValueColumns.length > 0) {
const filtersFromColumns = this.getFiltersFromColumns(val)
const newFilters = { ...this.state.filters }
Object.keys(filtersFromColumns).forEach(key => {
newFilters[key] = filtersFromColumns[key]
})
if (this.isFiltersChanged(newFilters)) {
this.setState({ filters: newFilters })
}
}
},
components (val, preVal) {
this.createComponents(val, preVal)
},
},
methods: {
getCheckboxPropsByItem (item, index) {
const { rowSelection = {}} = this
if (!rowSelection.getCheckboxProps) {
return {}
}
const key = this.getRecordKey(item, index)
// Cache checkboxProps
if (!this.CheckboxPropsCache[key]) {
this.CheckboxPropsCache[key] = rowSelection.getCheckboxProps(item)
}
return this.CheckboxPropsCache[key]
},
getDefaultSelection () {
const { rowSelection = {}} = this
if (!rowSelection.getCheckboxProps) {
return []
}
return this.getFlatData()
.filter((item, rowIndex) => this.getCheckboxPropsByItem(item, rowIndex).defaultChecked)
.map((record, rowIndex) => this.getRecordKey(record, rowIndex))
},
getDefaultPagination (props) {
const pagination = props.pagination || {}
pagination.props = pagination.props || {}
return this.hasPagination(props)
? mergeProps(
defaultPagination,
pagination,
{
props: {
current: pagination.props.defaultCurrent || pagination.props.current || 1,
pageSize: pagination.props.defaultPageSize || pagination.props.pageSize || 10,
},
}) : {}
},
onRow (record, index) {
const { prefixCls, customRow } = this
const custom = customRow ? customRow(record, index) : {}
return mergeProps(custom, {
props: {
prefixCls,
store: this.store,
rowKey: this.getRecordKey(record, index),
},
})
},
setSelectedRowKeys (selectedRowKeys, { selectWay, record, checked, changeRowKeys }) {
const { rowSelection = {}, $listeners: {
rowSelectionChange,
rowSelectionSelect,
rowSelectionSelectAll,
rowSelectionSelectInvert,
}} = this
if (rowSelection && !('selectedRowKeys' in rowSelection)) {
this.store.setState({ selectedRowKeys })
}
const data = this.getFlatData()
if (!rowSelectionChange && !rowSelection[selectWay]) {
return
}
const selectedRows = data.filter(
(row, i) => selectedRowKeys.indexOf(this.getRecordKey(row, i)) >= 0,
)
this.$emit('rowSelectionChange', selectedRowKeys, selectedRows)
if (selectWay === 'onSelect' && rowSelectionSelect) {
this.$emit('rowSelectionSelect', record, checked, selectedRows)
} else if (selectWay === 'onSelectAll' && rowSelectionSelectAll) {
const changeRows = data.filter(
(row, i) => changeRowKeys.indexOf(this.getRecordKey(row, i)) >= 0,
)
this.$emit('rowSelectionSelectAll', checked, selectedRows, changeRows)
} else if (selectWay === 'onSelectInvert' && rowSelectionSelectInvert) {
this.$emit('rowSelectionSelectInvert', selectedRowKeys)
}
},
hasPagination () {
return this.pagination !== false
},
isFiltersChanged (filters) {
let filtersChanged = false
if (Object.keys(filters).length !== Object.keys(this.sFilters).length) {
filtersChanged = true
} else {
Object.keys(filters).forEach(columnKey => {
if (filters[columnKey] !== this.sFilters[columnKey]) {
filtersChanged = true
}
})
}
return filtersChanged
},
getSortOrderColumns (columns) {
return flatFilter(
columns || this.columns || [],
(column) => 'sortOrder' in column,
)
},
getFilteredValueColumns (columns) {
return flatFilter(
columns || this.columns || [],
(column) => typeof column.filteredValue !== 'undefined',
)
},
getFiltersFromColumns (columns) {
const filters = {}
this.getFilteredValueColumns(columns).forEach((col) => {
const colKey = this.getColumnKey(col)
filters[colKey] = col.filteredValue
})
return filters
},
getDefaultSortOrder (columns) {
const definedSortState = this.getSortStateFromColumns(columns)
const defaultSortedColumn = flatFilter(columns || [], (column) => column.defaultSortOrder != null)[0]
if (defaultSortedColumn && !definedSortState.sortColumn) {
return {
sSortColumn: defaultSortedColumn,
sSortOrder: defaultSortedColumn.defaultSortOrder,
}
}
return definedSortState
},
getSortStateFromColumns (columns) {
// return first column which sortOrder is not falsy
const sortedColumn = this.getSortOrderColumns(columns).filter((col) => col.sortOrder)[0]
if (sortedColumn) {
return {
sSortColumn: sortedColumn,
sSortOrder: sortedColumn.sortOrder,
}
}
return {
sSortColumn: null,
sSortOrder: null,
}
},
getSorterFn () {
const { sSortOrder: sortOrder, sSortColumn: sortColumn } = this
if (!sortOrder || !sortColumn ||
typeof sortColumn.sorter !== 'function') {
return
}
return (a, b) => {
const result = sortColumn.sorter(a, b)
if (result !== 0) {
return (sortOrder === 'descend') ? -result : result
}
return 0
}
},
toggleSortOrder (order, column) {
let { sSortOrder: sortOrder, sSortColumn: sortColumn } = this
//
const isSortColumn = this.isSortColumn(column)
if (!isSortColumn) { //
sortOrder = order
sortColumn = column
} else { //
if (sortOrder === order) { //
sortOrder = ''
sortColumn = null
} else { //
sortOrder = order
}
}
const newState = {
sSortOrder: sortOrder,
sSortColumn: sortColumn,
}
// Controlled
if (this.getSortOrderColumns().length === 0) {
this.setState(newState)
}
this.$emit('change', this.prepareParamsArguments({
...this.$data,
...newState,
}))
},
handleFilter (column, nextFilters) {
const props = this.$props
const pagination = { ...this.sPagination }
const filters = {
...this.sFilters,
[this.getColumnKey(column)]: nextFilters,
}
// Remove filters not in current columns
const currentColumnKeys = []
treeMap(this.columns, c => {
if (!c.children) {
currentColumnKeys.push(this.getColumnKey(c))
}
})
Object.keys(filters).forEach((columnKey) => {
if (currentColumnKeys.indexOf(columnKey) < 0) {
delete filters[columnKey]
}
})
if (props.pagination) {
// Reset current prop
pagination.props = pagination.props || {}
pagination.props.current = 1
pagination.on.change(pagination.current)
}
const newState = {
sPagination: pagination,
sFilters: {},
}
const filtersToSetState = { ...filters }
// Remove filters which is controlled
this.getFilteredValueColumns().forEach((col) => {
const columnKey = this.getColumnKey(col)
if (columnKey) {
delete filtersToSetState[columnKey]
}
})
if (Object.keys(filtersToSetState).length > 0) {
newState.sFilters = filtersToSetState
}
// Controlled current prop will not respond user interaction
if (typeof props.pagination === 'object' && props.pagination.props && 'current' in (props.pagination.props)) {
newState.sPagination = mergeProps(pagination, {
props: {
current: this.sPagination.props.current,
},
})
}
this.setState(newState, () => {
this.store.setState({
selectionDirty: false,
})
this.$emit('change', this.prepareParamsArguments({
...this.$data,
selectionDirty: false,
filters,
pagination,
}))
})
},
handleSelect (record, rowIndex, e) {
const checked = e.target.checked
const defaultSelection = this.store.getState().selectionDirty ? [] : this.getDefaultSelection()
let selectedRowKeys = this.store.getState().selectedRowKeys.concat(defaultSelection)
const key = this.getRecordKey(record, rowIndex)
if (checked) {
selectedRowKeys.push(this.getRecordKey(record, rowIndex))
} else {
selectedRowKeys = selectedRowKeys.filter((i) => key !== i)
}
this.store.setState({
selectionDirty: true,
})
this.setSelectedRowKeys(selectedRowKeys, {
selectWay: 'onSelect',
record,
checked,
})
},
handleRadioSelect (record, rowIndex, e) {
const checked = e.target.checked
const defaultSelection = this.store.getState().selectionDirty ? [] : this.getDefaultSelection()
let selectedRowKeys = this.store.getState().selectedRowKeys.concat(defaultSelection)
const key = this.getRecordKey(record, rowIndex)
selectedRowKeys = [key]
this.store.setState({
selectionDirty: true,
})
this.setSelectedRowKeys(selectedRowKeys, {
selectWay: 'onSelect',
record,
checked,
})
},
handleSelectRow (selectionKey, index, onSelectFunc) {
const data = this.getFlatCurrentPageData()
const defaultSelection = this.store.getState().selectionDirty ? [] : this.getDefaultSelection()
const selectedRowKeys = this.store.getState().selectedRowKeys.concat(defaultSelection)
const changeableRowKeys = data
.filter((item, i) => !this.getCheckboxPropsByItem(item, i).disabled)
.map((item, i) => this.getRecordKey(item, i))
const changeRowKeys = []
let selectWay = ''
let checked
// handle default selection
switch (selectionKey) {
case 'all':
changeableRowKeys.forEach(key => {
if (selectedRowKeys.indexOf(key) < 0) {
selectedRowKeys.push(key)
changeRowKeys.push(key)
}
})
selectWay = 'onSelectAll'
checked = true
break
case 'removeAll':
changeableRowKeys.forEach(key => {
if (selectedRowKeys.indexOf(key) >= 0) {
selectedRowKeys.splice(selectedRowKeys.indexOf(key), 1)
changeRowKeys.push(key)
}
})
selectWay = 'onSelectAll'
checked = false
break
case 'invert':
changeableRowKeys.forEach(key => {
if (selectedRowKeys.indexOf(key) < 0) {
selectedRowKeys.push(key)
} else {
selectedRowKeys.splice(selectedRowKeys.indexOf(key), 1)
}
changeRowKeys.push(key)
selectWay = 'onSelectInvert'
})
break
default:
break
}
this.store.setState({
selectionDirty: true,
})
// when select custom selection, callback selections[n].onSelect
const { rowSelection } = this
let customSelectionStartIndex = 2
if (rowSelection && rowSelection.hideDefaultSelections) {
customSelectionStartIndex = 0
}
if (index >= customSelectionStartIndex && typeof onSelectFunc === 'function') {
return onSelectFunc(changeableRowKeys)
}
this.setSelectedRowKeys(selectedRowKeys, {
selectWay: selectWay,
checked,
changeRowKeys,
})
},
handlePageChange (current, ...otherArguments) {
const props = this.$props
const pagination = { ...this.sPagination }
if (current) {
pagination.props.current = current
} else {
pagination.props.current = pagination.props.current || 1
}
pagination.on.change(pagination.props.current, ...otherArguments)
const newState = {
sPagination: pagination,
}
// Controlled current prop will not respond user interaction
if (props.pagination &&
typeof props.pagination === 'object' &&
props.pagination.props &&
'current' in (props.pagination.props)) {
newState.sPagination = mergeProps(pagination, {
props: {
current: this.sPagination.props.current,
},
})
}
this.setState(newState)
this.store.setState({
selectionDirty: false,
})
this.$emit('change', this.prepareParamsArguments({
...this.$data,
selectionDirty: false,
pagination,
}))
},
renderSelectionBox (type) {
return (_, record, index) => {
const rowIndex = this.getRecordKey(record, index) // 1
const props = this.getCheckboxPropsByItem(record, index)
const handleChange = (e) => {
type === 'radio' ? this.handleRadioSelect(record, rowIndex, e)
: this.handleSelect(record, rowIndex, e)
}
const selectionBoxProps = mergeProps({
props: {
type,
store: this.store,
rowIndex,
defaultSelection: this.getDefaultSelection(),
},
on: {
change: handleChange,
},
}, props)
return (
<span onClick={stopPropagation}>
<SelectionBox
{...selectionBoxProps}
/>
</span>
)
}
},
getRecordKey (record, index) {
const rowKey = this.rowKey
const recordKey = (typeof rowKey === 'function')
? rowKey(record, index) : record[rowKey]
warning(recordKey !== undefined,
'Each record in dataSource of table should have a unique `key` prop, or set `rowKey` to an unique primary key,',
)
return recordKey === undefined ? index : recordKey
},
getPopupContainer () {
return this.$el
},
renderRowSelection (locale) {
const { prefixCls, rowSelection } = this
const columns = this.columns.concat()
if (rowSelection) {
const data = this.getFlatCurrentPageData().filter((item, index) => {
if (rowSelection.getCheckboxProps) {
return !this.getCheckboxPropsByItem(item, index).disabled
}
return true
})
const selectionColumnClass = classNames(`${prefixCls}-selection-column`, {
[`${prefixCls}-selection-column-custom`]: rowSelection.selections,
})
const selectionColumn = {
key: 'selection-column',
render: this.renderSelectionBox(rowSelection.type),
className: selectionColumnClass,
fixed: rowSelection.fixed,
}
if (rowSelection.type !== 'radio') {
const checkboxAllDisabled = data.every((item, index) => this.getCheckboxPropsByItem(item, index).disabled)
selectionColumn.title = (
<SelectionCheckboxAll
store={this.store}
locale={locale}
data={data}
getCheckboxPropsByItem={this.getCheckboxPropsByItem}
getRecordKey={this.getRecordKey}
disabled={checkboxAllDisabled}
prefixCls={prefixCls}
onSelect={this.handleSelectRow}
selections={rowSelection.selections}
hideDefaultSelections={rowSelection.hideDefaultSelections}
getPopupContainer={this.getPopupContainer}
/>
)
}
if ('fixed' in rowSelection) {
selectionColumn.fixed = rowSelection.fixed
} else if (columns.some(column => column.fixed === 'left' || column.fixed === true)) {
selectionColumn.fixed = 'left'
}
if (columns[0] && columns[0].key === 'selection-column') {
columns[0] = selectionColumn
} else {
columns.unshift(selectionColumn)
}
}
return columns
},
getColumnKey (column, index) {
return column.key || column.dataIndex || index
},
getMaxCurrent (total) {
const { current, pageSize } = this.sPagination.props
if ((current - 1) * pageSize >= total) {
return Math.floor((total - 1) / pageSize) + 1
}
return current
},
isSortColumn (column) {
const { sSortColumn: sortColumn } = this
if (!column || !sortColumn) {
return false
}
return this.getColumnKey(sortColumn) === this.getColumnKey(column)
},
renderColumnsDropdown (columns, locale) {
const { prefixCls, dropdownPrefixCls } = this
const { sSortOrder: sortOrder } = this
return treeMap(columns, (originColumn, i) => {
const column = { ...originColumn }
const key = this.getColumnKey(column, i)
let filterDropdown
let sortButton
if ((column.filters && column.filters.length > 0) || column.filterDropdown) {
const colFilters = this.sFilters[key] || []
filterDropdown = (
<FilterDropdown
locale={locale}
column={column}
selectedKeys={colFilters}
confirmFilter={this.handleFilter}
prefixCls={`${prefixCls}-filter`}
dropdownPrefixCls={dropdownPrefixCls || 'ant-dropdown'}
getPopupContainer={this.getPopupContainer}
/>
)
}
if (column.sorter) {
const isSortColumn = this.isSortColumn(column)
if (isSortColumn) {
column.className = classNames(column.className, {
[`${prefixCls}-column-sort`]: sortOrder,
})
}
const isAscend = isSortColumn && sortOrder === 'ascend'
const isDescend = isSortColumn && sortOrder === 'descend'
sortButton = (
<div class={`${prefixCls}-column-sorter`}>
<span
class={`${prefixCls}-column-sorter-up ${isAscend ? 'on' : 'off'}`}
title='↑'
onClick={() => this.toggleSortOrder('ascend', column)}
>
<Icon type='caret-up' />
</span>
<span
class={`${prefixCls}-column-sorter-down ${isDescend ? 'on' : 'off'}`}
title='↓'
onClick={() => this.toggleSortOrder('descend', column)}
>
<Icon type='caret-down' />
</span>
</div>
)
}
column.title = (
<span>
{column.title}
{sortButton}
{filterDropdown}
</span>
)
if (sortButton || filterDropdown) {
column.className = classNames(`${prefixCls}-column-has-filters`, column.className)
}
return column
})
},
handleShowSizeChange (current, pageSize) {
const pagination = this.sPagination
pagination.on.showSizeChange(current, pageSize)
const nextPagination = mergeProps(pagination, {
props: {
pageSize,
current,
},
})
this.setState({ sPagination: nextPagination })
this.$emit('change', this.prepareParamsArguments({
...this.$data,
pagination: nextPagination,
}))
},
renderPagination () {
//
if (!this.hasPagination()) {
return null
}
let size = 'default'
const { sPagination: pagination } = this
if (pagination.props && pagination.props.size) {
size = pagination.props.size
} else if (this.size === 'middle' || this.size === 'small') {
size = 'small'
}
const total = (pagination.props && pagination.props.total) || this.getLocalData().length
const { class: cls, style, on, props } = pagination
const paginationProps = mergeProps({
key: 'pagination',
class: classNames(cls, `${this.prefixCls}-pagination`),
props: {
...props,
total,
size,
current: this.getMaxCurrent(total),
},
style,
on: {
...on,
change: this.handlePageChange,
showSizeChange: this.handleShowSizeChange,
},
})
return (total > 0) ? (
<Pagination
{...paginationProps}
/>
) : null
},
// Get pagination, filters, sorter
prepareParamsArguments (state) {
const pagination = cloneDeep(state.sPagination)
// remove useless handle function in Table.onChange
if (pagination.on) {
delete pagination.on.change
delete pagination.on.showSizeChange
}
const filters = state.sFilters
const sorter = {}
if (state.sSortColumn && state.sSortOrder) {
sorter.column = state.sSortColumn
sorter.order = state.sSortOrder
sorter.field = state.sSortColumn.dataIndex
sorter.columnKey = this.getColumnKey(state.sSortColumn)
}
return [pagination, filters, sorter]
},
findColumn (myKey) {
let column
treeMap(this.columns, c => {
if (this.getColumnKey(c) === myKey) {
column = c
}
})
return column
},
getCurrentPageData () {
let data = this.getLocalData()
let current
let pageSize
const pagProps = this.sPagination.props || {}
//
if (!this.hasPagination()) {
pageSize = Number.MAX_VALUE
current = 1
} else {
pageSize = pagProps.pageSize
current = this.getMaxCurrent(pagProps.total || data.length)
}
//
// ---
//
//
if (data.length > pageSize || pageSize === Number.MAX_VALUE) {
data = data.filter((_, i) => {
return i >= (current - 1) * pageSize && i < current * pageSize
})
}
return data
},
getFlatData () {
return flatArray(this.getLocalData())
},
getFlatCurrentPageData () {
return flatArray(this.getCurrentPageData())
},
recursiveSort (data, sorterFn) {
const { childrenColumnName = 'children' } = this
return data.sort(sorterFn).map((item) => (item[childrenColumnName] ? {
...item,
[childrenColumnName]: this.recursiveSort(item[childrenColumnName], sorterFn),
} : item))
},
getLocalData () {
const { dataSource, sFilters: filters } = this
let data = dataSource || []
//
data = data.slice(0)
const sorterFn = this.getSorterFn()
if (sorterFn) {
data = this.recursiveSort(data, sorterFn)
}
//
if (filters) {
Object.keys(filters).forEach((columnKey) => {
const col = this.findColumn(columnKey)
if (!col) {
return
}
const values = filters[columnKey] || []
if (values.length === 0) {
return
}
const onFilter = col.onFilter
data = onFilter ? data.filter(record => {
return values.some(v => onFilter(v, record))
}) : data
})
}
return data
},
createComponents (components = {}, prevComponents) {
const bodyRow = components && components.body && components.body.row
const preBodyRow = prevComponents && prevComponents.body && prevComponents.body.row
if (!this.customComponents || bodyRow !== preBodyRow) {
this.customComponents = { ...components }
this.customComponents.body = {
...components.body,
row: createBodyRow(bodyRow),
}
}
},
renderTable (contextLocale, loading) {
const locale = { ...contextLocale, ...this.locale }
const { prefixCls, showHeader, ...restProps } = getOptionProps(this)
const data = this.getCurrentPageData()
const expandIconAsCell = this.expandedRowRender && this.expandIconAsCell !== false
const classString = classNames({
[`${prefixCls}-${this.size}`]: true,
[`${prefixCls}-bordered`]: this.bordered,
[`${prefixCls}-empty`]: !data.length,
[`${prefixCls}-without-column-header`]: !showHeader,
})
let columns = this.renderRowSelection(locale)
columns = this.renderColumnsDropdown(columns, locale)
columns = columns.map((column, i) => {
const newColumn = { ...column }
newColumn.key = this.getColumnKey(newColumn, i)
return newColumn
})
let expandIconColumnIndex = (columns[0] && columns[0].key === 'selection-column') ? 1 : 0
if ('expandIconColumnIndex' in restProps) {
expandIconColumnIndex = restProps.expandIconColumnIndex
}
const vcTableProps = {
key: 'table',
props: {
...restProps,
customRow: this.onRow,
components: this.customComponents,
prefixCls,
data,
columns,
showHeader,
expandIconColumnIndex,
expandIconAsCell,
emptyText: !(loading.props && loading.spinning) && locale.emptyText,
},
on: this.$listeners,
class: classString,
}
return (
<VcTable
{...vcTableProps}
/>
)
},
},
render () {
const { prefixCls } = this
const data = this.getCurrentPageData()
let loading = this.loading
if (typeof loading === 'boolean') {
loading = {
props: {
spinning: loading,
},
}
}
const table = (
<LocaleReceiver
componentName='Table'
defaultLocale={defaultLocale.Table}
children={(locale) => this.renderTable(locale, loading)}
/>
)
// if there is no pagination or no data,
// the height of spin should decrease by half of pagination
const paginationPatchClass = (this.hasPagination() && data && data.length !== 0)
? `${prefixCls}-with-pagination` : `${prefixCls}-without-pagination`
const spinProps = {
...loading,
class: loading.props && loading.props.spinning ? `${paginationPatchClass} ${prefixCls}-spin-holder` : '',
}
return (
<div
class={classNames(`${prefixCls}-wrapper`)}
>
<Spin
{...spinProps}
>
{table}
{this.renderPagination()}
</Spin>
</div>
)
},
}

File diff suppressed because it is too large Load Diff

View File

@ -44,7 +44,7 @@ export default function createTableRow (Component = 'tr') {
render () {
const className = {
[`${this.props.prefixCls}-row-selected`]: this.selected,
[`${this.prefixCls}-row-selected`]: this.selected,
}
return (

View File

@ -0,0 +1,59 @@
<cn>
#### 基本用法
简单的表格,最后一列是各种操作。
</cn>
<us>
#### basic Usage
Simple table with actions.
</us>
```html
<template>
<a-table :columns="columns" :dataSource="data" />
</template>
<script>
const columns = [{
title: 'Name',
dataIndex: 'name',
key: 'name',
}, {
title: 'Age',
dataIndex: 'age',
key: 'age',
}, {
title: 'Address',
dataIndex: 'address',
key: 'address',
}, {
title: 'Action',
key: 'action',
}];
const data = [{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
}, {
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
}, {
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
}];
export default {
data() {
return {
data,
columns,
}
}
}
</script>
```

View File

@ -0,0 +1,230 @@
import Menu, { SubMenu, Item as MenuItem } from '../vc-menu'
import closest from 'dom-closest'
import classNames from 'classnames'
import Dropdown from '../dropdown'
import Icon from '../icon'
import Checkbox from '../checkbox'
import Radio from '../radio'
import FilterDropdownMenuWrapper from './FilterDropdownMenuWrapper'
import { FilterMenuProps } from './interface'
import { initDefaultProps } from '../_util/props-util'
import { cloneElement } from '../_util/vnode'
import BaseMixin from '../_util/BaseMixin'
export default {
mixins: [BaseMixin],
name: 'FilterMenu',
props: initDefaultProps(FilterMenuProps, {
handleFilter () {},
column: {},
}),
data () {
const visible = ('filterDropdownVisible' in this.column)
? this.column.filterDropdownVisible : false
return {
sSelectedKeys: this.selectedKeys,
sKeyPathOfSelectedItem: {}, //
sVisible: visible,
}
},
mounted () {
const { column } = this
this.$nextTick(() => {
this.setNeverShown(column)
})
},
componentWillReceiveProps (nextProps) {
const { column } = nextProps
this.setNeverShown(column)
const newState = {}
if ('selectedKeys' in nextProps) {
newState.selectedKeys = nextProps.selectedKeys
}
if ('filterDropdownVisible' in column) {
newState.visible = column.filterDropdownVisible
}
if (Object.keys(newState).length > 0) {
this.setState(newState)
}
},
methods: {
},
setNeverShown (column) {
const rootNode = this.$el
const filterBelongToScrollBody = !!closest(rootNode, `.ant-table-scroll`)
if (filterBelongToScrollBody) {
// When fixed column have filters, there will be two dropdown menus
// Filter dropdown menu inside scroll body should never be shown
// To fix https://github.com/ant-design/ant-design/issues/5010 and
// https://github.com/ant-design/ant-design/issues/7909
this.neverShown = !!column.fixed
}
},
setSelectedKeys ({ selectedKeys }) {
this.setState({ sSelectedKeys: selectedKeys })
},
setVisible (visible) {
const { column } = this
if (!('filterDropdownVisible' in column)) {
this.setState({ sVisible: visible })
}
if (column.onFilterDropdownVisibleChange) {
column.onFilterDropdownVisibleChange(visible)
}
},
handleClearFilters () {
this.setState({
sSelectedKeys: [],
}, this.handleConfirm)
},
handleConfirm () {
this.setVisible(false)
this.confirmFilter()
},
onVisibleChange (visible) {
this.setVisible(visible)
if (!visible) {
this.confirmFilter()
}
},
confirmFilter () {
if (this.sSelectedKeys !== this.selectedKeys) {
this.confirmFilter(this.column, this.sSelectedKeys)
}
},
renderMenuItem (item) {
const { column } = this
const multiple = ('filterMultiple' in column) ? column.filterMultiple : true
const input = multiple ? (
<Checkbox checked={this.sSelectedKeys.indexOf(item.value.toString()) >= 0} />
) : (
<Radio checked={this.sSelectedKeys.indexOf(item.value.toString()) >= 0} />
)
return (
<MenuItem key={item.value}>
{input}
<span>{item.text}</span>
</MenuItem>
)
},
hasSubMenu () {
const { column: { filters = [] }} = this
return filters.some(item => !!(item.children && item.children.length > 0))
},
renderMenus (items) {
return items.map(item => {
if (item.children && item.children.length > 0) {
const { sKeyPathOfSelectedItem } = this
const containSelected = Object.keys(sKeyPathOfSelectedItem).some(
key => sKeyPathOfSelectedItem[key].indexOf(item.value) >= 0,
)
const subMenuCls = containSelected ? `${this.dropdownPrefixCls}-submenu-contain-selected` : ''
return (
<SubMenu title={item.text} class={subMenuCls} key={item.value.toString()}>
{this.renderMenus(item.children)}
</SubMenu>
)
}
return this.renderMenuItem(item)
})
},
handleMenuItemClick (info) {
if (info.keyPath.length <= 1) {
return
}
const keyPathOfSelectedItem = this.sKeyPathOfSelectedItem
if (this.sSelectedKeys.indexOf(info.key) >= 0) {
// deselect SubMenu child
delete keyPathOfSelectedItem[info.key]
} else {
// select SubMenu child
keyPathOfSelectedItem[info.key] = info.keyPath
}
this.setState({ keyPathOfSelectedItem })
},
renderFilterIcon () {
const { column, locale, prefixCls } = this
const filterIcon = column.filterIcon
const dropdownSelectedClass = this.selectedKeys.length > 0 ? `${prefixCls}-selected` : ''
return filterIcon ? cloneElement(filterIcon, {
title: locale.filterTitle,
className: classNames(filterIcon.className, {
[`${prefixCls}-icon`]: true,
}),
}) : <Icon title={locale.filterTitle} type='filter' class={dropdownSelectedClass} />
},
render () {
const { column, locale, prefixCls, dropdownPrefixCls, getPopupContainer } = this
// default multiple selection in filter dropdown
const multiple = ('filterMultiple' in column) ? column.filterMultiple : true
const dropdownMenuClass = classNames({
[`${dropdownPrefixCls}-menu-without-submenu`]: !this.hasSubMenu(),
})
const menus = column.filterDropdown ? (
<FilterDropdownMenuWrapper>
{column.filterDropdown}
</FilterDropdownMenuWrapper>
) : (
<FilterDropdownMenuWrapper class={`${prefixCls}-dropdown`}>
<Menu
multiple={multiple}
onClick={this.handleMenuItemClick}
prefixCls={`${dropdownPrefixCls}-menu`}
class={dropdownMenuClass}
onSelect={this.setSelectedKeys}
onDeselect={this.setSelectedKeys}
selectedKeys={this.sSelectedKeys}
>
{this.renderMenus(column.filters)}
</Menu>
<div class={`${prefixCls}-dropdown-btns`}>
<a
class={`${prefixCls}-dropdown-link confirm`}
onClick={this.handleConfirm}
>
{locale.filterConfirm}
</a>
<a
class={`${prefixCls}-dropdown-link clear`}
onClick={this.handleClearFilters}
>
{locale.filterReset}
</a>
</div>
</FilterDropdownMenuWrapper>
)
return (
<Dropdown
trigger={['click']}
visible={this.neverShown ? false : this.sVisible}
onVisibleChange={this.onVisibleChange}
getPopupContainer={getPopupContainer}
forceRender
>
<template slot='overlay'>{menus}</template>
{this.renderFilterIcon()}
</Dropdown>
)
},
}

View File

@ -1,228 +0,0 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import Menu, { SubMenu, Item as MenuItem } from 'rc-menu';
import closest from 'dom-closest';
import classNames from 'classnames';
import Dropdown from '../dropdown';
import Icon from '../icon';
import Checkbox from '../checkbox';
import Radio from '../radio';
import FilterDropdownMenuWrapper from './FilterDropdownMenuWrapper';
import { FilterMenuProps, FilterMenuState, ColumnProps, ColumnFilterItem } from './interface';
export default class FilterMenu<T> extends React.Component<FilterMenuProps<T>, FilterMenuState> {
static defaultProps = {
handleFilter() {},
column: {},
};
neverShown: boolean;
constructor(props: FilterMenuProps<T>) {
super(props);
const visible = ('filterDropdownVisible' in props.column) ?
props.column.filterDropdownVisible : false;
this.state = {
selectedKeys: props.selectedKeys,
keyPathOfSelectedItem: {}, // 记录所有有选中子菜单的祖先菜单
visible,
};
}
componentDidMount() {
const { column } = this.props;
this.setNeverShown(column);
}
componentWillReceiveProps(nextProps: FilterMenuProps<T>) {
const { column } = nextProps;
this.setNeverShown(column);
const newState = {} as {
selectedKeys: string[];
visible: boolean;
};
if ('selectedKeys' in nextProps) {
newState.selectedKeys = nextProps.selectedKeys;
}
if ('filterDropdownVisible' in column) {
newState.visible = column.filterDropdownVisible as boolean;
}
if (Object.keys(newState).length > 0) {
this.setState(newState);
}
}
setNeverShown = (column: ColumnProps<T>) => {
const rootNode = ReactDOM.findDOMNode(this);
const filterBelongToScrollBody = !!closest(rootNode, `.ant-table-scroll`);
if (filterBelongToScrollBody) {
// When fixed column have filters, there will be two dropdown menus
// Filter dropdown menu inside scroll body should never be shown
// To fix https://github.com/ant-design/ant-design/issues/5010 and
// https://github.com/ant-design/ant-design/issues/7909
this.neverShown = !!column.fixed;
}
}
setSelectedKeys = ({ selectedKeys }: { selectedKeys: string[] }) => {
this.setState({ selectedKeys });
}
setVisible(visible: boolean) {
const { column } = this.props;
if (!('filterDropdownVisible' in column)) {
this.setState({ visible });
}
if (column.onFilterDropdownVisibleChange) {
column.onFilterDropdownVisibleChange(visible);
}
}
handleClearFilters = () => {
this.setState({
selectedKeys: [],
}, this.handleConfirm);
}
handleConfirm = () => {
this.setVisible(false);
this.confirmFilter();
}
onVisibleChange = (visible: boolean) => {
this.setVisible(visible);
if (!visible) {
this.confirmFilter();
}
}
confirmFilter() {
if (this.state.selectedKeys !== this.props.selectedKeys) {
this.props.confirmFilter(this.props.column, this.state.selectedKeys);
}
}
renderMenuItem(item: ColumnFilterItem) {
const { column } = this.props;
const multiple = ('filterMultiple' in column) ? column.filterMultiple : true;
const input = multiple ? (
<Checkbox checked={this.state.selectedKeys.indexOf(item.value.toString()) >= 0} />
) : (
<Radio checked={this.state.selectedKeys.indexOf(item.value.toString()) >= 0} />
);
return (
<MenuItem key={item.value}>
{input}
<span>{item.text}</span>
</MenuItem>
);
}
hasSubMenu() {
const { column: { filters = [] } } = this.props;
return filters.some(item => !!(item.children && item.children.length > 0));
}
renderMenus(items: ColumnFilterItem[]): React.ReactElement<any>[] {
return items.map(item => {
if (item.children && item.children.length > 0) {
const { keyPathOfSelectedItem } = this.state;
const containSelected = Object.keys(keyPathOfSelectedItem).some(
key => keyPathOfSelectedItem[key].indexOf(item.value) >= 0,
);
const subMenuCls = containSelected ? `${this.props.dropdownPrefixCls}-submenu-contain-selected` : '';
return (
<SubMenu title={item.text} className={subMenuCls} key={item.value.toString()}>
{this.renderMenus(item.children)}
</SubMenu>
);
}
return this.renderMenuItem(item);
});
}
handleMenuItemClick = (info: { keyPath: string, key: string }) => {
if (info.keyPath.length <= 1) {
return;
}
const keyPathOfSelectedItem = this.state.keyPathOfSelectedItem;
if (this.state.selectedKeys.indexOf(info.key) >= 0) {
// deselect SubMenu child
delete keyPathOfSelectedItem[info.key];
} else {
// select SubMenu child
keyPathOfSelectedItem[info.key] = info.keyPath;
}
this.setState({ keyPathOfSelectedItem });
}
renderFilterIcon = () => {
const { column, locale, prefixCls } = this.props;
const filterIcon = column.filterIcon as any;
const dropdownSelectedClass = this.props.selectedKeys.length > 0 ? `${prefixCls}-selected` : '';
return filterIcon ? React.cloneElement(filterIcon as any, {
title: locale.filterTitle,
className: classNames(filterIcon.className, {
[`${prefixCls}-icon`]: true,
}),
}) : <Icon title={locale.filterTitle} type="filter" className={dropdownSelectedClass} />;
}
render() {
const { column, locale, prefixCls, dropdownPrefixCls, getPopupContainer } = this.props;
// default multiple selection in filter dropdown
const multiple = ('filterMultiple' in column) ? column.filterMultiple : true;
const dropdownMenuClass = classNames({
[`${dropdownPrefixCls}-menu-without-submenu`]: !this.hasSubMenu(),
});
const menus = column.filterDropdown ? (
<FilterDropdownMenuWrapper>
{column.filterDropdown}
</FilterDropdownMenuWrapper>
) : (
<FilterDropdownMenuWrapper className={`${prefixCls}-dropdown`}>
<Menu
multiple={multiple}
onClick={this.handleMenuItemClick}
prefixCls={`${dropdownPrefixCls}-menu`}
className={dropdownMenuClass}
onSelect={this.setSelectedKeys}
onDeselect={this.setSelectedKeys}
selectedKeys={this.state.selectedKeys}
>
{this.renderMenus(column.filters!)}
</Menu>
<div className={`${prefixCls}-dropdown-btns`}>
<a
className={`${prefixCls}-dropdown-link confirm`}
onClick={this.handleConfirm}
>
{locale.filterConfirm}
</a>
<a
className={`${prefixCls}-dropdown-link clear`}
onClick={this.handleClearFilters}
>
{locale.filterReset}
</a>
</div>
</FilterDropdownMenuWrapper>
);
return (
<Dropdown
trigger={['click']}
overlay={menus}
visible={this.neverShown ? false : this.state.visible}
onVisibleChange={this.onVisibleChange}
getPopupContainer={getPopupContainer}
forceRender
>
{this.renderFilterIcon()}
</Dropdown>
);
}
}

View File

@ -28,7 +28,7 @@ export const ColumnProps = {
defaultSortOrder: PropTypes.oneOf(['ascend', 'descend']),
colSpan: PropTypes.number,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
// className: string,
className: PropTypes.string,
fixed: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['left', 'right'])]),
filterIcon: PropTypes.any,
filteredValue: PropTypes.array,
@ -81,9 +81,9 @@ export const TableRowSelection = {
export const TableProps = {
prefixCls: PropTypes.string,
dropdownPrefixCls: PropTypes.string,
rowSelection: PropTypes.shape(TableRowSelection).loose,
rowSelection: PropTypes.oneOfType([PropTypes.shape(TableRowSelection).loose, null]),
pagination: PropTypes.oneOfType([PropTypes.shape(PaginationProps).loose, PropTypes.bool]),
size: PropTypes.oneOf(['default', 'middle', 'small']),
size: PropTypes.oneOf(['default', 'middle', 'small', 'large']),
dataSource: PropTypes.array,
components: PropTypes.object,
columns: PropTypes.array,
@ -103,7 +103,8 @@ export const TableProps = {
locale: PropTypes.object,
indentSize: PropTypes.number,
// onRowClick?: (record: T, index: number, event: Event) => any;
// onRow?: (record: T, index: number) => any;
customRow: PropTypes.func,
customHeaderRow: PropTypes.func,
useFixedHeader: PropTypes.bool,
bordered: PropTypes.bool,
showHeader: PropTypes.bool,
@ -177,6 +178,7 @@ export const FilterMenuProps = {
prefixCls: PropTypes.string,
dropdownPrefixCls: PropTypes.string,
getPopupContainer: PropTypes.func,
handleFilter: PropTypes.func,
}
// export interface FilterMenuState {

View File

@ -59,7 +59,7 @@ export default {
render () {
const columns = this.columns.map((col, index) => ({
...col,
onHeaderCell: (column) => ({
customHeaderCell: (column) => ({
width: column.width,
onResize: this.handleResize(index),
}),

View File

@ -10,8 +10,8 @@ const onRowClick = (record, index, event) => {
}
}
const onRowDoubleClick = (record, index) => {
console.log(`Double click nth(${index}) row of parent, record.name: ${record.name}`)
const onRowDoubleClick = (record, index, e) => {
console.log(`Double click nth(${index}) row of parent, record.name: ${record.name}`, e)
}
const columns = [{
@ -98,7 +98,7 @@ export default {
<Table
columns={columns}
data={data}
onRow={(record, index) => ({
customRow={(record, index) => ({
on: {
click: onRowClick.bind(null, record, index),
dblclick: onRowDoubleClick.bind(null, record, index),

View File

@ -46,8 +46,8 @@ const BaseTable = {
rowContextmenu: onRowContextMenu = noop,
rowMouseenter: onRowMouseEnter = noop,
rowMouseleave: onRowMouseLeave = noop,
row: onRow = noop,
},
customRow = noop,
} = this.table
const { getRowKey, fixed, expander, isAnyColumnsFixed } = this
@ -106,9 +106,9 @@ const BaseTable = {
ancestorKeys,
components,
isAnyColumnsFixed,
customRow,
},
on: {
row: onRow,
rowDoubleclick: onRowDoubleClick,
rowContextmenu: onRowContextMenu,
rowMouseenter: onRowMouseEnter,

View File

@ -16,8 +16,9 @@ export default {
'right',
]),
render: PropTypes.func,
className: PropTypes.string,
// onCellClick: PropTypes.func,
// onCell: PropTypes.func,
// onHeaderCell: PropTypes.func,
customHeaderCell: PropTypes.func,
},
}

View File

@ -25,8 +25,8 @@ export default {
bodyStyle: PropTypes.object,
rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
// onRow: PropTypes.func,
// onHeaderRow: PropTypes.func,
customRow: PropTypes.func,
customHeaderRow: PropTypes.func,
// onRowClick: PropTypes.func,
// onRowDoubleClick: PropTypes.func,
// onRowContextMenu: PropTypes.func,
@ -74,6 +74,7 @@ export default {
scroll: {},
rowRef: () => null,
emptyText: () => 'No Data',
customHeaderRow: () => {},
}),
// static childContextTypes = {
@ -91,7 +92,7 @@ export default {
].forEach(name => {
warningOnce(
this.$listeners[name] === undefined,
`${name} is deprecated, please use onRow instead.`,
`${name} is deprecated, please use customRow instead.`,
)
})

View File

@ -41,7 +41,7 @@ export default {
component: BodyCell,
} = this
const { dataIndex, render, className = '' } = column
const cls = column.class || className
const cls = className || column.class
// We should return undefined if no dataIndex is specified, but in order to
// be compatible with object-path's behavior, we return the record object instead.
let text

View File

@ -13,7 +13,7 @@ function getHeaderRows (columns, currentRow = 0, rows) {
}
const cell = {
key: column.key,
className: column.className || '',
className: column.className || column.class || '',
children: column.title,
column,
}
@ -44,15 +44,10 @@ export default {
inject: {
table: { default: {}},
},
methods: {
onHeaderRow () {
this.table.__emit('headerRow', ...arguments)
},
},
render () {
const { sComponents: components, prefixCls, showHeader } = this.table
const { expander, columns, fixed, onHeaderRow } = this
const { sComponents: components, prefixCls, showHeader, customHeaderRow } = this.table
const { expander, columns, fixed } = this
if (!showHeader) {
return null
@ -76,7 +71,7 @@ export default {
rows={rows}
row={row}
components={components}
onHeaderRow={onHeaderRow}
customHeaderRow={customHeaderRow}
/>
))
}

View File

@ -11,14 +11,14 @@ const TableHeaderRow = {
row: PropTypes.array,
components: PropTypes.object,
height: PropTypes.any,
customHeaderRow: PropTypes.func,
},
name: 'TableHeaderRow',
render (h) {
const { row, index, height, components, $listeners = {}} = this
const onHeaderRow = $listeners.headerRow
const { row, index, height, components, customHeaderRow } = this
const HeaderRow = components.header.row
const HeaderCell = components.header.cell
const rowProps = onHeaderRow(row.map(cell => cell.column), index)
const rowProps = customHeaderRow(row.map(cell => cell.column), index)
const customStyle = rowProps ? rowProps.style : {}
const style = { height, ...customStyle }
@ -27,7 +27,7 @@ const TableHeaderRow = {
{row.map((cell, i) => {
const { column, children, className, ...cellProps } = cell
const cls = cell.class || className
const customProps = column.onHeaderCell ? column.onHeaderCell(column) : {}
const customProps = column.customHeaderCell ? column.customHeaderCell(column) : {}
if (column.align) {
cellProps.style = { textAlign: column.align }
}

View File

@ -9,7 +9,7 @@ const TableRow = {
name: 'TableRow',
mixins: [BaseMixin],
props: initDefaultProps({
// onRow: PropTypes.func,
customRow: PropTypes.func,
// onRowClick: PropTypes.func,
// onRowDoubleClick: PropTypes.func,
// onRowContextMenu: PropTypes.func,
@ -175,7 +175,7 @@ const TableRow = {
columns,
record,
index,
// onRow,
customRow = noop,
indent,
indentSize,
hovered,
@ -185,9 +185,7 @@ const TableRow = {
hasExpandIcon,
renderExpandIcon,
renderExpandIconCell,
$listeners,
} = this
const { row: onRow = noop } = $listeners
const BodyRow = components.body.row
const BodyCell = components.body.cell
@ -227,7 +225,7 @@ const TableRow = {
const rowClassName =
`${prefixCls} ${className} ${prefixCls}-level-${indent}`.trim()
const rowProps = onRow(record, index)
const rowProps = customRow(record, index)
const customStyle = rowProps ? rowProps.style : {}
let style = { height: typeof height === 'number' ? `${height}px` : height }

13
package-lock.json generated
View File

@ -3424,6 +3424,14 @@
"resolved": "https://registry.npm.taobao.org/dom-align/download/dom-align-1.6.7.tgz",
"integrity": "sha1-aFgTjvtrd0Bc6ZFG0L5eT3KCgT8="
},
"dom-closest": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/dom-closest/-/dom-closest-0.2.0.tgz",
"integrity": "sha1-69n5HRvyLo1vR3h2u80+yQIWwM8=",
"requires": {
"dom-matches": "2.0.0"
}
},
"dom-converter": {
"version": "0.1.4",
"resolved": "http://r.cnpmjs.org/dom-converter/download/dom-converter-0.1.4.tgz",
@ -3441,6 +3449,11 @@
}
}
},
"dom-matches": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-matches/-/dom-matches-2.0.0.tgz",
"integrity": "sha1-0nKLQWqHUzmA6wibhI0lPPI6dYw="
},
"dom-scroll-into-view": {
"version": "1.2.1",
"resolved": "https://registry.npm.taobao.org/dom-scroll-into-view/download/dom-scroll-into-view-1.2.1.tgz",

View File

@ -131,6 +131,7 @@
"component-classes": "^1.2.6",
"css-animation": "^1.4.1",
"dom-align": "^1.6.7",
"dom-closest": "^0.2.0",
"dom-scroll-into-view": "^1.2.1",
"enquire.js": "^2.1.6",
"eslint-plugin-vue": "^3.13.0",