src/Calendar.js
/**
* @file melon/Calendar
* @author cxtom<[email protected]>
*/
import React, {PropTypes} from 'react';
import InputComponent from 'melon-core/InputComponent';
import {create} from 'melon-core/classname/cxBuilder';
import Validity from 'melon-core/Validity';
import {getNextValidity} from 'melon-core/util/syncPropsToState';
import Icon from 'melon/Icon';
import Confirm from 'melon/Confirm';
import Panel from './calendar/Panel';
import * as DateTime from './util';
const cx = create('Calendar');
/**
* melon 日期选择器
*
* @class
* @extends {melon-core/InputComponent}
*/
export default class Calendar extends InputComponent {
/**
* 构造函数
*
* @param {Object} props 组件属性
* @param {Object} context 组件上下文
* @public
*/
constructor(props, context) {
super(props, context);
const value = this.state.value;
this.onLabelClick = this.onLabelClick.bind(this);
this.onConfirm = this.onConfirm.bind(this);
this.onLabelClick = this.onLabelClick.bind(this);
this.onCancel = this.onCancel.bind(this);
this.onDateChange = this.onDateChange.bind(this);
/**
* 组件状态
*
* @type {Object}
*/
this.state = {
...this.state,
// 缓存用户在 confirm 前的选中值
date: value ? this.parseDate(value) : undefined,
// 是否打开选择窗
open: false
};
}
/**
* 组件每次更新 (componentWillRecieveProps) 时,需要
* 更新组件状态,包括校验信息、同步 date 和 value
*
* @param {Object} nextProps 组件更新的属性
* @return {Object} 最新的组件状态
* @public
*/
getSyncUpdates(nextProps) {
const {disabled, readOnly, customValidity, defaultValue} = nextProps;
let value = nextProps.value ? nextProps.value : defaultValue;
// 如果有值,那么就试着解析一下;否则设置为 null
let date = value ? this.parseDate(value) : null;
// 如果 date 为 null 或者是 invalid date,那么就用上默认值;
// 否则就用解析出来的这个值
date = !date || isNaN(date.getTime()) ? new Date() : date;
const vilidity = getNextValidity(this, {value, disabled, customValidity});
return {
date,
vilidity,
value: (disabled || readOnly || !value) ? value : this.stringifyValue(date)
};
}
/**
* 格式化日期
*
* @param {Date} rawValue 源日期对象
* @param {string=} format 日期格式,默认为当前实例的dateFormat
* @return {string} 格式化后的日期字符串
* @private
*/
stringifyValue(rawValue) {
if (!DateTime.isDate(rawValue)) {
return rawValue;
}
const dateFormat = this.props.dateFormat;
return DateTime.format(rawValue, dateFormat);
}
/**
* 格式化日期对象
*
* @param {string} date 日期字符串
* @return {Date} 转化后的日期对象
* @private
*/
parseDate(date) {
if (typeof date !== 'string') {
return date;
}
let format = this.props.dateFormat;
return DateTime.parse(date, format);
}
/**
* 点击 Label 时触发,打开弹窗
*
* @private
*/
onLabelClick() {
const {disabled, readOnly} = this.props;
if (disabled || readOnly) {
return;
}
this.setState({open: true});
}
/**
* 在浮层上点击确定按钮时触发
*
* @private
*/
onConfirm() {
let {value, date} = this.state;
value = this.parseDate(value);
if (value && DateTime.isEqualDate(date, this.parseDate(value))) {
this.setState({open: false});
return;
}
const newDate = date ? date : new Date();
this.setState({open: false, date: newDate}, () => {
super.onChange({
type: 'change',
target: this,
value: this.stringifyValue(newDate)
});
});
}
/**
* 在浮层上点击取消按钮时触发
*
* @private
*/
onCancel() {
this.setState({open: false});
}
/**
* CalendarPanel 日期变更时触发
* 当属性 autoConfirm 为 true 时,自动执行 onConfirm
*
* @param {Object} e 事件对象
* @param {Date} e.value 改变后的日期值
* @private
*/
onDateChange(e) {
const value = e.value;
this.setState(
{date: this.parseDate(value)},
this.props.autoConfirm ? () => this.onConfirm() : null
);
}
/**
* 渲染
*
* @public
* @return {React.Element}
*/
render() {
const {
state,
props
} = this;
const {
lang,
disabled,
readOnly,
size,
name,
placeholder,
...others
} = props;
const {value, validity} = state;
let {begin, end} = props;
begin = begin ? this.parseDate(begin) : null;
end = end ? this.parseDate(end) : null;
const {open, date} = state;
const className = cx(props)
.addStates({focus: open})
.addStates(this.getStyleStates())
.build();
return (
<div {...others} className={className}>
<input
name={name}
ref="input"
type="hidden"
value={value}
disabled={disabled}
size={size} />
<label onClick={(disabled || readOnly) ? null : this.onLabelClick}>
{value ? value : (
<span className={cx().part('label-placeholder').build()}>
{placeholder}
</span>
)}
<Icon icon='expand-more' />
</label>
<Validity validity={validity} />
<Confirm
open={open}
variants={['calendar']}
onConfirm={this.onConfirm}
onCancel={this.onCancel}
size={size}
buttonVariants={['secondery', 'calendar']} >
<Panel
date={date}
begin={begin}
end={end}
lang={lang}
onChange={this.onDateChange} />
</Confirm>
</div>
);
}
}
Calendar.displayName = 'Calendar';
Calendar.LANG = {
// 对于 '周' 的称呼
week: '周',
// 星期对应的顺序表示
days: '日,一,二,三,四,五,六'
};
Calendar.defaultProps = {
...InputComponent.defaultProps,
defaultValue: '',
dateFormat: 'YYYY-MM-DD',
lang: Calendar.LANG,
placeholder: '请选择'
};
Calendar.propTypes = {
...InputComponent.propTypes,
value: PropTypes.string,
autoConfirm: PropTypes.bool,
dateFormat: PropTypes.string,
end: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string
]),
begin: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string
]),
lang: PropTypes.shape({
week: PropTypes.string,
days: PropTypes.string
})
};
Calendar.childContextTypes = InputComponent.childContextTypes;
Calendar.contextTypes = InputComponent.contextTypes;