weex-nuke
Version:
基于 Rax 、Weex 的高性能组件体系 ~~
489 lines (463 loc) • 13.1 kB
JSX
'use strict';
/** @jsx createElement */
import { createElement, Component, findDOMNode, cloneElement, PropTypes } from 'rax';
import View from 'nuke-view';
import Text from 'nuke-text';
import Image from 'nuke-image';
import { isWeb, isWeex } from 'nuke-env';
import { genEventObject } from '../mods/util';
import TEXTINHERITKEYS from '../mods/inherit-keys';
import BaseInput from 'nuke-base-input';
import IMAGE_URL from '../mods/image';
import PlaceHolder from '../mods/placeholder';
import Count from '../mods/count';
import { isValidInput, isValidNumber } from '../uitls';
const textlineheight = 36;
class Input extends Component {
constructor(props, context) {
super(props);
this.validInput = true;
let value = null;
if ('value' in props) {
value = props.value;
} else if ('defaultValue' in props) {
value = props.defaultValue;
}
const count = (value && value.length) || 0;
this.state = {
focus: false,
value,
lines: 1,
inputValue: value,
count,
maxLengthError: props.maxLength ? count > props.maxLength : false,
};
this.materialDesign = true;
this.fixedFont = context.commonConfigs && context.commonConfigs.fixedFont;
if ('fixedFont' in props) {
this.fixedFont = props.fixedFont;
}
this.inputHandler = this.inputHandler.bind(this);
this.focusHandler = this.focusHandler.bind(this);
this.blurHandler = this.blurHandler.bind(this);
this.clearHandler = this.clearHandler.bind(this);
this.changeHandler = this.changeHandler.bind(this);
this.returnHandler = this.returnHandler.bind(this);
this.getRef = this.getRef.bind(this);
this.getValue = this.getValue.bind(this);
this.focus = this.focus.bind(this);
this.blur = this.blur.bind(this);
this.clear = this.clear.bind(this);
this.setNativeFormatRule = this.setNativeFormatRule.bind(this);
}
componentDidMount() {
const { multiple } = this.props;
if (isWeb && multiple) {
const textarea = findDOMNode(this.refs.baseinput);
this.minHeight = textarea.scrollHeight; // 18
}
}
componentWillReceiveProps(nextProps) {
if (
'value' in nextProps &&
(typeof nextProps.value === 'string' || typeof nextProps.value === 'number') &&
nextProps.value !== this.state.inputValue
) {
const { type } = this.props;
// if (type === 'number' && !isValidNumber(nextProps.value)) return;
this.setState({
value: nextProps.value,
inputValue: nextProps.value,
count: nextProps.value.length,
maxLengthError: nextProps.value.length > this.props.maxLength,
});
}
}
getTextRect(ref) {
return new Promise((resolve, reject) => {
const domEl = findDOMNode(ref);
if (domEl) {
if (isWeex) {
try {
const dom = require('@weex-module/dom');
dom.getComponentRect(domEl.ref, (e) => {
if (e && e.size && e.size.height) {
resolve(e.size.height.toFixed());
} else {
reject(0);
}
});
} catch (e) {
reject(0);
}
}
} else {
reject(0);
}
});
}
setTextareaHeight(lines) {
this.setState({
lines,
});
}
getHideElement(hiddenStyle) {
const { multiple, maxRows } = this.props;
if (!multiple || isWeb) return null;
return (
<Text
fixedFont={this.fixedFont}
ref={(n) => {
this.multipleText = n;
}}
style={hiddenStyle}
numberOfLines={maxRows}
>
{this.state.inputValue}
</Text>
);
}
clearHandler(e) {
this.setState({
value: this.state.inputValue,
});
this.setState({
value: '',
inputValue: '',
count: 0,
maxLengthError: false,
});
this.focus();
this.trigger('onClear', '');
}
blur() {
this.refs.baseinput.blur();
}
focus() {
this.refs.baseinput.focus();
}
getRef() {
return this.refs.baseinput.getRef();
}
setNativeFormatRule(rules) {
return this.refs.baseinput.setNativeFormatRule(rules);
}
clear() {
this.setState({
value: '',
inputValue: '',
count: 0,
maxLengthError: false,
});
this.trigger('onClear', '');
}
trigger(fn, ...attrs) {
if (typeof fn === 'string') fn = this.props[fn];
if (!(typeof fn === 'function')) return;
return fn.apply(this, attrs);
}
focusHandler(e) {
this.setState({
focus: true,
});
this.trigger('onFocus', e);
}
blurHandler(e) {
const { type } = this.props;
// hack For type number useage invalid input can't get any value
if (type === 'number' && isWeb) {
this.validInput = isValidInput(e.srcElement);
}
this.setState({
focus: false,
});
this.trigger('onBlur', e);
}
returnHandler(e) {
this.trigger('onReturn', e);
}
inputHandler(text, eventObj) {
const { multiple, maxLength } = this.props;
this.setState({
inputValue: text,
count: text.length,
maxLengthError: maxLength ? text.length > maxLength : false,
});
this.trigger('onInput', eventObj);
}
changeHandler(text, eventObj) {
const { type, maxLength } = this.props;
if (type === 'date' || type === 'time') {
this.setState({
inputValue: text,
});
} else {
this.setState({
value: text,
inputValue: text,
count: text.length,
maxLengthError: text.length > maxLength,
});
}
this.trigger('onChange', text, eventObj);
}
renderPlaceholder() {
const { focus, inputValue, maxLengthError } = this.state;
const {
placeholder,
placeholderColor,
themeStyle,
status,
disabled,
errorMessage,
floatPlaceholder,
hideErrorWhenFocus,
} = this.props;
const placeholderAttrs = {
placeholder,
placeholderColor,
themeStyle,
status,
disabled,
errorMessage,
floatPlaceholder,
hideErrorWhenFocus,
focus,
inputValue,
validInput: this.validInput,
maxLengthError,
fixedFont: this.fixedFont,
};
return <PlaceHolder {...placeholderAttrs} />;
}
renderCount() {
const { maxLength, multiple, renderCount, themeStyle } = this.props;
if (!maxLength || !renderCount) return null;
const { count } = this.state;
const countAttrs = {
maxLength,
multiple,
renderCount,
count,
themeStyle,
fixedFont: this.fixedFont,
};
return <Count {...countAttrs} />;
}
renderClear(styles) {
return <Image source={{ uri: IMAGE_URL.clear }} onClick={this.clearHandler} style={styles['md-clear']} />;
}
renderCustomIcon(styles) {
const { icon = {} } = this.props;
const { uri, onPress = () => {}, style = {} } = icon;
if (!uri) return null;
return (
<View style={[styles['md-icon'], style]} onClick={onPress}>
<Image source={{ uri }} style={[styles['icon-image'], style]} />
</View>
);
}
renderHelpLine(styles) {
const { status, errorMessage, autoAdjustHeight, hideErrorWhenFocus = false, renderCount } = this.props;
const { focus, maxLengthError } = this.state;
let renderErrorMessage;
if (hideErrorWhenFocus) {
renderErrorMessage = !focus && status === 'error' && errorMessage;
} else {
renderErrorMessage = status === 'error' && errorMessage;
}
if (!renderErrorMessage && !renderCount && autoAdjustHeight) return null;
let errorMessageDom;
if (errorMessage) {
if (typeof errorMessage === 'string') {
errorMessageDom = (
<Text fixedFont={this.fixedFont} style={styles['md-error-text']}>
{errorMessage}
</Text>
);
} else {
errorMessageDom = cloneElement(errorMessage, {
style: errorMessage.props.style
? Object.assign({}, styles['md-error-text'], errorMessage.props.style)
: styles['md-error-text'],
});
}
}
return (
<View data-role="md-help" style={styles['md-help']}>
{renderErrorMessage ? errorMessageDom : null}
{this.renderCount()}
</View>
);
}
getValue() {
return this.refs.baseinput.getValue();
}
render() {
const {
defaultValue,
onChange,
onFocus,
onInput,
onBlur,
placeholder,
renderCount,
readOnly,
disabled,
style = {},
maxLength,
multiple,
inputStyle,
returnKeyType,
hasClear,
rows,
type,
icon,
autoAdjustHeight,
status,
floatPlaceholder,
hideErrorWhenFocus,
errorMessage,
themeStyle,
maxRows,
...others
} = this.props;
const styles = this.props.themeStyle;
const { focus, value, lines, maxLengthError } = this.state;
const mode = multiple ? 'multiple' : 'single';
const MDWrapperStyle = Object.assign(
{},
styles['input-wrap'],
styles[`${mode}-md-wrap`],
readOnly ? styles.readonly : {},
floatPlaceholder ? {} : styles['static-placeholder'],
disabled ? styles[`md-${mode}-disabled`] : {},
style
);
let mdLineStyle = Object.assign(
{},
styles['md-bottom-line'],
status === 'error' ? styles['md-bottom-line-error'] : {},
focus ? styles['md-bottom-line-focus'] : {}
);
if ((status === 'error' && !hideErrorWhenFocus) || maxLengthError) {
mdLineStyle = Object.assign(mdLineStyle, styles['md-bottom-line-error']);
}
if (disabled) {
mdLineStyle = Object.assign(mdLineStyle, styles['md-bottom-line-disabled']);
}
const inputElementAttrs = {
onInput: this.inputHandler,
onFocus: this.focusHandler,
onBlur: this.blurHandler,
onChange: this.changeHandler,
onReturn: this.returnHandler,
onResize: this.resizeHandler,
multiple,
disabled,
readOnly,
returnKeyType,
maxLength,
placeholder: '',
value: typeof value !== null ? value : null,
type,
style: multiple ? styles['multiple-md-input-ele'] : styles['md-input-ele'],
};
if (isWeb) {
if ('readOnly' in inputElementAttrs && !inputElementAttrs.readOnly) {
delete inputElementAttrs.readOnly;
}
}
if (multiple) {
if (isWeb) {
inputElementAttrs.rows = 1;
} else {
// maxRows
// inputElementAttrs.rows = lines;
inputElementAttrs.style.height = maxRows * textlineheight;
// not adjust maxRows
inputElementAttrs.rows = maxRows;
}
}
TEXTINHERITKEYS.map((item) => {
if (styles[item]) {
inputElementAttrs.style[item] = styles[item];
}
});
const multipleTextStyle = Object.assign(
{},
inputElementAttrs.style,
styles['text-for-height']
// {lineHeight: textlineheight }
);
if ('height' in multipleTextStyle) {
delete multipleTextStyle.height;
}
const inputElestyle = inputElementAttrs.style;
return (
<View data-role="md-wrap" style={MDWrapperStyle}>
{this.renderPlaceholder()}
{this.getHideElement(multipleTextStyle)}
<BaseInput fixedFont={this.fixedFont} ref="baseinput" {...others} {...inputElementAttrs} />
<View data-role="md-line" style={mdLineStyle} />
{this.renderHelpLine(styles)}
{hasClear ? this.renderClear(styles) : null}
{!hasClear ? this.renderCustomIcon(styles) : null}
</View>
);
}
}
Input.propTypes = {
defaultValue: PropTypes.any,
onFocus: PropTypes.func,
onInput: PropTypes.func,
onBlur: PropTypes.func,
placeholder: PropTypes.string,
readOnly: PropTypes.boolean,
disabled: PropTypes.boolean,
style: PropTypes.any,
maxLength: PropTypes.number,
multiple: PropTypes.boolean,
inputStyle: PropTypes.any,
returnKeyType: PropTypes.any,
hasClear: PropTypes.boolean,
rows: PropTypes.number,
maxRows: PropTypes.number,
type: PropTypes.oneOf(['text', 'url', 'password', 'tel', 'date', 'time', 'email']),
status: PropTypes.oneOf(['success', 'error']),
errorMessage: PropTypes.string,
placeholderColor: PropTypes.string,
value: PropTypes.string,
themeStyle: PropTypes.any,
renderCount: PropTypes.boolean,
floatPlaceholder: PropTypes.boolean,
hideErrorWhenFocus: PropTypes.boolean,
fixedFont: PropTypes.boolean,
};
Input.defaultProps = {
onFocus: () => {},
onInput: () => {},
onBlur: () => {},
placeholder: '',
readOnly: false,
disabled: false,
style: {},
multiple: false,
inputStyle: {},
themeStyle: {},
hasClear: false,
type: 'text',
rows: 1,
maxRows: 3,
status: 'success',
renderCount: false,
placeholderColor: '#999999',
floatPlaceholder: true,
hideErrorWhenFocus: true,
fixedFont: true,
};
Input.contextTypes = {
androidConfigs: PropTypes.any,
commonConfigs: PropTypes.any,
};
export default Input;