Skip to content

Commit

Permalink
fix(Cascader): suport immutable dataSource, close alibaba-fusion#1558
Browse files Browse the repository at this point in the history
  • Loading branch information
皆虚 authored and youluna committed May 14, 2021
1 parent 85600e1 commit c2f1927
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 86 deletions.
1 change: 1 addition & 0 deletions docs/cascader-select/index.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ CascaderSelect consists of Select and Cascader. Cascader are hidden in a pop up
| popupContainer | container of dropdown | String/Function | - |
| popupProps | properties of Popup | Object | {} |
| followTrigger | follow Trigger or not | Boolean | - |
| immutable | whether allow immutable dataSource | Boolean | false | 1.23 |

<!-- api-extra-start -->

Expand Down
89 changes: 45 additions & 44 deletions docs/cascader-select/index.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion docs/cascader/index.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
- category: Components
- family: DataDisplay
- chinese: 级联
- type: 基本
- type: Baisc

---

Expand Down Expand Up @@ -38,6 +38,7 @@
| listClassName | class name of list | String | - |
| itemRender | render function of item<br><br>**signatures**:<br>Function(data: Object) => ReactNode<br>**params**:<br>_data_: {Object} data<br>**returns**:<br>{ReactNode} content of item<br> | Function | item => item.label |
| loadData | asynchronous data loading function<br><br>**signatures**:<br>Function(data: Object) => void<br>**params**:<br>_data_: {Object} clicked item | Function | - |
| immutable | whether support immutable dataSource | Boolean | false |

<!-- api-extra-start -->

Expand Down
39 changes: 20 additions & 19 deletions docs/cascader/index.md

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion src/cascader-select/cascader-select.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ class CascaderSelect extends Component {
* @param {Array<data>} value 选择值 { label: , value:}
*/
renderPreview: PropTypes.func,
/**
* 是否是不可变数据
* @version 1.23
*/
immutable: PropTypes.bool,
};

