Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add labelField and valueField for custom label field name and value field name #23

Merged
merged 7 commits into from
Apr 27, 2018
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: 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');
});
});