Skip to content

Commit

Permalink
Merge pull request #23 from 405go/master
Browse files Browse the repository at this point in the history
add labelField and valueField for custom label field name and value field name
  • Loading branch information
afc163 authored Apr 27, 2018
2 parents 776b2c3 + 3186e36 commit f893513
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 20 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ React.render(
<td></td>
<td>style object for each cascader pop menu</td>
</tr>
<tr>
<td>filedNames</td>
<td>Object</td>
<td>{ label: 'label', value: 'value', children: 'children' }</td>
<td>custom field name for label and value and children</td>
</tr>
</tbody>
</table>

Expand Down
1 change: 1 addition & 0 deletions examples/custom-field-name.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
placeholder
73 changes: 73 additions & 0 deletions examples/custom-field-name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/* eslint-disable no-console */
import 'rc-cascader/assets/index.less';
import Cascader from 'rc-cascader';
import React from 'react';
import ReactDOM from 'react-dom';

const addressOptions = [{
name: '福建',
code: 'fj',
nodes: [{
name: '福州',
code: 'fuzhou',
nodes: [{
name: '马尾',
code: 'mawei',
}],
}, {
name: '泉州',
code: 'quanzhou',
}],
}, {
name: '浙江',
code: 'zj',
nodes: [{
name: '杭州',
code: 'hangzhou',
nodes: [{
name: '余杭',
code: 'yuhang',
}],
}],
}, {
name: '北京',
code: 'bj',
nodes: [{
name: '朝阳区',
code: 'chaoyang',
}, {
name: '海淀区',
code: 'haidian',
disabled: true,
}],
}];

class Demo extends React.Component {
state = {
inputValue: '',
}

onChange = (value, selectedOptions) => {
console.log(value, selectedOptions);
this.setState({
inputValue: selectedOptions.map(o => o.name).join(', '),
});
}

render() {
return (
<Cascader
options={addressOptions}
onChange={this.onChange}
filedNames={{ label: 'name', value: 'code', children: 'nodes' }}
>
<input
placeholder="please select address"
value={this.state.inputValue}
/>
</Cascader>
);
}
}

ReactDOM.render(<Demo />, document.getElementById('__react-content'));
41 changes: 30 additions & 11 deletions src/Cascader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class Cascader extends Component {
activeValue: initialValue,
value: initialValue,
};
this.defaultFiledNames = { label: 'label', value: 'value', children: 'children' };
}
componentWillReceiveProps(nextProps) {
if ('value' in nextProps && !shallowEqualArrays(this.props.value, nextProps.value)) {
Expand All @@ -79,17 +80,27 @@ class Cascader extends Component {
getPopupDOMNode() {
return this.trigger.getPopupDomNode();
}
getFieldName(name) {
const { defaultFiledNames } = this;
const { filedNames } = this.props;
// 防止只设置单个属性的名字
return filedNames[name] || defaultFiledNames[name];
}
getCurrentLevelOptions() {
const { options } = this.props;
const { activeValue = [] } = this.state;
const result = arrayTreeFilter(options, (o, level) => o.value === activeValue[level]);
const result = arrayTreeFilter(options,
(o, level) => o[this.getFieldName('value')] === activeValue[level],
{ childrenKeyName: this.getFieldName('children') });
if (result[result.length - 2]) {
return result[result.length - 2].children;
return result[result.length - 2][this.getFieldName('children')];
}
return [...options].filter(o => !o.disabled);
}
getActiveOptions(activeValue) {
return arrayTreeFilter(this.props.options, (o, level) => o.value === activeValue[level]);
return arrayTreeFilter(this.props.options,
(o, level) => o[this.getFieldName('value')] === activeValue[level],
{ childrenKeyName: this.getFieldName('children') });
}
setPopupVisible = (popupVisible) => {
if (!('popupVisible' in this.props)) {
Expand All @@ -105,7 +116,7 @@ class Cascader extends Component {
}
handleChange = (options, setProps, e) => {
if (e.type !== 'keydown' || e.keyCode === KeyCode.ENTER) {
this.props.onChange(options.map(o => o.value), options);
this.props.onChange(options.map(o => o[this.getFieldName('value')]), options);
this.setPopupVisible(setProps.visible);
}
}
Expand All @@ -124,9 +135,9 @@ class Cascader extends Component {
}
let { activeValue } = this.state;
activeValue = activeValue.slice(0, menuIndex + 1);
activeValue[menuIndex] = targetOption.value;
activeValue[menuIndex] = targetOption[this.getFieldName('value')];
const activeOptions = this.getActiveOptions(activeValue);
if (targetOption.isLeaf === false && !targetOption.children && loadData) {
if (targetOption.isLeaf === false && !targetOption[this.getFieldName('children')] && loadData) {
if (changeOnSelect) {
this.handleChange(activeOptions, { visible: true }, e);
}
Expand All @@ -135,7 +146,8 @@ class Cascader extends Component {
return;
}
const newState = {};
if (!targetOption.children || !targetOption.children.length) {
if (!targetOption[this.getFieldName('children')]
|| !targetOption[this.getFieldName('children')].length) {
this.handleChange(activeOptions, { visible: false }, e);
// set value to activeValue when select leaf option
newState.value = activeValue;
Expand Down Expand Up @@ -168,7 +180,9 @@ class Cascader extends Component {
const activeValue = [...this.state.activeValue];
const currentLevel = activeValue.length - 1 < 0 ? 0 : activeValue.length - 1;
const currentOptions = this.getCurrentLevelOptions();
const currentIndex = currentOptions.map(o => o.value).indexOf(activeValue[currentLevel]);
const currentIndex = currentOptions
.map(o => o[this.getFieldName('value')])
.indexOf(activeValue[currentLevel]);
if (e.keyCode !== KeyCode.DOWN &&
e.keyCode !== KeyCode.UP &&
e.keyCode !== KeyCode.LEFT &&
Expand Down Expand Up @@ -200,12 +214,14 @@ class Cascader extends Component {
} else {
nextIndex = 0;
}
activeValue[currentLevel] = currentOptions[nextIndex].value;
activeValue[currentLevel] = currentOptions[nextIndex][this.getFieldName('value')];
} else if (e.keyCode === KeyCode.LEFT || e.keyCode === KeyCode.BACKSPACE) {
activeValue.splice(activeValue.length - 1, 1);
} else if (e.keyCode === KeyCode.RIGHT) {
if (currentOptions[currentIndex] && currentOptions[currentIndex].children) {
activeValue.push(currentOptions[currentIndex].children[0].value);
if (currentOptions[currentIndex]
&& currentOptions[currentIndex][this.getFieldName('children')]) {
activeValue.push(currentOptions[currentIndex]
[this.getFieldName('children')][0][this.getFieldName('value')]);
}
} else if (e.keyCode === KeyCode.ESC) {
this.setPopupVisible(false);
Expand Down Expand Up @@ -239,6 +255,7 @@ class Cascader extends Component {
menus = (
<Menus
{...this.props}
defaultFiledNames={this.defaultFiledNames}
value={this.state.value}
activeValue={this.state.activeValue}
onSelect={this.handleMenuSelect}
Expand Down Expand Up @@ -284,6 +301,7 @@ Cascader.defaultProps = {
popupPlacement: 'bottomLeft',
builtinPlacements: BUILT_IN_PLACEMENTS,
expandTrigger: 'click',
filedNames: { label: 'label', value: 'value', children: 'children' },
};

Cascader.propTypes = {
Expand All @@ -305,6 +323,7 @@ Cascader.propTypes = {
children: PropTypes.node,
onKeyDown: PropTypes.func,
expandTrigger: PropTypes.string,
filedNames: PropTypes.object,
};

export default Cascader;
27 changes: 18 additions & 9 deletions src/Menus.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@ class Menus extends React.Component {
this.scrollActiveItemToView();
}
}

getFieldName(name) {
const { filedNames, defaultFiledNames } = this.props;
// 防止只设置单个属性的名字
return filedNames[name] || defaultFiledNames[name];
}
getOption(option, menuIndex) {
const { prefixCls, expandTrigger } = this.props;
const onSelect = this.props.onSelect.bind(this, option, menuIndex);
let expandProps = {
onClick: onSelect,
};
let menuItemCls = `${prefixCls}-menu-item`;
const hasChildren = option.children && option.children.length > 0;
const hasChildren = option[this.getFieldName('children')]
&& option[this.getFieldName('children')].length > 0;
if (hasChildren || option.isLeaf === false) {
menuItemCls += ` ${prefixCls}-menu-item-expand`;
}
Expand All @@ -51,31 +56,33 @@ class Menus extends React.Component {
let title = '';
if (option.title) {
title = option.title;
} else if (typeof option.label === 'string') {
title = option.label;
} else if (typeof option[this.getFieldName('label')] === 'string') {
title = option[this.getFieldName('label')];
}
return (
<li
key={option.value}
key={option[this.getFieldName('value')]}
className={menuItemCls}
title={title}
{...expandProps}
>
{option.label}
{option[this.getFieldName('label')]}
</li>
);
}

getActiveOptions(values) {
const activeValue = values || this.props.activeValue;
const options = this.props.options;
return arrayTreeFilter(options, (o, level) => o.value === activeValue[level]);
return arrayTreeFilter(options,
(o, level) => o[this.getFieldName('value')] === activeValue[level],
{ childrenKeyName: this.getFieldName('children') });
}

getShowOptions() {
const { options } = this.props;
const result = this.getActiveOptions()
.map(activeOption => activeOption.children)
.map(activeOption => activeOption[this.getFieldName('children')])
.filter(activeOption => !!activeOption);
result.unshift(options);
return result;
Expand Down Expand Up @@ -108,7 +115,7 @@ class Menus extends React.Component {

isActiveOption(option, menuIndex) {
const { activeValue = [] } = this.props;
return activeValue[menuIndex] === option.value;
return activeValue[menuIndex] === option[this.getFieldName('value')];
}

saveMenuItem = (index) => (node) => {
Expand Down Expand Up @@ -148,6 +155,8 @@ Menus.propTypes = {
onSelect: PropTypes.func,
visible: PropTypes.bool,
dropdownMenuColumnStyle: PropTypes.object,
defaultFiledNames: PropTypes.object,
filedNames: PropTypes.object,
};

export default Menus;
27 changes: 27 additions & 0 deletions tests/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,4 +408,31 @@ describe('Cascader', () => {

jest.useRealTimers();
});

it('should has default fieldName' +
'when props not exist labelField and valueField and childrenField', () => {
const wrapper = mount(
<Cascader>
<input />
</Cascader>
);
const props = wrapper.props();
expect(props.filedNames.label).toBe('label');
expect(props.filedNames.value).toBe('value');
expect(props.filedNames.children).toBe('children');
});

it('should has custom filedName', () => {
const wrapper = mount(
<Cascader
filedNames={{ label: 'name', value: 'code', children: 'nodes' }}
>
<input />
</Cascader>
);
const props = wrapper.props();
expect(props.filedNames.label).toBe('name');
expect(props.filedNames.value).toBe('code');
expect(props.filedNames.children).toBe('nodes');
});
});

0 comments on commit f893513

Please sign in to comment.