src/Select.js
/**
* @file melon/Select
* @author leon([email protected])
*/
import React, {PropTypes, Children} from 'react';
import ReactDOM from 'react-dom';
import Icon from './Icon';
import SeparatePopup from './select/SeparatePopup';
import Validity from 'melon-core/Validity';
import InputComponent from 'melon-core/InputComponent';
import Group from './select/OptionGroup';
import Option from './select/Option';
import {create} from 'melon-core/classname/cxBuilder';
const cx = create('Select');
export default class Select extends InputComponent {
constructor(props, context) {
super(props, context);
this.state = {
...this.state,
open: false
};
this.onClick = this.onClick.bind(this);
this.onClickOption = this.onClickOption.bind(this);
this.onPopupHide = this.onPopupHide.bind(this);
}
componentDidMount() {
super.componentDidMount();
let container = this.container = document.createElement('div');
container.className = cx().part('popup').build();
document.body.appendChild(container);
this.popup = ReactDOM.render(
<SeparatePopup
target={ReactDOM.findDOMNode(this)}
open={false}
onHide={this.onPopupHide}>
{Children.map(
this.props.children,
this.renderItem,
this
)}
</SeparatePopup>,
container
);
}
componentWillReceiveProps(nextProps) {
const children = nextProps.children;
if (children !== this.props.children) {
this.popup = ReactDOM.render(
<SeparatePopup
target={ReactDOM.findDOMNode(this)}
open={this.state.open}
onHide={this.onPopupHide}>
{Children.map(
children,
this.renderItem,
this
)}
</SeparatePopup>,
this.container
);
}
super.componentWillReceiveProps(nextProps);
}
componentWillUnmount() {
let container = this.container;
if (container) {
ReactDOM.unmountComponentAtNode(container);
container.parentElement.removeChild(container);
this.container = container = null;
}
super.componentWillUnmount();
}
showOptions() {
this.setState({
open: true
}, () => {
ReactDOM.render(
<SeparatePopup
target={ReactDOM.findDOMNode(this)}
open={true}
onHide={this.onPopupHide}>
{Children.map(
this.props.children,
this.renderItem,
this
)}
</SeparatePopup>,
this.container
);
});
}
hideOptions() {
this.setState({
open: false
}, () => {
ReactDOM.render(
<SeparatePopup
target={ReactDOM.findDOMNode(this)}
open={false}
onHide={this.onPopupHide}>
{Children.map(
this.props.children,
this.renderItem,
this
)}
</SeparatePopup>,
this.container
);
});
}
onClick() {
const {disabled, readOnly} = this.props;
if (disabled || readOnly) {
return;
}
if (this.isOpen()) {
this.hideOptions();
}
else {
this.showOptions();
}
}
onClickOption({value}) {
this.hideOptions();
super.onChange({
type: 'change',
target: this,
value
});
}
onPopupHide(e) {
this.hideOptions();
}
renderItem(child, index) {
if (!child) {
return null;
}
if (child.type === 'option') {
return (
<Option
{...child.props}
onClick={this.onClickOption}
key={index} />
);
}
if (child.type === 'optgroup') {
return (
<Group
{...child.props}
onClick={this.onClickOption}
key={index} />
);
}
return null;
}
renderHiddenInput() {
const {name, value} = this.props;
return name
? (
<input
name={name}
type="hidden"
value={value} />
)
: null;
}
/**
* 渲染label部件
*
* @param {string|ReactElement} label label部件内容
* @return {ReactElement}
*/
renderLabel() {
const value = this.state.value;
const {children, placeholder} = this.props;
const option = this.findOption(value, children);
const label = option
? (option.props.label || option.props.children)
: (
<span className={cx().part('label-placeholder').build()}>
{placeholder}
</span>
);
return (
<label className={cx().part('label').build()}>
{label}
</label>
);
}
findOption(value, children) {
children = Children.toArray(children);
if (!children) {
return null;
}
for (let i = 0, len = children.length; i < len; ++i) {
const child = children[i];
if (child.type === 'optgroup') {
const option = this.findOption(value, child.props.children);
if (option) {
return option;
}
continue;
}
if (child.props.value === value) {
return child;
}
}
return null;
}
renderIcon() {
return <Icon icon='expand-more' />;
}
isOpen() {
return this.state.open;
}
render() {
return (
<div
onClick={this.onClick}
className={cx(this.props).addStates(this.getStyleStates()).build()}>
{this.renderLabel()}
{this.renderHiddenInput()}
{this.renderIcon()}
<Validity validity={this.state.validity} />
</div>
);
}
}
Select.displayName = 'Select';
Select.defaultProps = {
...InputComponent.defaultProps,
placeholder: '请选择',
defaultValue: ''
};
Select.propTypes = {
...InputComponent.propTypes,
placeholder: PropTypes.string,
children: PropTypes.node.isRequired
};
Select.childContextTypes = InputComponent.childContextTypes;
Select.contextTypes = InputComponent.contextTypes;
export function createOptions(dataSource) {
return dataSource.map(function (option, index) {
return (
<option
key={index}
disabled={option.disabled}
value={option.value}
label={option.name} />
);
});
}
Select.createOptions = createOptions;