Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,10 @@ React.render(<Cascader options={options} />, container);
<td>callback when finishing cascader select</td>
</tr>
<tr>
<td>onSelect</td>
<td>Function(selectedOptions, done)</td>
<td>loadData</td>
<td>Function(selectedOptions)</td>
<td></td>
<td>callback when click any option, argument "done" use for async select</td>
<td>callback when click any option, use for loading more options</td>
</tr>
<tr>
<td>expandTrigger</td>
Expand Down
48 changes: 23 additions & 25 deletions examples/dynamic-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import ReactDOM from 'react-dom';

const addressOptions = [{
'label': '福建',
isLeaf: false,
'value': 'fj',
}, {
'label': '浙江',
isLeaf: false,
'value': 'zj',
}];

Expand All @@ -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 (
<Cascader options={this.state.options}
onSelect={this.onSelect}
onChange={this.onChange}>
<input value={this.state.inputValue} readOnly />
loadData={this.loadData}
onChange={this.onChange}>
<input value={this.state.inputValue} readOnly/>
</Cascader>
);
},
Expand Down
2 changes: 1 addition & 1 deletion src/Cascader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class Cascader extends React.Component {
handleChange(options) {
this.props.onChange(
options.map(o => o.value),
options,
options
);
this.setPopupVisible(false);
}
Expand Down
134 changes: 71 additions & 63 deletions src/Menus.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,78 @@ class Menus extends React.Component {
value: initialValue,
};
}

componentWillReceiveProps(nextProps) {
if ('value' in nextProps) {
this.setState({
value: nextProps.value || [],
});
}
// 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 (
<li key={option.value}
className={menuItemCls}
title={option.label}
{...expandProps}>
{option.label}
</li>
);
}

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()
Expand All @@ -37,81 +91,35 @@ 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 (
<div>
{this.getShowOptions().map((options, menuIndex) =>
<ul className={`${prefixCls}-menu`} key={menuIndex}>
{options.map(option => {
let menuItemCls = `${prefixCls}-menu-item`;
if (this.isActiveOption(option)) {
menuItemCls += ` ${prefixCls}-menu-item-active`;
}
const handleSelect = this.handleSelect.bind(this, option, menuIndex);
let expandProps = {
onClick: handleSelect,
};
if (expandTrigger === 'hover' &&
option.children &&
option.children.length > 0) {
expandProps = {
onMouseEnter: handleSelect,
};
menuItemCls += ` ${prefixCls}-menu-item-expand`;
}
return (
<li key={option.value}
className={menuItemCls}
title={option.label}
{...expandProps}>
{option.label}
</li>
);
})}
</ul>
)}
<ul className={`${prefixCls}-menu`} key={menuIndex}>
{
options.map(option => {
return this.getOption(option, menuIndex);
})
}
</ul>
)}
</div>
);
}
}

Menus.defaultProps = {
options: [],
onChange() {},
onSelect() {},
onChange() {
},
onSelect() {
},
prefixCls: 'rc-cascader-menus',
visible: false,
expandTrigger: 'click',
Expand All @@ -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,
};

Expand Down
6 changes: 2 additions & 4 deletions tests/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

Expand Down Expand Up @@ -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();
});

Expand Down