static defaultProps = {
Expand Down Expand Up @@ -281,6 +286,7 @@ class CascaderSelect extends Component {
defaultVisible: false,
onVisibleChange: () => {},
popupProps: {},
immutable: false,
};

constructor(props, context) {
Expand Down Expand Up @@ -742,6 +748,7 @@ class CascaderSelect extends Component {
resultRender,
readOnly,
itemRender,
immutable,
} = this.props;
const { value } = this.state;

Expand All @@ -761,6 +768,7 @@ class CascaderSelect extends Component {
listClassName,
loadData,
itemRender,
immutable,
};

if ('expandedValue' in this.props) {
Expand Down Expand Up @@ -871,7 +879,6 @@ class CascaderSelect extends Component {
visible,
onVisibleChange: this.handleVisibleChange,
showSearch,
// searchValue,
onSearch: this.handleSearch,
onKeyDown: this.handleKeyDown,
popupContent,
Expand Down
29 changes: 27 additions & 2 deletions src/cascader/cascader.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { polyfill } from 'react-lifecycles-compat';
import cloneDeep from 'lodash.clonedeep';
import cx from 'classnames';
import Menu from '../menu';
import { func, obj, dom } from '../util';
Expand All @@ -19,12 +20,15 @@ const { bindCtx } = func;
const { pickOthers } = obj;
const { addClass, removeClass, setStyle, getStyle } = dom;

// 数据打平
const flatDataSource = (data, prefix = '0', v2n = {}, p2n = {}) => {
data.forEach((item, index) => {
const { value, children } = item;
const pos = `${prefix}-${index}`;
const newValue = String(value);

item.value = newValue;

v2n[newValue] = p2n[pos] = {
...item,
pos,
Expand All @@ -39,6 +43,20 @@ const flatDataSource = (data, prefix = '0', v2n = {}, p2n = {}) => {
return { v2n, p2n };
};

function preHandleData(data, immutable) {
const _data = immutable ? cloneDeep(data) : data;

try {
return flatDataSource(_data);
} catch (err) {
if ((err.message || '').match('Cannot assign to read only property')) {
// eslint-disable-next-line no-console
console.error(err.message, 'try to set immutable to true to allow immutable dataSource');
}
throw err;
}
}

const getExpandedValue = (v, _v2n, _p2n) => {
if (!v || !_v2n[v]) {
return [];
Expand Down Expand Up @@ -169,6 +187,11 @@ class Cascader extends Component {
filteredPaths: PropTypes.array,
filteredListStyle: PropTypes.object,
resultRender: PropTypes.func,
/**
* 是否是不可变数据
* @version 1.23
*/
immutable: PropTypes.bool,
};

static defaultProps = {
Expand All @@ -184,6 +207,7 @@ class Cascader extends Component {
useVirtual: false,
checkStrictly: false,
itemRender: item => item.label,
immutable: false,
};

constructor(props, context) {
Expand All @@ -199,9 +223,10 @@ class Cascader extends Component {
checkStrictly,
canOnlyCheckLeaf,
loadData,
immutable,
} = props;

const { v2n, p2n } = flatDataSource(dataSource);
const { v2n, p2n } = preHandleData(dataSource, immutable);

let normalizedValue = normalizeValue(typeof value === 'undefined' ? defaultValue : value);

Expand Down Expand Up @@ -234,7 +259,7 @@ class Cascader extends Component {
}

static getDerivedStateFromProps(props, state) {
const { v2n, p2n } = flatDataSource(props.dataSource);
const { v2n, p2n } = preHandleData(props.dataSource, props.immutable);
const states = {};

if ('value' in props) {
Expand Down
21 changes: 21 additions & 0 deletions test/cascader-select/index-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ import '../../src/cascader-select/style.js';

Enzyme.configure({ adapter: new Adapter() });

function freeze(dataSource) {
return dataSource.map(item => {
const { children } = item;
children && freeze(children);
return Object.freeze({ ...item });
});
}

const ChinaArea = [
{
value: '2973',
Expand Down Expand Up @@ -505,6 +513,19 @@ describe('CascaderSelect', () => {
);
assert(findRealItem(document.querySelector('.myCascaderSelect'), 2, 1));
});

it('should support immutable data', () => {
wrapper = mount(
<CascaderSelect
immutable
popupProps={{ className: 'myCascaderSelect' }}
dataSource={freeze(ChinaArea)}
expandedValue={['2973', '2974']}
defaultVisible
/>
);
assert(findRealItem(document.querySelector('.myCascaderSelect'), 2, 1));
});
});

function findItem(menuIndex, itemIndex) {
Expand Down
22 changes: 19 additions & 3 deletions test/cascader/index-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ReactTestUtils from 'react-dom/test-utils';
import Enzyme, { mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import assert from 'power-assert';
import cloneDeep from 'lodash.clonedeep';
import { KEYCODE } from '../../src/util';
import Cascader from '../../src/cascader';
import '../../src/cascader/style.js';
Expand All @@ -13,6 +14,14 @@ import '../../src/cascader/style.js';

Enzyme.configure({ adapter: new Adapter() });

function freeze(dataSource) {
return dataSource.map(item => {
const { children } = item;
children && freeze(children);
return Object.freeze({ ...item });
});
}

const ChinaArea = [
{
value: '2973',
Expand Down Expand Up @@ -190,8 +199,11 @@ describe('Cascader', () => {
});

it('should support remove title', () => {
ChinaArea[0].title = '';
wrapper = mount(<Cascader dataSource={ChinaArea} />);
const data = cloneDeep(ChinaArea);

data[0].title = '';

wrapper = mount(<Cascader dataSource={data} />);
assert(
wrapper
.find('.next-menu-item')
Expand All @@ -206,7 +218,7 @@ describe('Cascader', () => {
.getDOMNode()
.getAttribute('title') === '四川'
);
delete ChinaArea[0].title;
delete data[0].title;
});

it('could only select leaf item when set canOnlySelectLeaf to true', () => {
Expand Down Expand Up @@ -576,6 +588,10 @@ describe('Cascader', () => {
document.body.removeChild(div);
});

it('support immutable data source', () => {
wrapper = mount(<Cascader id="cascader-style" dataSource={freeze(ChinaArea)} immutable />);
});

it('should support rtl', () => {
const div = document.createElement('div');
document.body.appendChild(div);
Expand Down
16 changes: 7 additions & 9 deletions types/cascader-select/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,7 @@ export interface CascaderSelectProps extends CascaderProps, HTMLAttributesWeak,
/**
* 选中值改变时触发的回调函数
*/
onChange?: (
value: string | Array<string>,
data: data | Array<data>,
extra: extra
) => void;
onChange?: (value: string | Array<string>, data: data | Array<data>, extra: extra) => void;

/**
* 默认展开值,如果不设置,组件内部会根据 defaultValue/value 进行自动设置
Expand Down Expand Up @@ -211,9 +207,11 @@ export interface CascaderSelectProps extends CascaderProps, HTMLAttributesWeak,
* 透传到 Popup 的属性对象
*/
popupProps?: PopupProps;

/**
* 是否是不可变数据
*/
immutable: boolean;
}

export default class CascaderSelect extends React.Component<
CascaderSelectProps,
any
> {}
export default class CascaderSelect extends React.Component<CascaderSelectProps, any> {}
15 changes: 8 additions & 7 deletions types/cascader/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type data = {
checkboxDisabled?: boolean;
children?: Array<data>;
[propName: string]: any;
}
};

type extra = {
/**
Expand All @@ -38,7 +38,7 @@ type extra = {
* 多选时半选的数据
*/
indeterminateData?: Array<data>;
}
};

export interface CascaderProps extends HTMLAttributesWeak, CommonProps {
/**
Expand All @@ -59,11 +59,7 @@ export interface CascaderProps extends HTMLAttributesWeak, CommonProps {
/**
* 选中值改变时触发的回调函数
*/
onChange?: (
value: string | Array<string>,
data: data | Array<data>,
extra: extra
) => void;
onChange?: (value: string | Array<string>, data: data | Array<data>, extra: extra) => void;

/**
* (非受控)默认展开值,如果不设置,组件内部会根据 defaultValue/value 进行自动设置
Expand Down Expand Up @@ -129,6 +125,11 @@ export interface CascaderProps extends HTMLAttributesWeak, CommonProps {
* 异步加载数据函数,source是原始对象
*/
loadData?: (data: data, source: data) => void;

/**
* 是否是不可变数据
*/
immutable: boolean;
}

export default class Cascader extends React.Component<CascaderProps, any> {}

0 comments on commit c2f1927

Please sign in to comment.