123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419 |
- /* eslint-disable no-nested-ternary */
- /* eslint-disable prefer-const */
- /* eslint-disable prefer-destructuring */
- /* eslint-disable no-shadow */
- /* eslint-disable no-param-reassign */
- /* eslint-disable max-len */
- /* eslint-disable react/no-did-update-set-state */
- /* eslint-disable eqeqeq */
- /* eslint-disable max-lines-per-function */
- import React, { ReactNode, createRef, isValidElement } from 'react';
- import PropTypes from 'prop-types';
- import classnames from 'classnames';
- import {
- get,
- noop,
- includes,
- find,
- findIndex,
- some,
- debounce,
- flattenDeep,
- each,
- omit,
- isNull,
- difference,
- isFunction,
- isObject
- } from 'lodash';
- import {
- mergeQueries,
- equalWith,
- mergeColumns,
- isAnyFixedRight,
- assignColumnKeys,
- flattenColumns,
- getAllDisabledRowKeys
- } from '@douyinfe/semi-foundation/table/utils';
- import Store from '@douyinfe/semi-foundation/utils/Store';
- import TableFoundation, { TableAdapter, BasePageData, BaseRowKeyType, BaseHeadWidth } from '@douyinfe/semi-foundation/table/foundation';
- import { TableSelectionCellEvent } from '@douyinfe/semi-foundation/table/tableSelectionCellFoundation';
- import { strings, cssClasses, numbers } from '@douyinfe/semi-foundation/table/constants';
- import '@douyinfe/semi-foundation/table/table.scss';
- import Spin from '../spin';
- import BaseComponent from '../_base/baseComponent';
- import LocaleConsumer from '../locale/localeConsumer';
- import ColumnShape from './ColumnShape';
- import getColumns from './getColumns';
- import TableContext, { TableContextProps } from './table-context';
- import TableContextProvider from './TableContextProvider';
- import ColumnSelection from './ColumnSelection';
- import TablePagination from './TablePagination';
- import ColumnFilter, { OnSelectData } from './ColumnFilter';
- import ColumnSorter from './ColumnSorter';
- import ExpandedIcon from './CustomExpandIcon';
- import HeadTable, { HeadTableProps } from './HeadTable';
- import BodyTable, { BodyProps } from './Body';
- import { measureScrollbar, logger, cloneDeep, mergeComponents } from './utils';
- import {
- ColumnProps,
- TablePaginationProps,
- BodyScrollEvent,
- BodyScrollPosition,
- ExpandIcon,
- ColumnTitleProps,
- Pagination,
- RenderPagination,
- TableLocale,
- TableProps,
- TableComponents,
- RowSelectionProps,
- Data
- } from './interface';
- import { ArrayElement } from '../_base/base';
- export type NormalTableProps<RecordType extends Record<string, any> = Data> = Omit<TableProps<RecordType>, 'resizable'>;
- export interface NormalTableState<RecordType extends Record<string, any> = Data> {
- cachedColumns?: ColumnProps<RecordType>[];
- cachedChildren?: React.ReactNode;
- flattenColumns?: ColumnProps<RecordType>[];
- components?: TableComponents;
- queries?: ColumnProps<RecordType>[];
- dataSource?: RecordType[];
- flattenData?: RecordType[];
- expandedRowKeys?: (string | number)[];
- rowSelection?: TableStateRowSelection<RecordType>;
- pagination?: Pagination;
- groups?: Map<string, RecordType[]>;
- allRowKeys?: (string | number)[];
- disabledRowKeys?: (string | number)[];
- disabledRowKeysSet?: Set<string | number>;
- headWidths?: Array<Array<BaseHeadWidth>>;
- bodyHasScrollBar?: boolean;
- prePropRowSelection?: TableStateRowSelection<RecordType>;
- tableWidth?: number;
- prePagination?: Pagination;
- }
- export type TableStateRowSelection<RecordType extends Record<string, any> = Data> = (RowSelectionProps<RecordType> & { selectedRowKeysSet?: Set<(string | number)> }) | boolean;
- export interface RenderTableProps<RecordType> extends HeadTableProps, BodyProps {
- filteredColumns: ColumnProps<RecordType>[];
- useFixedHeader: boolean;
- bodyRef: React.MutableRefObject<HTMLDivElement> | ((instance: any) => void);
- rowSelection: TableStateRowSelection<RecordType>;
- bodyHasScrollBar: boolean;
- }
- class Table<RecordType extends Record<string, any>> extends BaseComponent<NormalTableProps<RecordType>, NormalTableState<RecordType>> {
- static contextType = TableContext;
- static propTypes = {
- className: PropTypes.string,
- style: PropTypes.object,
- prefixCls: PropTypes.string,
- components: PropTypes.any,
- bordered: PropTypes.bool,
- loading: PropTypes.bool,
- size: PropTypes.oneOf(strings.SIZES),
- tableLayout: PropTypes.oneOf(strings.LAYOUTS),
- columns: PropTypes.arrayOf(PropTypes.shape(ColumnShape)),
- hideExpandedColumn: PropTypes.bool,
- id: PropTypes.string,
- expandIcon: PropTypes.oneOfType([PropTypes.bool, PropTypes.func, PropTypes.node]),
- expandCellFixed: PropTypes.oneOf(strings.FIXED_SET),
- title: PropTypes.oneOfType([PropTypes.string, PropTypes.node, PropTypes.func]),
- onHeaderRow: PropTypes.func,
- showHeader: PropTypes.bool,
- indentSize: PropTypes.number,
- rowKey: PropTypes.oneOfType([PropTypes.func, PropTypes.string, PropTypes.number]),
- onRow: PropTypes.func,
- onExpandedRowsChange: PropTypes.func,
- onExpand: PropTypes.func,
- rowExpandable: PropTypes.func,
- expandedRowRender: PropTypes.func,
- expandedRowKeys: PropTypes.array,
- defaultExpandAllRows: PropTypes.bool,
- expandAllRows: PropTypes.bool,
- defaultExpandAllGroupRows: PropTypes.bool,
- expandAllGroupRows: PropTypes.bool,
- defaultExpandedRowKeys: PropTypes.array,
- pagination: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
- renderPagination: PropTypes.func,
- footer: PropTypes.oneOfType([PropTypes.func, PropTypes.string, PropTypes.node]),
- empty: PropTypes.node,
- dataSource: PropTypes.array,
- childrenRecordName: PropTypes.string, // children data property name
- rowSelection: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
- onChange: PropTypes.func,
- scroll: PropTypes.shape({
- x: PropTypes.oneOfType([PropTypes.number, PropTypes.string, PropTypes.bool]),
- y: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
- }),
- groupBy: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]),
- renderGroupSection: PropTypes.oneOfType([PropTypes.func]),
- onGroupedRow: PropTypes.func,
- clickGroupedRowToExpand: PropTypes.bool,
- virtualized: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
- dropdownPrefixCls: PropTypes.string, // TODO: future api
- expandRowByClick: PropTypes.bool, // TODO: future api
- getVirtualizedListRef: PropTypes.func, // TODO: future api
- };
- static defaultProps = {
- // rowExpandable: stubTrue,
- tableLayout: '',
- dataSource: [] as [],
- prefixCls: cssClasses.PREFIX,
- rowSelection: null as null,
- className: '',
- childrenRecordName: 'children',
- size: 'default',
- loading: false,
- bordered: false,
- expandCellFixed: false,
- hideExpandedColumn: true,
- showHeader: true,
- indentSize: numbers.DEFAULT_INDENT_WIDTH,
- onChange: noop,
- pagination: true,
- rowKey: 'key',
- defaultExpandedRowKeys: [] as [],
- defaultExpandAllRows: false,
- defaultExpandAllGroupRows: false,
- expandAllRows: false,
- expandAllGroupRows: false,
- onFilterDropdownVisibleChange: noop,
- onExpand: noop,
- onExpandedRowsChange: noop,
- expandRowByClick: false,
- };
- get adapter(): TableAdapter<RecordType> {
- return {
- ...super.adapter,
- resetScrollY: () => {
- if (this.bodyWrapRef.current) {
- this.bodyWrapRef.current.scrollTop = 0;
- }
- },
- setSelectedRowKeys: selectedRowKeys => {
- this.setState({ rowSelection: {
- ...this.state.rowSelection as Record<string, any>,
- selectedRowKeys: [...selectedRowKeys],
- selectedRowKeysSet: new Set(selectedRowKeys),
- } });
- },
- setDisabledRowKeys: disabledRowKeys => {
- this.setState({ disabledRowKeys, disabledRowKeysSet: new Set(disabledRowKeys) });
- },
- setCurrentPage: currentPage => {
- const { pagination } = this.state;
- if (typeof pagination === 'object') {
- this.setState({ pagination: { ...pagination, currentPage } });
- } else {
- this.setState({ pagination: { currentPage } });
- }
- },
- setPagination: pagination => this.setState({ pagination }),
- setGroups: groups => this.setState({ groups }),
- setDataSource: dataSource => this.setState({ dataSource }),
- setExpandedRowKeys: expandedRowKeys => this.setState({ expandedRowKeys: [...expandedRowKeys] }),
- setQuery: (query = {}) => {
- let queries = [...this.state.queries];
- queries = mergeQueries(query, queries);
- this.setState({ queries });
- },
- // Update queries when filtering or sorting
- setQueries: (queries: ColumnProps<RecordType>[]) => this.setState({ queries }),
- setFlattenData: flattenData => this.setState({ flattenData }),
- setAllRowKeys: allRowKeys => this.setState({ allRowKeys }),
- setHoveredRowKey: hoveredRowKey => {
- this.store.setState({ hoveredRowKey });
- },
- setCachedFilteredSortedDataSource: filteredSortedDataSource => {
- this.cachedFilteredSortedDataSource = filteredSortedDataSource;
- },
- setCachedFilteredSortedRowKeys: filteredSortedRowKeys => {
- this.cachedFilteredSortedRowKeys = filteredSortedRowKeys;
- this.cachedFilteredSortedRowKeysSet = new Set(filteredSortedRowKeys);
- },
- getCurrentPage: () => get(this.state, 'pagination.currentPage', 1),
- getCurrentPageSize: () => get(this.state, 'pagination.pageSize', numbers.DEFAULT_PAGE_SIZE),
- getCachedFilteredSortedDataSource: () => this.cachedFilteredSortedDataSource,
- getCachedFilteredSortedRowKeys: () => this.cachedFilteredSortedRowKeys,
- getCachedFilteredSortedRowKeysSet: () => this.cachedFilteredSortedRowKeysSet,
- notifyFilterDropdownVisibleChange: (visible, dataIndex) =>
- this._invokeColumnFn(dataIndex, 'onFilterDropdownVisibleChange', visible),
- notifyChange: (...args) => this.props.onChange(...args),
- notifyExpand: (...args) => this.props.onExpand(...args),
- notifyExpandedRowsChange: (...args) => this.props.onExpandedRowsChange(...args),
- notifySelect: (...args) => this._invokeRowSelection('onSelect', ...args),
- notifySelectAll: (...args) => this._invokeRowSelection('onSelectAll', ...args),
- notifySelectInvert: (...args) => this._invokeRowSelection('onSelectInvert', ...args),
- notifySelectionChange: (...args) => this._invokeRowSelection('onChange', ...args),
- isAnyColumnFixed: (columns: ColumnProps<RecordType>[]) =>
- some(this.getColumns(columns || this.props.columns, this.props.children), column => Boolean(column.fixed)),
- useFixedHeader: () => {
- const { scroll, sticky } = this.props;
- if (get(scroll, 'y')) {
- return true;
- }
- if (sticky) {
- return true;
- }
- return false;
- },
- setHeadWidths: (headWidths: Array<BaseHeadWidth>, index = 0) => {
- if (!equalWith(this.state.headWidths[index], headWidths)) {
- // The map call depends on the last state
- this.setState(state => {
- const newHeadWidths: Array<Array<BaseHeadWidth>> = [...state.headWidths];
- newHeadWidths[index] = [...headWidths];
- return { headWidths: newHeadWidths };
- });
- }
- },
- getHeadWidths: (index = 0) => {
- if (this.state.headWidths.length && typeof index === 'number') {
- const configs = this.state.headWidths[index] || [];
- return configs.map(item => item.width);
- }
- return [];
- },
- // This method is called by row rendering function
- getCellWidths: (flattenedColumns: ColumnProps<RecordType>[], flattenedWidths: BaseHeadWidth[] = null, ignoreScrollBarKey = false): number[] => {
- if (Array.isArray(flattenedColumns) && flattenedColumns.length) {
- flattenedWidths =
- flattenedWidths == null && this.state.headWidths.length ?
- flattenDeep(this.state.headWidths) :
- [];
- if (
- Array.isArray(flattenedWidths) &&
- flattenedWidths.length
- ) {
- return flattenedColumns.reduce((result, column) => {
- const found =
- column.key === strings.DEFAULT_KEY_COLUMN_SCROLLBAR && ignoreScrollBarKey ?
- null :
- find(
- flattenedWidths,
- item => item && item.key != null && item.key === column.key
- );
- if (found) {
- result.push(found.width);
- }
- return result;
- }, [] as number[]);
- }
- }
- return [];
- },
- mergedRowExpandable: record => {
- const { expandedRowRender, childrenRecordName, rowExpandable } = this.props;
- const children = get(record, childrenRecordName);
- const hasExpandedRowRender = typeof expandedRowRender === 'function';
- const hasRowExpandable = typeof rowExpandable === 'function';
- const hasChildren = Array.isArray(children) && children.length;
- const strictExpandableResult = hasRowExpandable && rowExpandable(record);
- const looseExpandableResult = !hasRowExpandable || strictExpandableResult;
- return (
- ((hasExpandedRowRender || hasChildren) && looseExpandableResult) ||
- (!(hasExpandedRowRender || hasChildren) && strictExpandableResult)
- );
- },
- isAnyColumnUseFullRender: (columns: ColumnProps<RecordType>[]) =>
- some(columns, column => Boolean(column.useFullRender)),
- getNormalizeColumns: () => this.normalizeColumns,
- getHandleColumns: () => this.handleColumns,
- getMergePagination: () => this.mergePagination,
- setBodyHasScrollbar: bodyHasScrollBar => {
- if (bodyHasScrollBar !== this.state.bodyHasScrollBar) {
- this.setState({ bodyHasScrollBar });
- }
- }
- };
- }
- bodyWrapRef: React.MutableRefObject<HTMLDivElement>;
- rootWrapRef: React.MutableRefObject<HTMLDivElement>;
- wrapRef: React.MutableRefObject<HTMLDivElement>;
- headerWrapRef: React.MutableRefObject<HTMLDivElement>;
- debouncedWindowResize: () => void;
- cachedFilteredSortedDataSource: RecordType[];
- cachedFilteredSortedRowKeys: BaseRowKeyType[];
- cachedFilteredSortedRowKeysSet: Set<string | number>;
- store: Store;
- lastScrollTop!: number;
- lastScrollLeft!: number;
- scrollPosition!: BodyScrollPosition;
- position!: BodyScrollPosition;
- foundation: TableFoundation<RecordType>;
- context: TableContextProps;
- constructor(props: NormalTableProps<RecordType>, context: TableContextProps) {
- super(props);
- this.foundation = new TableFoundation<RecordType>(this.adapter);
- // columns cannot be deepClone, otherwise the comparison will be false
- const columns = this.getColumns(props.columns, props.children);
- const cachedflattenColumns = flattenColumns(columns);
- this.state = {
- /**
- * Cached props
- */
- cachedColumns: columns, // update cachedColumns after columns or children change
- cachedChildren: props.children,
- flattenColumns: cachedflattenColumns,
- components: mergeComponents(props.components, props.virtualized), // cached components
- /**
- * State calculated based on prop
- */
- queries: cloneDeep(cachedflattenColumns), // flatten columns, update when sorting or filtering
- dataSource: [], // data after paging
- flattenData: [],
- expandedRowKeys: [...(props.expandedRowKeys || []), ...(props.defaultExpandedRowKeys || [])], // cached expandedRowKeys
- rowSelection: props.rowSelection ? isObject(props.rowSelection) ? { ...props.rowSelection } : {} : null,
- pagination:
- props.pagination && typeof props.pagination === 'object' ?
- { ...props.pagination } :
- props.pagination || false,
- /**
- * Internal state
- */
- groups: null,
- allRowKeys: [], // row keys after paging
- disabledRowKeys: [], // disabled row keys after paging
- disabledRowKeysSet: new Set(),
- headWidths: [], // header cell width
- bodyHasScrollBar: false,
- prePropRowSelection: undefined,
- prePagination: undefined
- };
- this.rootWrapRef = createRef();
- this.wrapRef = createRef(); // table's outside wrap
- this.bodyWrapRef = createRef();
- this.headerWrapRef = createRef();
- this.store = new Store({
- hoveredRowKey: null,
- });
- this.debouncedWindowResize = debounce(this.handleWindowResize, 150);
- this.cachedFilteredSortedDataSource = [];
- this.cachedFilteredSortedRowKeys = [];
- this.cachedFilteredSortedRowKeysSet = new Set();
- }
- static getDerivedStateFromProps(props: NormalTableProps, state: NormalTableState) {
- const willUpdateStates: Partial<NormalTableState> = {};
- const { rowSelection, dataSource, childrenRecordName, rowKey, pagination } = props;
- props.columns && props.children && logger.warn('columns should not given by object and children at the same time');
- if (props.columns && props.columns !== state.cachedColumns) {
- const newFlattenColumns = flattenColumns(props.columns);
- willUpdateStates.flattenColumns = newFlattenColumns;
- willUpdateStates.queries = mergeColumns(state.queries, newFlattenColumns, null, false);
- willUpdateStates.cachedColumns = props.columns;
- willUpdateStates.cachedChildren = null;
- } else if (props.children && props.children !== state.cachedChildren) {
- const newNestedColumns = getColumns(props.children);
- const newFlattenColumns = flattenColumns(newNestedColumns);
- const columns = mergeColumns(state.queries, newFlattenColumns, null, false);
- willUpdateStates.flattenColumns = newFlattenColumns;
- willUpdateStates.queries = [...columns];
- willUpdateStates.cachedColumns = [...newNestedColumns];
- willUpdateStates.cachedChildren = props.children;
- }
- // Update controlled selection column
- if (rowSelection !== state.prePropRowSelection) {
- let newSelectionStates: TableStateRowSelection = {};
- if (isObject(state.rowSelection)) {
- newSelectionStates = { ...newSelectionStates, ...state.rowSelection };
- }
- if (isObject(rowSelection)) {
- newSelectionStates = { ...newSelectionStates, ...rowSelection };
- }
- const selectedRowKeys = get(rowSelection, 'selectedRowKeys');
- const getCheckboxProps = get(rowSelection, 'getCheckboxProps');
- if (selectedRowKeys && Array.isArray(selectedRowKeys)) {
- newSelectionStates.selectedRowKeysSet = new Set(selectedRowKeys);
- }
- // The return value of getCheckboxProps affects the disabled rows
- if (isFunction(getCheckboxProps)) {
- const disabledRowKeys = getAllDisabledRowKeys({ dataSource, getCheckboxProps, childrenRecordName, rowKey });
- willUpdateStates.disabledRowKeys = disabledRowKeys;
- willUpdateStates.disabledRowKeysSet = new Set(disabledRowKeys);
- }
- willUpdateStates.rowSelection = newSelectionStates;
- willUpdateStates.prePropRowSelection = rowSelection;
- }
- if (pagination !== state.prePagination) {
- let newPagination: Pagination = {};
- if (isObject(state.pagination)) {
- newPagination = { ...newPagination, ...state.pagination };
- }
- if (isObject(pagination)) {
- newPagination = { ...newPagination, ...pagination };
- }
- willUpdateStates.pagination = newPagination;
- willUpdateStates.prePagination = pagination;
- }
- return willUpdateStates;
- }
- componentDidMount() {
- super.componentDidMount();
- this.setScrollPosition('left');
- if (this.adapter.isAnyColumnFixed() || (this.props.showHeader && this.adapter.useFixedHeader())) {
- this.handleWindowResize();
- window.addEventListener('resize', this.debouncedWindowResize);
- }
- }
- // TODO: Extract the setState operation to the adapter or getDerivedStateFromProps function
- componentDidUpdate(prevProps: NormalTableProps<RecordType>, prevState: NormalTableState<RecordType>) {
- const {
- dataSource,
- expandedRowKeys,
- expandAllRows,
- expandAllGroupRows,
- virtualized,
- components,
- pagination: propsPagination
- } = this.props;
- const {
- pagination: statePagination,
- queries: stateQueries,
- cachedColumns: stateCachedColumns,
- cachedChildren: stateCachedChildren,
- groups: stateGroups,
- } = this.state;
- /**
- * State related to paging
- *
- * @param dataSource
- * @param groups
- * @param pagination
- * @param disabledRowKeys
- * @param allRowKeys
- * @param queries
- */
- const states: Partial<NormalTableState<RecordType>> = {};
- this._warnIfNoKey();
- /**
- * The state that needs to be updated after props changes
- */
- // Update controlled expand column
- if (Array.isArray(expandedRowKeys) && expandedRowKeys !== prevProps.expandedRowKeys) {
- this.setState({ expandedRowKeys });
- }
- // Update components
- if (components !== prevProps.components || virtualized !== prevProps.virtualized) {
- this.setState({ components: mergeComponents(components, virtualized) });
- }
- // Update the default expanded column
- if (
- expandAllRows !== prevProps.expandAllRows ||
- expandAllGroupRows !== prevProps.expandAllGroupRows
- ) {
- this.foundation.initExpandedRowKeys({ groups: stateGroups });
- }
- /**
- * After dataSource is updated || (cachedColumns || cachedChildren updated)
- * 1. Cache filtered sorted data and a collection of data rows, stored in this
- * 2. Update pager and group, stored in state
- */
- if (dataSource !== prevProps.dataSource || stateCachedColumns !== prevState.cachedColumns || stateCachedChildren !== prevState.cachedChildren) {
- // TODO: foundation.getFilteredSortedDataSource has side effects and will be modified to the dataSource reference
- // Temporarily use _dataSource=[...dataSource] for processing
- const _dataSource = [...dataSource];
- const filteredSortedDataSource = this.foundation.getFilteredSortedDataSource(_dataSource, stateQueries);
- this.foundation.setCachedFilteredSortedDataSource(filteredSortedDataSource);
- states.dataSource = filteredSortedDataSource;
- if (this.props.groupBy) {
- states.groups = null;
- }
- }
- // when dataSource has change, should reset currentPage
- if (dataSource !== prevProps.dataSource) {
- states.pagination = isObject(statePagination) ? {
- ...statePagination,
- currentPage: isObject(propsPagination) && propsPagination.currentPage ? propsPagination.currentPage : 1,
- } : statePagination;
- }
- if (Object.keys(states).length) {
- const {
- // eslint-disable-next-line @typescript-eslint/no-shadow
- pagination: mergedStatePagination = null,
- queries: stateQueries = null,
- dataSource: stateDataSource = null,
- } = states;
- const handledProps: Partial<NormalTableState<RecordType>> = this.foundation.getCurrentPageData(stateDataSource, mergedStatePagination as TablePaginationProps, stateQueries);
- // After the pager is updated, reset allRowKeys of the current page
- this.adapter.setAllRowKeys(handledProps.allRowKeys);
- this.adapter.setDisabledRowKeys(handledProps.disabledRowKeys);
- if ('dataSource' in states) {
- if (this.props.defaultExpandAllRows && handledProps.groups && handledProps.groups.size ||
- this.props.expandAllRows ||
- this.props.expandAllGroupRows
- ) {
- this.foundation.initExpandedRowKeys(handledProps);
- }
- }
- // Centrally update paging related state
- const statesKeys: any[] = Object.keys(states);
- for (const k of statesKeys) {
- this.setState({ [k]: handledProps[k] });
- }
- }
- if (this.adapter.isAnyColumnFixed() || (this.props.showHeader && this.adapter.useFixedHeader())) {
- if (!this.debouncedWindowResize) {
- window.addEventListener('resize', this.debouncedWindowResize);
- }
- }
- }
- componentWillUnmount() {
- super.componentWillUnmount();
- if (this.debouncedWindowResize) {
- window.removeEventListener('resize', this.debouncedWindowResize);
- (this.debouncedWindowResize as any).cancel();
- this.debouncedWindowResize = null;
- }
- }
- // TODO: notify when data don't have key
- _warnIfNoKey = () => {
- if (
- (this.props.rowSelection || this.props.expandedRowRender) &&
- some(this.props.dataSource, record => this.foundation.getRecordKey(record) == null)
- ) {
- logger.error(
- 'You must specify a key for each element in the dataSource or use "rowKey" to specify an attribute name as the primary key!'
- );
- }
- };
- _invokeRowSelection = (funcName: string, ...args: any[]) => {
- const func = get(this.state, ['rowSelection', funcName]);
- if (typeof func === 'function') {
- func(...args);
- }
- };
- _invokeColumnFn = (key: string, funcName: string, ...args: any[]) => {
- if (key && funcName) {
- const column = this.foundation.getQuery(key);
- const func = get(column, funcName, null);
- if (typeof func === 'function') {
- func(...args);
- }
- }
- };
- _cacheHeaderRef = (node: HTMLDivElement) => {
- this.headerWrapRef.current = node;
- };
- getCurrentPageData = () => {
- const pageData = this.foundation.getCurrentPageData();
- const retObj = ['dataSource', 'groups'].reduce((result, key) => {
- if (pageData[key]) {
- result[key] = pageData[key];
- }
- return result;
- }, {});
- return cloneDeep(retObj);
- };
- getColumns = (columns: ColumnProps<RecordType>[], children: ReactNode) => (!Array.isArray(columns) || !columns || !columns.length ? getColumns(children) : columns);
- // @ts-ignore
- getCellWidths = (...args: any[]) => this.foundation.getCellWidths(...args);
- // @ts-ignore
- setHeadWidths = (...args: any[]) => this.foundation.setHeadWidths(...args);
- // @ts-ignore
- getHeadWidths = (...args: any[]) => this.foundation.getHeadWidths(...args);
- // @ts-ignore
- mergedRowExpandable = (...args: any[]) => this.foundation.mergedRowExpandable(...args);
- // @ts-ignore
- setBodyHasScrollbar = (...args: any[]) => this.foundation.setBodyHasScrollbar(...args);
- handleWheel = (event: React.WheelEvent<HTMLDivElement>) => {
- const { scroll = {} } = this.props;
- if (window.navigator.userAgent.match(/Trident\/7\./) && scroll.y) {
- event.preventDefault();
- const wd = event.deltaY;
- const { target } = event;
- // const { bodyTable, fixedColumnsBodyLeft, fixedColumnsBodyRight } = this;
- const bodyTable = this.bodyWrapRef.current;
- let scrollTop = 0;
- if (this.lastScrollTop) {
- scrollTop = this.lastScrollTop + wd;
- } else {
- scrollTop = wd;
- }
- if (bodyTable && target !== bodyTable) {
- bodyTable.scrollTop = scrollTop;
- }
- }
- };
- handleBodyScrollLeft = (e: BodyScrollEvent) => {
- if (e.currentTarget !== e.target) {
- return;
- }
- const { target } = e;
- // const { headTable, bodyTable } = this;
- const headTable = this.headerWrapRef.current;
- const bodyTable = this.bodyWrapRef.current;
- if (target.scrollLeft !== this.lastScrollLeft) {
- if (target === bodyTable && headTable) {
- headTable.scrollLeft = target.scrollLeft;
- } else if (target === headTable && bodyTable) {
- bodyTable.scrollLeft = target.scrollLeft;
- }
- this.setScrollPositionClassName();
- }
- // Remember last scrollLeft for scroll direction detecting.
- this.lastScrollLeft = target.scrollLeft;
- };
- handleWindowResize = () => {
- this.syncTableWidth();
- this.setScrollPositionClassName();
- };
- handleBodyScrollTop = (e: BodyScrollEvent) => {
- const { target } = e;
- if (e.currentTarget !== target) {
- return;
- }
- const { scroll = {} } = this.props;
- // const { headTable, bodyTable, fixedColumnsBodyLeft, fixedColumnsBodyRight } = this;
- const headTable = this.headerWrapRef.current;
- const bodyTable = this.bodyWrapRef.current;
- if (target.scrollTop !== this.lastScrollTop && scroll.y && target !== headTable) {
- const { scrollTop } = target;
- if (bodyTable && target !== bodyTable) {
- bodyTable.scrollTop = scrollTop;
- }
- }
- // Remember last scrollTop for scroll direction detecting.
- this.lastScrollTop = target.scrollTop;
- };
- handleBodyScroll = (e: BodyScrollEvent) => {
- this.handleBodyScrollLeft(e);
- this.handleBodyScrollTop(e);
- };
- setScrollPosition = (position: BodyScrollPosition) => {
- const { prefixCls } = this.props;
- const positionAll = [
- `${prefixCls}-scroll-position-both`,
- `${prefixCls}-scroll-position-middle`,
- `${prefixCls}-scroll-position-left`,
- `${prefixCls}-scroll-position-right`,
- ];
- this.scrollPosition = position;
- const tableNode = this.wrapRef.current;
- if (tableNode && tableNode.nodeType) {
- if (position === 'both') {
- const acceptPosition = [`${prefixCls}-scroll-position-left`, `${prefixCls}-scroll-position-right`];
- tableNode.classList.remove(...difference(positionAll, acceptPosition));
- tableNode.classList.add(...acceptPosition);
- } else {
- const acceptPosition = [`${prefixCls}-scroll-position-${position}`];
- tableNode.classList.remove(...difference(positionAll, acceptPosition));
- tableNode.classList.add(...acceptPosition);
- }
- }
- };
- setScrollPositionClassName = () => {
- const node = this.bodyWrapRef.current;
- if (node && node.children && node.children.length) {
- 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');
- }
- }
- };
- syncTableWidth = () => {
- if (this.rootWrapRef && this.rootWrapRef.current) {
- this.setState({ tableWidth: this.rootWrapRef.current.getBoundingClientRect().width });
- }
- };
- renderSelection = (record = {} as any, inHeader = false): React.ReactNode => {
- const { rowSelection, disabledRowKeysSet } = this.state;
- if (rowSelection && typeof rowSelection === 'object') {
- const { selectedRowKeys = [], selectedRowKeysSet = new Set(), getCheckboxProps, disabled } = rowSelection;
- if (inHeader) {
- const columnKey = get(rowSelection, 'key', strings.DEFAULT_KEY_COLUMN_SELECTION);
- const allRowKeys = this.cachedFilteredSortedRowKeys;
- const allRowKeysSet = this.cachedFilteredSortedRowKeysSet;
- const allIsSelected = this.foundation.allIsSelected(selectedRowKeysSet, disabledRowKeysSet, allRowKeys);
- const hasRowSelected = this.foundation.hasRowSelected(selectedRowKeys, allRowKeysSet);
- return (
- <ColumnSelection
- aria-label={`${allIsSelected ? 'Deselect' : 'Select'} all rows`}
- disabled={disabled}
- key={columnKey}
- selected={allIsSelected}
- indeterminate={hasRowSelected && !allIsSelected}
- onChange={(status, e) => {
- this.toggleSelectAllRow(status, e);
- }}
- />
- );
- } else {
- const key = this.foundation.getRecordKey(record);
- const selected = selectedRowKeysSet.has(key);
- const checkboxPropsFn = () => (typeof getCheckboxProps === 'function' ? getCheckboxProps(record) : {});
- return (
- <ColumnSelection
- aria-label={`${selected ? 'Deselect' : 'Select'} this row`}
- getCheckboxProps={checkboxPropsFn}
- selected={selected}
- onChange={(status, e) => this.toggleSelectRow(status, key, e)}
- />
- );
- }
- }
- return null;
- };
- renderRowSelectionCallback = (text: string, record: RecordType = {} as RecordType) => this.renderSelection(record);
- renderTitleSelectionCallback = () => this.renderSelection(null, true);
- normalizeSelectionColumn = (props: { rowSelection?: TableStateRowSelection<RecordType>; prefixCls?: string } = {}) => {
- const { rowSelection, prefixCls } = props;
- let column: ColumnProps = {};
- if (rowSelection) {
- const needOmitSelectionKey = ['selectedRowKeys', 'selectedRowKeysSet'];
- column = { key: strings.DEFAULT_KEY_COLUMN_SELECTION };
- if (isObject(rowSelection)) {
- column = { ...column, ...omit(rowSelection, needOmitSelectionKey) };
- }
- column.className = classnames(column.className, `${prefixCls}-column-selection`);
- column.title = this.renderTitleSelectionCallback;
- column.render = this.renderRowSelectionCallback;
- }
- return column;
- };
- // If there is a scroll bar, manually construct a column and insert it into the header
- normalizeScrollbarColumn = (props: { scrollbarWidth?: number } = {}): { key: 'column-scrollbar'; width: number; fixed: 'right' } => {
- const { scrollbarWidth = 0 } = props;
- return {
- key: strings.DEFAULT_KEY_COLUMN_SCROLLBAR as 'column-scrollbar',
- width: scrollbarWidth,
- fixed: 'right',
- };
- };
- /**
- * render expand icon
- * @param {Object} record
- * @param {Boolean} isNested
- * @param {String} groupKey
- * @returns {ReactNode}
- */
- renderExpandIcon = (record = {}, isNested = false, groupKey: string | number = null) => {
- const { expandedRowKeys } = this.state;
- const { expandIcon } = this.props;
- const key =
- typeof groupKey === 'string' || typeof groupKey === 'number' ?
- groupKey :
- this.foundation.getRecordKey(record as RecordType);
- return (
- <ExpandedIcon
- key={key}
- componentType={isNested ? 'tree' : 'expand'}
- expanded={includes(expandedRowKeys, key)}
- expandIcon={expandIcon}
- onClick={(expanded, e) => this.handleRowExpanded(expanded, key, e)}
- />
- );
- };
- // @ts-ignore
- handleRowExpanded = (...args: any[]) => this.foundation.handleRowExpanded(...args);
- normalizeExpandColumn = (props: { prefixCls?: string; expandCellFixed?: ArrayElement<typeof strings.FIXED_SET>; expandIcon?: ExpandIcon } = {}) => {
- let column: ColumnProps = null;
- const { prefixCls, expandCellFixed, expandIcon } = props;
- column = { fixed: expandCellFixed, key: strings.DEFAULT_KEY_COLUMN_EXPAND };
- column.className = classnames(column.className, `${prefixCls}-column-expand`);
- column.render =
- expandIcon !== false ?
- (text = '', record, index) =>
- (this.adapter.mergedRowExpandable(record) ? this.renderExpandIcon(record) : null) :
- () => null;
- return column;
- };
- /**
- * Add sorting, filtering, and rendering functions to columns, and add column event handling
- * Title support function, passing parameters as {filter: node, sorter: node, selection: node}
- * @param {*} column
- */
- addFnsInColumn = (column: ColumnProps = {}) => {
- if (column && (column.sorter || column.filters || column.useFullRender)) {
- const { dataIndex, title: rawTitle, useFullRender } = column;
- const curQuery = this.foundation.getQuery(dataIndex);
- const titleMap: ColumnTitleProps = {};
- const titleArr = [];
- // useFullRender adds select buttons to each column
- if (useFullRender) {
- titleMap.selection = this.renderSelection(null, true);
- }
- const stateSortOrder = get(curQuery, 'sortOrder');
- const defaultSortOrder = get(curQuery, 'defaultSortOrder', false);
- const sortOrder = this.foundation.isSortOrderValid(stateSortOrder) ? stateSortOrder : defaultSortOrder;
- const TitleNode = typeof rawTitle !== 'function' && <React.Fragment key={strings.DEFAULT_KEY_COLUMN_TITLE}>{rawTitle as React.ReactNode}</React.Fragment>;
- if (typeof column.sorter === 'function' || column.sorter === true) {
- // In order to increase the click hot area of sorting, when sorting is required & useFullRender is false,
- // both the title and sorting areas are used as the click hot area for sorting。
- const sorter = (
- <ColumnSorter
- key={strings.DEFAULT_KEY_COLUMN_SORTER}
- sortOrder={sortOrder}
- onClick={e => this.foundation.handleSort(column, e)}
- title={TitleNode}
- />
- );
- useFullRender && (titleMap.sorter = sorter);
- titleArr.push(sorter);
- } else {
- titleArr.push(TitleNode);
- }
- const stateFilteredValue = get(curQuery, 'filteredValue');
- const defaultFilteredValue = get(curQuery, 'defaultFilteredValue');
- const filteredValue = stateFilteredValue ? stateFilteredValue : defaultFilteredValue;
- if ((Array.isArray(column.filters) && column.filters.length) || isValidElement(column.filterDropdown)) {
- const filter = (
- <ColumnFilter
- key={strings.DEFAULT_KEY_COLUMN_FILTER}
- {...curQuery}
- filteredValue={filteredValue}
- onFilterDropdownVisibleChange={(visible: boolean) => this.foundation.toggleShowFilter(dataIndex, visible)}
- onSelect={(data: OnSelectData) => this.foundation.handleFilterSelect(dataIndex, data)}
- />
- );
- useFullRender && (titleMap.filter = filter);
- titleArr.push(filter);
- }
- const newTitle =
- typeof rawTitle === 'function' ?
- () => rawTitle(titleMap) : titleArr;
- column = { ...column, title: newTitle };
- }
- return column;
- };
- toggleSelectRow = (selected: boolean, realKey: string | number, e: TableSelectionCellEvent) => {
- this.foundation.handleSelectRow(realKey, selected, e);
- };
- toggleSelectAllRow = (status: boolean, e: TableSelectionCellEvent) => {
- this.foundation.handleSelectAllRow(status, e);
- };
- /**
- * render pagination
- * @param {object} pagination
- * @param {object} propRenderPagination
- */
- renderPagination = (pagination: TablePaginationProps, propRenderPagination: RenderPagination) => {
- if (!pagination) {
- return null;
- }
- // use memoized pagination
- const mergedPagination = this.foundation.memoizedPagination(pagination);
- return (
- <LocaleConsumer componentName="Table">
- {(locale: TableLocale) => {
- const info = this.foundation.formatPaginationInfo(mergedPagination, locale.pageText);
- return <TablePagination info={info} pagination={mergedPagination} renderPagination={propRenderPagination} />;
- }}
- </LocaleConsumer>
- );
- };
- renderTitle = (props: { title?: ReactNode | ((dataSource?: RecordType[]) => ReactNode); prefixCls?: string; dataSource?: any[] } = {}) => {
- let { title } = props;
- const { prefixCls, dataSource } = props;
- if (typeof title === 'function') {
- title = title(dataSource);
- }
- return isValidElement(title) || typeof title === 'string' ? (
- <div className={`${prefixCls}-title`} x-semi-prop="title">{title}</div>
- ) : null;
- };
- renderEmpty = (props: { prefixCls?: string; empty?: ReactNode; dataSource?: RecordType[] } = {}) => {
- const { prefixCls, empty, dataSource } = props;
- const wrapCls = `${prefixCls}-placeholder`;
- const isEmpty = this.foundation.isEmpty(dataSource);
- if (!isEmpty) {
- return null;
- }
- return (
- <LocaleConsumer componentName="Table" key={'emptyText'}>
- {(locale: TableLocale, localeCode: string) => (
- <div className={wrapCls}>
- <div className={`${prefixCls}-empty`} x-semi-prop="empty">
- {empty || locale.emptyText}
- </div>
- </div>
- )}
- </LocaleConsumer>
- );
- };
- renderFooter = (props: { footer?: ReactNode | ((dataSource?: RecordType[]) => ReactNode); prefixCls?: string; dataSource?: RecordType[] } = {}) => {
- let { footer } = props;
- const { prefixCls, dataSource } = props;
- if (typeof footer === 'function') {
- footer = footer(dataSource);
- }
- return isValidElement(footer) || typeof footer === 'string' ? (
- <div className={`${prefixCls}-footer`} key="footer" x-semi-prop="footer">
- {footer}
- </div>
- ) : null;
- };
- renderMainTable = (props: any) => {
- const useFixedHeader = this.adapter.useFixedHeader();
- const emptySlot = this.renderEmpty(props);
- const table = [
- this.renderTable({
- ...props,
- fixed: false,
- useFixedHeader,
- headerRef: this._cacheHeaderRef,
- bodyRef: this.bodyWrapRef,
- includeHeader: !useFixedHeader,
- }),
- emptySlot,
- this.renderFooter(props),
- ];
- return table;
- };
- renderTable = (props: RenderTableProps<RecordType>) => {
- const {
- columns,
- filteredColumns,
- fixed,
- useFixedHeader,
- scroll,
- prefixCls,
- anyColumnFixed,
- includeHeader,
- showHeader,
- components,
- headerRef,
- bodyRef,
- onHeaderRow,
- rowSelection,
- dataSource,
- bodyHasScrollBar,
- disabledRowKeysSet,
- sticky
- } = props;
- const selectedRowKeysSet = get(rowSelection, 'selectedRowKeysSet', new Set());
- const headTable =
- fixed || useFixedHeader ? (
- <HeadTable
- key="head"
- anyColumnFixed={anyColumnFixed}
- ref={headerRef}
- columns={filteredColumns}
- prefixCls={prefixCls}
- fixed={fixed}
- handleBodyScroll={this.handleBodyScrollLeft}
- components={components}
- scroll={scroll}
- showHeader={showHeader}
- selectedRowKeysSet={selectedRowKeysSet}
- onHeaderRow={onHeaderRow}
- dataSource={dataSource}
- bodyHasScrollBar={bodyHasScrollBar}
- sticky={sticky}
- />
- ) : null;
- const bodyTable = (
- <BodyTable
- {...omit(props, ['rowSelection', 'headWidths']) as any}
- key="body"
- ref={bodyRef}
- columns={filteredColumns}
- fixed={fixed}
- prefixCls={prefixCls}
- handleWheel={this.handleWheel}
- handleBodyScroll={this.handleBodyScroll}
- anyColumnFixed={anyColumnFixed}
- includeHeader={includeHeader}
- showHeader={showHeader}
- scroll={scroll}
- components={components}
- store={this.store}
- selectedRowKeysSet={selectedRowKeysSet}
- disabledRowKeysSet={disabledRowKeysSet}
- />
- );
- return [headTable, bodyTable];
- };
- /**
- * When columns change, call this function to get the latest withFnsColumns
- * In addition to changes in columns, these props changes must be recalculated
- * - hideExpandedColumn
- * -rowSelection changes from trusy to falsy or rowSelection.hidden changes
- * -isAnyFixedRight(columns) || get(scroll,'y') changes
- *
- * columns变化时,调用此函数获取最新的withFnsColumns
- * 除了 columns 变化,这些 props 变化也要重新计算
- * - hideExpandedColumn
- * - rowSelection 从 trusy 变为 falsy 或 rowSelection.hidden 发生变化
- * - isAnyFixedRight(columns) || get(scroll, 'y') 发生变化
- *
- * @param {Array} queries
- * @param {Array} cachedColumns
- * @returns columns after adding extended functions
- */
- handleColumns = (queries: ColumnProps<RecordType>[], cachedColumns: ColumnProps<RecordType>[]) => {
- const { hideExpandedColumn, scroll, prefixCls, expandCellFixed, expandIcon, rowSelection } = this.props;
- const childrenColumnName = 'children';
- let columns: ColumnProps<RecordType>[] = cloneDeep(cachedColumns);
- // eslint-disable-next-line @typescript-eslint/no-shadow
- const addFns = (columns: ColumnProps<RecordType>[] = []) => {
- if (Array.isArray(columns) && columns.length) {
- each(columns, (column, index, originColumns) => {
- const newColumn = this.addFnsInColumn(column);
- const children = column[childrenColumnName];
- if (Array.isArray(children) && children.length) {
- const newChildren = [...children];
- addFns(newChildren);
- newColumn[childrenColumnName] = newChildren;
- }
- originColumns[index] = newColumn;
- });
- }
- };
- addFns(columns);
- // hideExpandedColumn=false render expand column separately
- if (!hideExpandedColumn) {
- const column = this.normalizeExpandColumn({ prefixCls, expandCellFixed, expandIcon });
- const destIndex = findIndex(columns, item => item.key === strings.DEFAULT_KEY_COLUMN_EXPAND);
- if (column) {
- if (destIndex > -1) {
- columns[destIndex] = { ...column, ...columns[destIndex] };
- } else if (column.fixed === 'right') {
- columns = [...columns, column];
- } else {
- columns = [column, ...columns];
- }
- }
- }
- // selection column
- if (rowSelection && !get(rowSelection, 'hidden')) {
- const destIndex = findIndex(columns, item => item.key === strings.DEFAULT_KEY_COLUMN_SELECTION);
- const column = this.normalizeSelectionColumn({ rowSelection, prefixCls });
- if (destIndex > -1) {
- columns[destIndex] = { ...column, ...columns[destIndex] };
- } else if (column.fixed === 'right') {
- columns = [...columns, column];
- } else {
- columns = [column, ...columns];
- }
- }
- assignColumnKeys(columns);
- return columns;
- };
- /**
- * Convert children to columns object
- * @param {Array} columns
- * @param {ReactNode} children
- * @returns {Array}
- */
- normalizeColumns = (columns: ColumnProps<RecordType>[], children: ReactNode) => {
- const normalColumns = cloneDeep(this.getColumns(columns, children));
- return normalColumns;
- };
- /**
- * Combine pagination and table paging processing functions
- */
- mergePagination = (pagination: TablePaginationProps) => {
- const newPagination = { onChange: this.foundation.setPage, ...pagination };
- return newPagination;
- };
- render() {
- let {
- scroll,
- prefixCls,
- className,
- style: wrapStyle = {},
- bordered,
- id,
- pagination: propPagination,
- virtualized,
- size,
- renderPagination: propRenderPagination,
- getVirtualizedListRef,
- loading,
- hideExpandedColumn,
- rowSelection: propRowSelection,
- ...rest
- } = this.props;
- let {
- rowSelection,
- expandedRowKeys,
- headWidths,
- tableWidth,
- pagination,
- dataSource,
- queries,
- cachedColumns,
- bodyHasScrollBar,
- } = this.state;
- wrapStyle = { ...wrapStyle };
- let columns: ColumnProps<RecordType>[];
- /**
- * As state.queries will change, the columns should be refreshed as a whole at this time
- * The scene of changes in queries
- * 1. Filter
- * 2. Pagination
- *
- * useFullRender needs to be passed to the user selection ReactNode, so columns need to be recalculated every time the selectedRowKeys changes
- * TODO: In the future, the selection passed to the user can be changed to the function type, allowing the user to execute the function to obtain the real-time status of the selection title
- *
- * 由于state.queries会发生变化,此时columns应该整体刷新
- * queries变化的场景
- * 1. 筛选
- * 2. 分页
- * useFullRender需要传给用户selection ReactNode,因此需要每次selectedRowKeys变化时重新计算columns
- * TODO: 未来可以将传给用户的selection改为函数类型,让用户执行函数获取selection title的实时状态
- */
- if (!this.adapter.isAnyColumnUseFullRender(queries)) {
- const rowSelectionUpdate: boolean = propRowSelection && !get(propRowSelection, 'hidden');
- columns = this.foundation.memoizedWithFnsColumns(
- queries,
- cachedColumns,
- rowSelectionUpdate,
- hideExpandedColumn,
- // Update the columns after the body scrollbar changes to ensure that the head and body are aligned
- bodyHasScrollBar
- );
- } else {
- columns = this.handleColumns(queries, cachedColumns);
- }
- const filteredColumns: ColumnProps<RecordType>[] = this.foundation.memoizedFilterColumns(columns);
- const flattenFnsColumns: ColumnProps<RecordType>[] = this.foundation.memoizedFlattenFnsColumns(columns);
- const anyColumnFixed = this.adapter.isAnyColumnFixed(columns);
- /**
- * - If it is the first page break, you need to calculate the current page
- * - If it is manual paging, call foundation to modify the state
- *
- * TODO: After merging issue 1007, you can place it in the constructor to complete
- * The reason is that #1007 exposes the parameters required by getCurrentPageData in the constructor
- */
- if (isNull(dataSource)) {
- const pageData: BasePageData<RecordType> = this.foundation.getCurrentPageData(this.props.dataSource);
- dataSource = pageData.dataSource;
- pagination = pageData.pagination;
- }
- const props = {
- ...rest,
- ...this.state,
- // props not in rest
- virtualized,
- scroll,
- prefixCls,
- size,
- hideExpandedColumn,
- // renamed state
- columns,
- // calculated value
- anyColumnFixed,
- rowExpandable: this.mergedRowExpandable,
- pagination,
- dataSource,
- rowSelection,
- expandedRowKeys,
- renderExpandIcon: this.renderExpandIcon,
- filteredColumns,
- };
- const x = get(scroll, 'x');
- const y = get(scroll, 'y');
- if (virtualized) {
- if (typeof wrapStyle.width !== 'number') {
- wrapStyle.width = x;
- }
- }
- const wrapCls = classnames({
- [`${prefixCls}-${strings.SIZE_SMALL}`]: size === strings.SIZE_SMALL,
- [`${prefixCls}-${strings.SIZE_MIDDLE}`]: size === strings.SIZE_MIDDLE,
- [`${prefixCls}-virtualized`]: Boolean(virtualized),
- [`${prefixCls}-bordered`]: bordered,
- [`${prefixCls}-fixed-header`]: Boolean(y),
- [`${prefixCls}-scroll-position-left`]: ['both', 'left'].includes(this.position),
- [`${prefixCls}-scroll-position-right`]: ['both', 'right'].includes(this.position),
- });
- // pagination
- const tablePagination = pagination && propPagination ? this.renderPagination(pagination as TablePaginationProps, propRenderPagination) : null;
- const paginationPosition = get(propPagination, 'position', 'bottom');
- const tableContextValue: TableContextProps = {
- ...this.context,
- headWidths,
- tableWidth,
- anyColumnFixed,
- flattenedColumns: flattenFnsColumns,
- renderExpandIcon: this.renderExpandIcon,
- renderSelection: this.renderSelection,
- setHeadWidths: this.setHeadWidths,
- getHeadWidths: this.getHeadWidths,
- getCellWidths: this.getCellWidths,
- handleRowExpanded: this.handleRowExpanded,
- getVirtualizedListRef,
- setBodyHasScrollbar: this.setBodyHasScrollbar,
- };
- return (
- <div
- ref={this.rootWrapRef}
- className={classnames(className, `${prefixCls}-wrapper`)}
- data-column-fixed={anyColumnFixed}
- style={wrapStyle}
- id={id}
- >
- <TableContextProvider {...tableContextValue}>
- <Spin spinning={loading} size="large">
- <div ref={this.wrapRef} className={wrapCls}>
- <React.Fragment key={'pagination-top'}>
- {['top', 'both'].includes(paginationPosition) ? tablePagination : null}
- </React.Fragment>
- {this.renderTitle({ title: (props as any).title, dataSource: props.dataSource, prefixCls: props.prefixCls })}
- <div className={`${prefixCls}-container`}>{this.renderMainTable({ ...props })}</div>
- <React.Fragment key={'pagination-bottom'}>
- {['bottom', 'both'].includes(paginationPosition) ? tablePagination : null}
- </React.Fragment>
- </div>
- </Spin>
- </TableContextProvider>
- </div>
- );
- }
- }
- export default Table;
|