diff --git a/README.md b/README.md index 0b3b986e..41c3592b 100644 --- a/README.md +++ b/README.md @@ -143,10 +143,10 @@ React.render(, container); callback when finishing cascader select - onSelect - Function(selectedOptions, done) + loadData + Function(selectedOptions) - callback when click any option, argument "done" use for async select + callback when click any option, use for loading more options expandTrigger diff --git a/examples/dynamic-options.js b/examples/dynamic-options.js index 24ff96ba..eea587f0 100644 --- a/examples/dynamic-options.js +++ b/examples/dynamic-options.js @@ -6,9 +6,11 @@ import ReactDOM from 'react-dom'; const addressOptions = [{ 'label': '福建', + isLeaf: false, 'value': 'fj', }, { 'label': '浙江', + isLeaf: false, 'value': 'zj', }]; @@ -19,39 +21,35 @@ const Demo = React.createClass({ options: addressOptions, }; }, - onSelect(selectedOptions, done) { - const options = this.state.options; - const targetOption = selectedOptions[selectedOptions.length - 1]; - if (selectedOptions.length === 1 && !targetOption.children) { - targetOption.label += ' loading'; - // 动态加载下级数据 - setTimeout(() => { - targetOption.label = targetOption.label.replace(' loading', ''); - targetOption.children = [{ - 'label': targetOption.label + '动态加载1', - 'value': 'dynamic1', - }, { - 'label': targetOption.label + '动态加载2', - 'value': 'dynamic2', - }]; - this.setState({ options }); - done(); - }, 1000); - return; - } - done(); - }, onChange(value, selectedOptions) { this.setState({ inputValue: selectedOptions.map(o => o.label).join(', '), }); }, + loadData(selectedOptions) { + const options = this.state.options; + const targetOption = selectedOptions[selectedOptions.length - 1]; + targetOption.label += ' loading'; + // 动态加载下级数据 + setTimeout(() => { + targetOption.label = targetOption.label.replace(' loading', ''); + targetOption.children = [{ + 'label': targetOption.label + '动态加载1', + 'value': 'dynamic1', + }, { + 'label': targetOption.label + '动态加载2', + 'value': 'dynamic2', + }]; + this.setState({options}); + }, 1000); + this.setState({options}); + }, render() { return ( - + loadData={this.loadData} + onChange={this.onChange}> + ); }, diff --git a/src/Cascader.jsx b/src/Cascader.jsx index 2bbd800b..12f946f2 100644 --- a/src/Cascader.jsx +++ b/src/Cascader.jsx @@ -53,7 +53,7 @@ class Cascader extends React.Component { handleChange(options) { this.props.onChange( options.map(o => o.value), - options, + options ); this.setPopupVisible(false); } diff --git a/src/Menus.jsx b/src/Menus.jsx index 8262ce28..f8bdad67 100644 --- a/src/Menus.jsx +++ b/src/Menus.jsx @@ -11,6 +11,7 @@ class Menus extends React.Component { value: initialValue, }; } + componentWillReceiveProps(nextProps) { if ('value' in nextProps) { this.setState({ @@ -18,17 +19,70 @@ class Menus extends React.Component { }); } // sync activeValue with value when panel open - if (nextProps.visible) { + if (nextProps.visible && !this.props.visible) { this.setState({ activeValue: this.state.value, }); } } + + onSelect(targetOption, menuIndex) { + if (!targetOption) { + return; + } + let activeValue = this.state.activeValue; + activeValue = activeValue.slice(0, menuIndex + 1); + activeValue[menuIndex] = targetOption.value; + const activeOptions = this.getActiveOptions(activeValue); + if (targetOption.isLeaf === false && !targetOption.children) { + if (this.props.loadData) { + this.setState({activeValue}); + this.props.loadData(activeOptions); + } + } else if (!targetOption.children || !targetOption.children.length) { + this.props.onChange(activeOptions); + // finish select + // should set value to activeValue + this.setState({value: activeValue}); + } else { + this.setState({activeValue}); + } + } + + getOption(option, menuIndex) { + const { prefixCls, expandTrigger } = this.props; + let menuItemCls = `${prefixCls}-menu-item`; + if (this.isActiveOption(option)) { + menuItemCls += ` ${prefixCls}-menu-item-active`; + } + const onSelect = this.onSelect.bind(this, option, menuIndex); + let expandProps = { + onClick: onSelect, + }; + if (expandTrigger === 'hover' && + option.children && + option.children.length > 0) { + expandProps = { + onMouseEnter: onSelect, + }; + menuItemCls += ` ${prefixCls}-menu-item-expand`; + } + return ( +
  • + {option.label} +
  • + ); + } + getActiveOptions(values) { const activeValue = values || this.state.activeValue; const options = this.props.options; return arrayTreeFilter(options, (o, level) => o.value === activeValue[level]); } + getShowOptions() { const { options } = this.props; const result = this.getActiveOptions() @@ -37,72 +91,24 @@ class Menus extends React.Component { result.unshift(options); return result; } - handleSelect(targetOption, menuIndex) { - if (!targetOption) { - return; - } - let activeValue = this.state.activeValue; - activeValue = activeValue.slice(0, menuIndex + 1); - activeValue[menuIndex] = targetOption.value; - const activeOptions = this.getActiveOptions(activeValue); - const selectCallback = () => { - if (!targetOption.children || targetOption.children.length === 0) { - this.props.onChange(activeOptions); - // finish select - // should set value to activeValue - this.setState({ value: activeValue }); - } - this.forceUpdate(); - }; - // specify the last argument `done`: - // onSelect(selectedOptions, done) - // it means async select - if (this.props.onSelect.length >= 2) { - this.props.onSelect(activeOptions, selectCallback); - } else { - this.props.onSelect(activeOptions); - selectCallback(); - } - // set activeValue, waiting for async callback - this.setState({ activeValue }); - } + isActiveOption(option) { return this.state.activeValue.some(value => value === option.value); } + render() { - const { prefixCls, expandTrigger } = this.props; + const { prefixCls } = this.props; return (
    {this.getShowOptions().map((options, menuIndex) => - - )} + + )}
    ); } @@ -110,8 +116,10 @@ class Menus extends React.Component { Menus.defaultProps = { options: [], - onChange() {}, - onSelect() {}, + onChange() { + }, + onSelect() { + }, prefixCls: 'rc-cascader-menus', visible: false, expandTrigger: 'click', @@ -122,7 +130,7 @@ Menus.propTypes = { prefixCls: React.PropTypes.string, expandTrigger: React.PropTypes.string, onChange: React.PropTypes.func, - onSelect: React.PropTypes.func, + loadData: React.PropTypes.func, visible: React.PropTypes.bool, }; diff --git a/tests/index.spec.js b/tests/index.spec.js index 0f299cae..f9d9e420 100644 --- a/tests/index.spec.js +++ b/tests/index.spec.js @@ -71,9 +71,8 @@ describe('Cascader', () => { expect(selectedValue).not.to.be.ok(); Simulate.click(menu3Items[0]); - expect(menu3Items[0].className).to.contain('rc-cascader-menu-item-active'); - expect(selectedValue.join(',')).to.be('fj,fuzhou,mawei'); expect(instance.state.popupVisible).not.to.be.ok(); + expect(selectedValue.join(',')).to.be('fj,fuzhou,mawei'); done(); }); @@ -126,9 +125,8 @@ describe('Cascader', () => { expect(selectedValue).not.to.be.ok(); Simulate.click(menu3Items[0]); - expect(menu3Items[0].className).to.contain('rc-cascader-menu-item-active'); - expect(selectedValue.join(',')).to.be('fj,fuzhou,mawei'); expect(instance.state.popupVisible).not.to.be.ok(); + expect(selectedValue.join(',')).to.be('fj,fuzhou,mawei'); done(); });