mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-04-20 20:38:06 +00:00
342 lines
10 KiB
JavaScript
342 lines
10 KiB
JavaScript
import React, { Children } from 'react';
|
||
import PropTypes from 'prop-types';
|
||
import Checkbox from '../checkbox';
|
||
import Radio from '../radio';
|
||
import { func, log } from '../util';
|
||
import zhCN from '../locale/zh-cn';
|
||
import SelectionRow from './selection/row';
|
||
import Col from './column';
|
||
import { statics } from './util';
|
||
|
||
const { makeChain } = func;
|
||
|
||
const unique = (arr, key = 'this') => {
|
||
const temp = {};
|
||
const ret = [];
|
||
arr.forEach(item => {
|
||
let value;
|
||
if (key === 'this') {
|
||
value = item;
|
||
} else {
|
||
value = item[key];
|
||
}
|
||
if (!temp[value]) {
|
||
ret.push(item);
|
||
temp[value] = true;
|
||
}
|
||
});
|
||
return ret;
|
||
};
|
||
|
||
export default function selection(BaseComponent) {
|
||
/** Table */
|
||
class SelectionTable extends React.Component {
|
||
static SelectionRow = SelectionRow;
|
||
|
||
static propTypes = {
|
||
/**
|
||
* 是否启用选择模式
|
||
* @property {Function} getProps `Function(record, index)=>Object` 获取selection的默认属性
|
||
* @property {Function} onChange `Function(selectedRowKeys:Array, records:Array)` 选择改变的时候触发的事件,**注意:** 其中records只会包含当前dataSource的数据,很可能会小于selectedRowKeys的长度。
|
||
* @property {Function} onSelect `Function(selected:Boolean, record:Object, records:Array)` 用户手动选择/取消选择某行的回调
|
||
* @property {Function} onSelectAll `Function(selected:Boolean, records:Array)` 用户手动选择/取消选择所有行的回调
|
||
* @property {Array} selectedRowKeys 设置了此属性,将rowSelection变为受控状态,接收值为该行数据的primaryKey的值
|
||
* @property {String} mode 选择selection的模式, 可选值为`single`, `multiple`,默认为`multiple`
|
||
* @property {Function} columnProps `Function()=>Object` 选择列 的props,例如锁列、对齐等,可使用`Table.Column` 的所有参数
|
||
* @property {Function} titleProps `Function()=>Object` 选择列 表头的props,仅在 `multiple` 模式下生效
|
||
*/
|
||
rowSelection: PropTypes.object,
|
||
primaryKey: PropTypes.string,
|
||
dataSource: PropTypes.array,
|
||
entireDataSource: PropTypes.array,
|
||
...BaseComponent.propTypes,
|
||
};
|
||
|
||
static defaultProps = {
|
||
...BaseComponent.defaultProps,
|
||
locale: zhCN.Table,
|
||
primaryKey: 'id',
|
||
prefix: 'next-',
|
||
};
|
||
|
||
static contextTypes = {
|
||
listHeader: PropTypes.any,
|
||
};
|
||
|
||
static childContextTypes = {
|
||
rowSelection: PropTypes.object,
|
||
selectedRowKeys: PropTypes.array,
|
||
};
|
||
|
||
constructor(props, context) {
|
||
super(props, context);
|
||
this.state = {
|
||
selectedRowKeys:
|
||
props.rowSelection &&
|
||
'selectedRowKeys' in props.rowSelection
|
||
? props.rowSelection.selectedRowKeys || []
|
||
: [],
|
||
};
|
||
}
|
||
|
||
getChildContext() {
|
||
return {
|
||
rowSelection: this.props.rowSelection,
|
||
selectedRowKeys: this.state.selectedRowKeys,
|
||
};
|
||
}
|
||
|
||
componentWillReceiveProps(nextProps) {
|
||
if (
|
||
nextProps.rowSelection &&
|
||
'selectedRowKeys' in nextProps.rowSelection
|
||
) {
|
||
const selectedRowKeys =
|
||
nextProps.rowSelection.selectedRowKeys || [];
|
||
this.setState({
|
||
selectedRowKeys,
|
||
});
|
||
}
|
||
}
|
||
|
||
normalizeChildren(children) {
|
||
const { prefix, rowSelection, size } = this.props;
|
||
if (rowSelection) {
|
||
children = Children.map(children, (child, index) => React.cloneElement(child, {
|
||
key: index,
|
||
}));
|
||
|
||
const attrs =
|
||
(rowSelection.columnProps && rowSelection.columnProps()) ||
|
||
{};
|
||
|
||
children.unshift(
|
||
<Col
|
||
key="selection"
|
||
title={this.renderSelectionHeader.bind(this)}
|
||
cell={this.renderSelectionBody.bind(this)}
|
||
width={size === 'small' ? 34 : 50}
|
||
className={`${prefix}table-selection ${prefix}table-prerow`}
|
||
__normalized
|
||
{...attrs}
|
||
/>,
|
||
);
|
||
return children;
|
||
}
|
||
return children;
|
||
}
|
||
|
||
renderSelectionHeader = () => {
|
||
const onChange = this.selectAllRow;
|
||
const attrs = {};
|
||
const {
|
||
rowSelection,
|
||
primaryKey,
|
||
dataSource,
|
||
entireDataSource,
|
||
locale,
|
||
} = this.props;
|
||
const { selectedRowKeys } = this.state;
|
||
const mode = rowSelection.mode ? rowSelection.mode : 'multiple';
|
||
|
||
let checked = !!selectedRowKeys.length;
|
||
let indeterminate = false;
|
||
|
||
const source = entireDataSource || dataSource;
|
||
|
||
this.flatDataSource(source)
|
||
.filter((record, index) => {
|
||
if (!rowSelection.getProps) {
|
||
return true;
|
||
} else {
|
||
return !(rowSelection.getProps(record, index) || {})
|
||
.disabled;
|
||
}
|
||
})
|
||
.map(record => record[primaryKey])
|
||
.forEach(id => {
|
||
if (selectedRowKeys.indexOf(id) === -1) {
|
||
checked = false;
|
||
} else {
|
||
indeterminate = true;
|
||
}
|
||
});
|
||
attrs.onClick = makeChain(e => {
|
||
e.stopPropagation();
|
||
}, attrs.onClick);
|
||
|
||
const userAttrs =
|
||
(rowSelection.titleProps && rowSelection.titleProps()) || {};
|
||
|
||
if (checked) {
|
||
indeterminate = false;
|
||
}
|
||
return [
|
||
mode === 'multiple' ? (
|
||
<Checkbox
|
||
key="_total"
|
||
indeterminate={indeterminate}
|
||
aria-label={locale.selectAll}
|
||
checked={checked}
|
||
onChange={onChange}
|
||
{...attrs}
|
||
{...userAttrs}
|
||
/>
|
||
) : null,
|
||
rowSelection.titleAddons && rowSelection.titleAddons(),
|
||
];
|
||
};
|
||
|
||
renderSelectionBody = (value, index, record) => {
|
||
const { rowSelection, primaryKey } = this.props;
|
||
const { selectedRowKeys } = this.state;
|
||
const mode = rowSelection.mode ? rowSelection.mode : 'multiple';
|
||
const checked = selectedRowKeys.indexOf(record[primaryKey]) > -1;
|
||
const onChange = this.selectOneRow.bind(this, index, record);
|
||
const attrs = rowSelection.getProps
|
||
? rowSelection.getProps(record, index) || {}
|
||
: {};
|
||
|
||
attrs.onClick = makeChain(e => {
|
||
e.stopPropagation();
|
||
}, attrs.onClick);
|
||
return mode === 'multiple' ? (
|
||
<Checkbox checked={checked} onChange={onChange} {...attrs} />
|
||
) : (
|
||
<Radio checked={checked} onChange={onChange} {...attrs} />
|
||
);
|
||
};
|
||
|
||
selectAllRow = (checked, e) => {
|
||
const ret = [...this.state.selectedRowKeys];
|
||
const {
|
||
rowSelection,
|
||
primaryKey,
|
||
dataSource,
|
||
entireDataSource,
|
||
} = this.props;
|
||
const { selectedRowKeys } = this.state;
|
||
const { getProps } = rowSelection;
|
||
let attrs = {};
|
||
let records = [];
|
||
|
||
const source = entireDataSource || dataSource;
|
||
|
||
this.flatDataSource(source).forEach((record, index) => {
|
||
const id = record[primaryKey];
|
||
if (getProps) {
|
||
attrs = getProps(record, index) || {};
|
||
}
|
||
// 反选和全选的时候不要丢弃禁用项的选中状态
|
||
if (
|
||
checked &&
|
||
(!attrs.disabled || selectedRowKeys.indexOf(id) > -1)
|
||
) {
|
||
ret.push(id);
|
||
records.push(record);
|
||
} else if (attrs.disabled && selectedRowKeys.indexOf(id) > -1) {
|
||
ret.push(id);
|
||
records.push(record);
|
||
} else {
|
||
const i = ret.indexOf(id);
|
||
i > -1 && ret.splice(i, 1);
|
||
}
|
||
});
|
||
|
||
records = unique(records, primaryKey);
|
||
if (typeof rowSelection.onSelectAll === 'function') {
|
||
rowSelection.onSelectAll(checked, records);
|
||
}
|
||
this.triggerSelection(rowSelection, unique(ret), records);
|
||
e.stopPropagation();
|
||
};
|
||
|
||
selectOneRow(index, record, checked, e) {
|
||
let selectedRowKeys = [...this.state.selectedRowKeys];
|
||
let i;
|
||
const { primaryKey, rowSelection, dataSource } = this.props;
|
||
const mode = rowSelection.mode ? rowSelection.mode : 'multiple';
|
||
const id = record[primaryKey];
|
||
if (!id) {
|
||
log.warning(
|
||
`Can't get value from record using given ${primaryKey} as primaryKey.`,
|
||
);
|
||
}
|
||
if (mode === 'multiple') {
|
||
if (checked) {
|
||
selectedRowKeys.push(id);
|
||
} else {
|
||
i = selectedRowKeys.indexOf(id);
|
||
selectedRowKeys.splice(i, 1);
|
||
}
|
||
} else if (checked) {
|
||
selectedRowKeys = [id];
|
||
}
|
||
const records = unique(
|
||
dataSource.filter(
|
||
item => selectedRowKeys.indexOf(item[primaryKey]) > -1,
|
||
),
|
||
primaryKey,
|
||
);
|
||
if (typeof rowSelection.onSelect === 'function') {
|
||
rowSelection.onSelect(checked, record, records);
|
||
}
|
||
|
||
this.triggerSelection(rowSelection, selectedRowKeys, records);
|
||
|
||
e.stopPropagation();
|
||
}
|
||
|
||
triggerSelection(rowSelection, selectedRowKeys, records) {
|
||
if (!('selectedRowKeys' in rowSelection)) {
|
||
this.setState({
|
||
selectedRowKeys,
|
||
});
|
||
}
|
||
if (typeof rowSelection.onChange === 'function') {
|
||
rowSelection.onChange(selectedRowKeys, records);
|
||
}
|
||
}
|
||
|
||
flatDataSource(dataSource) {
|
||
let ret = dataSource;
|
||
const { listHeader } = this.context;
|
||
|
||
if (listHeader) {
|
||
ret = [];
|
||
const { hasChildrenSelection, hasSelection } = listHeader;
|
||
dataSource.forEach(item => {
|
||
const { children } = item;
|
||
// 如果需要渲染selection才将这条记录插入到dataSource
|
||
// 或者没有孩子节点
|
||
if (hasSelection) {
|
||
ret.push(item);
|
||
}
|
||
if (children && hasChildrenSelection) {
|
||
ret = ret.concat(children);
|
||
}
|
||
});
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
render() {
|
||
/* eslint-disable prefer-const */
|
||
let { rowSelection, components, children, ...others } = this.props;
|
||
|
||
if (rowSelection) {
|
||
children = this.normalizeChildren(children);
|
||
components = { ...components };
|
||
components.Row = components.Row || SelectionRow;
|
||
}
|
||
return (
|
||
<BaseComponent {...others} components={components}>
|
||
{children}
|
||
</BaseComponent>
|
||
);
|
||
}
|
||
}
|
||
statics(SelectionTable, BaseComponent);
|
||
return SelectionTable;
|
||
}
|