react-native-dropdown-select
Version:
678 lines (598 loc) • 22.3 kB
JavaScript
/**
* Created by sohobloo on 16/9/13.
*/
'use strict';
import React, {
Component,
} from 'react';
import {
StyleSheet,
Dimensions,
View,
Text,
ListView,
TouchableWithoutFeedback,
TouchableNativeFeedback,
TouchableOpacity,
TouchableHighlight,
Modal,
ActivityIndicator,
Platform,
StatusBar,
} from 'react-native';
import PropTypes from 'prop-types';
import StyleSheetAdapt from 'react-native-stylesheet-adapt';
import Button from "./Button";
/**
* 将样式leftTextStyle增加数组样式
* **/
import CheckBox from 'react-native-checkbox-cus';
const TOUCHABLE_ELEMENTS = [
'TouchableHighlight',
'TouchableOpacity',
'TouchableWithoutFeedback',
'TouchableNativeFeedback'
];
/**
* 下拉框 支持单选和多选,基础组件
* **/
export default class DropdownBox extends Component {
static propTypes = {
multiple:PropTypes.bool,//单选或多选,true:多选,false:单选;默认是false
disabled: PropTypes.bool,
scrollEnabled: PropTypes.bool,
defaultIndex: PropTypes.number,
defaultValue: PropTypes.string,
/**
* options数组成员是{
isChecked:true/false,//可不传
name:'',//显示数据value//存入数据 不需要
}//回传数据
,或'XX'字符成员,回传'XX'
onSelect回传 单选,选中元素;多选选中数据数组
* **/
options: PropTypes.array,
accessible: PropTypes.bool,
animated: PropTypes.bool,
showsVerticalScrollIndicator: PropTypes.bool,
keyboardShouldPersistTaps: PropTypes.string,
style: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]),
textStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]),
defalutTextStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]),//默认文本样式
dropdownStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]),
dropdownTextStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]),
dropdownTextHighlightStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]),
adjustFrame: PropTypes.func,
renderRow: PropTypes.func,
renderSeparator: PropTypes.func,
renderButtonText: PropTypes.func,
onDropdownWillShow: PropTypes.func,//下拉框展示前执行
onDropdownDidShow:PropTypes.func,//下拉框已经打开 执行
onDropdownWillHide: PropTypes.func,//下拉框将要隐藏 执行
onDropdownDidHide: PropTypes.func,//框已经隐藏 执行
onSelect: PropTypes.func, //单选回传选中值,第一个参数,选中的数组地址;第二个参数数组中选中成员
defaultSelectedIndex:PropTypes.number,//选中数据在数组的中的地址,不设置择无效
};
static defaultProps = {
multiple:false,
disabled: false,
scrollEnabled: true,
defaultIndex: -1,
defaultValue: 'Please select...',
// options: null,
options: [],
animated: true,
showsVerticalScrollIndicator: true,
keyboardShouldPersistTaps: 'never'
};
constructor(props) {
super(props);
this._button = null;
this._buttonFrame = null;
this._nextValue = null;
this._nextIndex = null;
this._selectData = [];//多选数据
this._selectDataText = [];//多选数据显示文本
this._dataSourceList = [];//数据源列表
this.state = {
accessible: !!props.accessible,
loading: !props.options,
showDropdown: false,
buttonText: props.defaultValue,
selectedIndex: props.defaultIndex,
rowHeight:null,//行高
};
}
componentWillReceiveProps(nextProps) {
let {buttonText, selectedIndex} = this.state;
const {defaultIndex, defaultValue, options} = nextProps;
buttonText = this._nextValue == null ? buttonText : this._nextValue;
selectedIndex = this._nextIndex == null ? selectedIndex : this._nextIndex;
if (selectedIndex < 0) {
selectedIndex = defaultIndex;
if (selectedIndex < 0) {
buttonText = defaultValue;
}
}
this._nextValue = null;
this._nextIndex = null;
this.setState({
loading: !options,
buttonText,
selectedIndex
});
}
render() {
return (
<View {...this.props}>
{this._renderButton()}
{this._renderModal()}
</View>
);
}
_updatePosition(callback) {
if (this._button && this._button.measure) {
this._button.measure((fx, fy, width, height, px, py) => {
this._buttonFrame = {x: px, y: py, w: width, h: height};
// console.info("this._buttonFrame",this._buttonFrame)
callback && callback();
});
}
}
show() {
this._updatePosition(() => {
const {onDropdownDidShow} = this.props;
this.setState({
showDropdown: true
});
(!onDropdownDidShow || onDropdownDidShow(true))
});
}
hide() {
const {onDropdownDidHide} = this.props;
this.setState({
showDropdown: false
});
(!onDropdownDidHide || onDropdownDidHide(false))
}
select(idx) {
const {defaultValue, options, defaultIndex, renderButtonText} = this.props;
let value = defaultValue;
if (idx == null || !options || idx >= options.length) {
idx = defaultIndex;
}
if (idx >= 0) {
value = renderButtonText ? renderButtonText(options[idx]) : options[idx].toString();
}
this._nextValue = value;
this._nextIndex = idx;
this.setState({
buttonText: value,
selectedIndex: idx
});
}
_renderButton() {
const {disabled, accessible, children, defalutTextStyle,options,defaultSelectedIndex,style} = this.props;
const {buttonText} = this.state;
return (
<TouchableOpacity ref={button => this._button = button}
disabled={disabled}
accessible={accessible}
style={style}
onPress={this._onButtonPress}
>
{
children ||
(
<View style={styles.button}>
<Text style={[styles.buttonText, defalutTextStyle]}
numberOfLines={1}
>
{
defaultSelectedIndex == undefined ?
buttonText :
options[defaultSelectedIndex].name == undefined ?
options[defaultSelectedIndex] :
options[defaultSelectedIndex].name
}
</Text>
</View>
)
}
</TouchableOpacity>
);
}
_onButtonPress = () => {
const {onDropdownWillShow} = this.props;
if (!onDropdownWillShow ||
onDropdownWillShow() !== false) {
this.show();
}
};
_renderModal() {
const {animated, accessible, dropdownStyle,multiple} = this.props;
const {showDropdown, loading} = this.state;
if (showDropdown && this._buttonFrame) {
const frameStyle = this._calcPosition();
// console.info("frameStyle",frameStyle)
const animationType = animated ? 'fade' : 'none';//œalert(JSON.stringify(frameStyle))
return (
<Modal animationType={animationType}
visible={true}
transparent={true}
onRequestClose={this._onRequestClose}
supportedOrientations={['portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right']}
>
<TouchableWithoutFeedback accessible={accessible}
disabled={!showDropdown}
onPress={()=>{
multiple ? this._onMultiplePress() : this._onModalPress();
}}>
<View style={styles.modal}>
<View style={[styles.dropdown, multiple ? {} : {position: 'absolute'}, dropdownStyle, frameStyle]}>
{loading ? this._renderLoading() : this._renderDropdown()}
</View>
{
multiple
? <View style={[{backgroundColor:"white"}, dropdownStyle, frameStyle]}>
<Button style={styles.multipleBtn}
textStyle={styles.multipleBtnText}
onPress={()=>this._onMultiplePress()}
text={"确定"}/>
</View>
: null
}
</View>
</TouchableWithoutFeedback>
</Modal>
);
}
}
_calcPosition() {
const {dropdownStyle, style, adjustFrame,options,dropdownTextStyle} = this.props;
const {rowHeight} = this.state;
const dimensions = Dimensions.get('window');
const windowWidth = dimensions.width;
const windowHeight = dimensions.height;
const dropdownHeight = (dropdownStyle && StyleSheet.flatten(dropdownStyle).height) ||
(rowHeight&&(rowHeight*options.length)) || StyleSheet.flatten(styles.dropdown).height;
const bottomSpace = windowHeight - this._buttonFrame.y - this._buttonFrame.h;
const rightSpace = windowWidth - this._buttonFrame.x;
const showInBottom = bottomSpace >= dropdownHeight || bottomSpace >= this._buttonFrame.y;
const showInLeft = rightSpace >= this._buttonFrame.x;
const dpHeight = dropdownHeight >= bottomSpace ? bottomSpace * 0.9 : dropdownHeight;
const positionStyle = {
// height: dropdownHeight,
height: dpHeight
};
if(Platform.OS == 'ios'){
positionStyle.top = showInBottom ? this._buttonFrame.y + this._buttonFrame.h : Math.max(0, this._buttonFrame.y - dropdownHeight);
}
else
{
positionStyle.top = showInBottom ? this._buttonFrame.y + this._buttonFrame.h - StatusBar.currentHeight : Math.max(0, this._buttonFrame.y - dropdownHeight);
}
const dropdownWidth = (dropdownStyle && StyleSheet.flatten(dropdownStyle).width) ||
(style && StyleSheet.flatten(style).width) || -1;
if (showInLeft) {
positionStyle.left = this._buttonFrame.x;
} else {
if (dropdownWidth !== -1) {
positionStyle.width = dropdownWidth;
}
positionStyle.right = rightSpace - this._buttonFrame.w;
}
const posiStyleObj = {
positionStyle:positionStyle,
multipleBtnStyle:JSON.parse(JSON.stringify(positionStyle))
}
posiStyleObj.multipleBtnStyle.left += (dropdownWidth + StyleSheetAdapt.getWidth(100));
// return adjustFrame ? adjustFrame(positionStyle) : positionStyle;
return adjustFrame ? adjustFrame(positionStyle) : positionStyle;
}
_onRequestClose = () => {
const {onDropdownWillHide} = this.props;
if (!onDropdownWillHide ||
onDropdownWillHide() !== false) {
this.hide();
}
};
_onModalPress = () => {
const {onDropdownWillHide} = this.props;
if (!onDropdownWillHide ||
onDropdownWillHide() !== false) {
this.hide();
}
};
_renderLoading() {
return (
<ActivityIndicator size='small'/>
);
}
_renderDropdown() {
const {scrollEnabled, renderSeparator, showsVerticalScrollIndicator, keyboardShouldPersistTaps} = this.props;
return (
<ListView scrollEnabled={scrollEnabled}
style={styles.list}
dataSource={this._dataSource}
renderRow={this._renderRow}
renderSeparator={renderSeparator || this._renderSeparator}
automaticallyAdjustContentInsets={false}
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
keyboardShouldPersistTaps={keyboardShouldPersistTaps}
/>
);
}
get _dataSource() {
const {options} = this.props;
let dataList = [];
options.forEach((v,i,a)=>{
if(typeof(v) == 'string')
{
dataList.push({
name:v,
value:v,
isChecked:false
});
}
else if(typeof(v) == 'object')
{
v.isChecked = v.isChecked == undefined ? false : v.isChecked;
dataList.push(v);
}
});
if(this._dataSourceList.length == dataList.length)
{
for(var i = 0; i < dataList.length; i++)
{
if(dataList[i].name != this._dataSourceList[i].name)
{
this._dataSourceList = dataList;
break;
}
}
}
else
{
this._dataSourceList = dataList;
}
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
return ds.cloneWithRows(this._dataSourceList);
}
_renderRow = (rowData, sectionID, rowID, highlightRow) => {
const {renderRow, dropdownTextStyle, dropdownTextHighlightStyle, accessible, multiple,dropdownStyle} = this.props;
const {selectedIndex} = this.state;
const key = `row_${rowID}`;
const highlighted = rowID == selectedIndex;
let row = !renderRow ?
(<Text style={[
styles.rowText,
dropdownTextStyle,
highlighted && styles.highlightedRowText,
highlighted && dropdownTextHighlightStyle
]}
onLayout={({nativeEvent: { layout: {x, y, width, height}}})=>{
const {rowHeight} = this.state;
if(!(dropdownStyle && StyleSheet.flatten(dropdownStyle).height)&&rowHeight === null) {
this.setState({rowHeight:height});
}
}}>
{rowData.name}
</Text>) :
renderRow(rowData, rowID, highlighted);
let preservedProps = {
key,
accessible
};
if(multiple)
{
preservedProps.onClick = (isChecked) => this._onRowPress(rowData, sectionID, rowID, highlightRow,isChecked);
// preservedProps.onClick = () => {};
}
else
{
preservedProps.onPress = () => this._onRowPress(rowData, sectionID, rowID, highlightRow);
}
if (TOUCHABLE_ELEMENTS.find(name => name == row.type.displayName)) {
const props = {...row.props};
props.key = preservedProps.key;
props.onPress = preservedProps.onPress;
const {children} = row.props;
switch (row.type.displayName) {
case 'TouchableHighlight': {
return (
<TouchableHighlight {...props}>
{children}
</TouchableHighlight>
);
}
case 'TouchableOpacity': {
return (
<TouchableOpacity {...props}>
{children}
</TouchableOpacity>
);
}
case 'TouchableWithoutFeedback': {
return (
<TouchableWithoutFeedback {...props}>
{children}
</TouchableWithoutFeedback>
);
}
case 'TouchableNativeFeedback': {
return (
<TouchableNativeFeedback {...props}>
{children}
</TouchableNativeFeedback>
);
}
default:
break;
}
}
if(multiple)
{
// alert(JSON.stringify(styles.highlightedRowText))
return (
<CheckBox {...preservedProps}
rightTextStyle={[
styles.rowText,
dropdownTextStyle,
// highlighted && styles.highlightedRowText,
// highlighted && dropdownTextHighlightStyle
]}
isChecked={rowData.isChecked}
rightText={rowData.name}/>
);
}
else
{
return (
<TouchableHighlight {...preservedProps}>
{row}
</TouchableHighlight>
);
}
};
_onMultiplePress(){
const {onSelect, onDropdownWillHide} = this.props;//alert(JSON.stringify(this._selectData))
if (!onDropdownWillHide || onDropdownWillHide() !== false) {
this.setState({
showDropdown: false
});
}
if (onSelect != undefined) {
let selList = [];
// alert(JSON.stringify(this._selectData));
this._selectData.forEach((v,i,a)=>{
if(v.name == v.value)
{
selList.push(v.value);
}
else
{
selList.push(v);
}
});
// alert(JSON.stringify(selList));
onSelect(selList);
}
}
_onRowPress(rowData, sectionID, rowID, highlightRow,isChecked) {
const {onSelect, renderButtonText, onDropdownWillHide,multiple} = this.props;
if(multiple)
{
Array.prototype.remove = function(val) {
var index = this.indexOf(val);
if (index > -1) {
this.splice(index, 1);
}
};
// highlightRow(sectionID, rowID);
if(isChecked)
{
this._selectDataText.push(rowData.name);
this._selectData.push(rowData);
}
else
{
this._selectData.remove(rowData);
this._selectDataText.remove(rowData.name);
}
this._dataSourceList[rowID].isChecked = isChecked;
const value = renderButtonText && renderButtonText(this._selectData) || this._selectDataText.toString();
this._nextValue = value;
this._nextIndex = rowID;
this.setState({
buttonText: value,
selectedIndex: rowID
});
// alert(JSON.stringify(this._selectData))
}
else
{
if (!onSelect || onSelect(rowID, rowData) !== false) {
highlightRow(sectionID, rowID);
const value = renderButtonText && renderButtonText(rowData) || rowData.name;
this._nextValue = value;
this._nextIndex = rowID;
this.setState({
buttonText: value,
selectedIndex: rowID
});
}
this.hide();
/* if (!onDropdownWillHide || onDropdownWillHide() !== false) {
this.setState({
showDropdown: false
});
}*/
}
}
_renderSeparator = (sectionID, rowID, adjacentRowHighlighted) => {
const key = `spr_${rowID}`;
return (
<View style={styles.separator}
key={key}
/>
);
};
}
const styles = StyleSheetAdapt.create({
multipleBtn:{
backgroundColor:"#FABE00",
margin:10,
borderRadius:5,
padding:5,
},
multipleBtnText:{
color:'#FFFFFF',
},
button: {
justifyContent: 'center'
},
buttonText: {
fontSize: 12
},
modal: {
flexGrow: 1,
flexDirection:'row'
},
dropdown: {
// position: 'absolute',
height: (33 + StyleSheet.hairlineWidth) * 5,
borderWidth: StyleSheet.hairlineWidth,
borderColor: 'lightgray',
borderRadius: 2,
backgroundColor: 'white',
justifyContent: 'center'
},
loading: {
alignSelf: 'center'
},
list: {
//flexGrow: 1,
},
rowText: {
// paddingHorizontal: 6,
// paddingVertical: 10,
paddingTop:10,
paddingBottom:10,
paddingLeft:6,
paddingRight:6,
fontSize: 11,
color: 'gray',
backgroundColor: 'white',
textAlignVertical: 'center'
},
highlightedRowText: {
color: 'black'
},
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: 'lightgray'
}
});