index.js 13.4 KB
Newer Older
xwenliang's avatar
xwenliang committed
1 2 3 4 5 6 7 8 9
'use strict';

import React, {
	StyleSheet, 
	PropTypes, 
	View, 
	Text,
	Animated,
	Platform,
xwenliang's avatar
bugs  
xwenliang committed
10 11
	Dimensions,
	PickerIOS
xwenliang's avatar
xwenliang committed
12 13 14 15 16
} from 'react-native';

import PickerAndroid from 'react-native-picker-android';

let Picker = Platform.OS === 'ios' ? PickerIOS : PickerAndroid;
xwenliang's avatar
xwenliang committed
17
let PickerItem = Picker.Item;
xwenliang's avatar
xwenliang committed
18 19 20 21 22
let width = Dimensions.get('window').width;
let height = Dimensions.get('window').height;

export default class PickerAny extends React.Component {

xwenliang's avatar
bugs  
xwenliang committed
23
	static propTypes = {
xwenliang's avatar
xwenliang committed
24
		pickerBtnText: PropTypes.string,
25
		pickerCancelBtnText: PropTypes.string,
xwenliang's avatar
xwenliang committed
26
		pickerBtnStyle: PropTypes.any,
27 28
		pickerTitle: PropTypes.string,
		pickerTitleStyle: PropTypes.any,
xwenliang's avatar
xwenliang committed
29 30
		pickerToolBarStyle: PropTypes.any,
		pickerItemStyle: PropTypes.any,
xwenliang's avatar
bugs  
xwenliang committed
31 32
		pickerHeight: PropTypes.number,
		showDuration: PropTypes.number,
xwenliang's avatar
xwenliang committed
33 34
		pickerData: PropTypes.any.isRequired,
		selectedValue: PropTypes.any.isRequired,
35
		onPickerDone: PropTypes.func,
xwenliang's avatar
xwenliang committed
36 37
		onPickerCancel: PropTypes.func,
		onValueChange: PropTypes.func
xwenliang's avatar
xwenliang committed
38
	};
xwenliang's avatar
bugs  
xwenliang committed
39 40

	static defaultProps = {
xwenliang's avatar
xwenliang committed
41
		pickerBtnText: '完成',
42
		pickerCancelBtnText: '取消',
xwenliang's avatar
xwenliang committed
43
		pickerHeight: 250,
xwenliang's avatar
bugs  
xwenliang committed
44
		showDuration: 300,
45
		onPickerDone: ()=>{},
xwenliang's avatar
xwenliang committed
46 47
		onPickerCancel: ()=>{},
		onValueChange: ()=>{}
xwenliang's avatar
xwenliang committed
48
	};
xwenliang's avatar
bugs  
xwenliang committed
49

xwenliang's avatar
xwenliang committed
50 51
	constructor(props, context){
		super(props, context);
xwenliang's avatar
xwenliang committed
52 53 54 55 56 57 58 59 60 61 62
	}

	componentWillMount(){
		this.state = this._getStateFromProps(this.props);
	}

	componentWillReceiveProps(newProps){
		let newState = this._getStateFromProps(newProps);
		this.setState(newState);
	}

xwenliang's avatar
xwenliang committed
63 64 65
	shouldComponentUpdate(nextProps, nextState, context){
		if(JSON.stringify(nextState.selectedValue) === JSON.stringify(this.state.selectedValue)){
			return false;
xwenliang's avatar
xwenliang committed
66
		}
xwenliang's avatar
xwenliang committed
67 68
		return true;
	}
xwenliang's avatar
xwenliang committed
69

xwenliang's avatar
xwenliang committed
70
	_getStateFromProps(props){
xwenliang's avatar
xwenliang committed
71
		//the pickedValue must looks like [wheelone's, wheeltwo's, ...]
xwenliang's avatar
xwenliang committed
72 73
		//this.state.selectedValue may be the result of the first pickerWheel
		let pickerBtnText = props.pickerBtnText;
74
		let pickerCancelBtnText = props.pickerCancelBtnText;
xwenliang's avatar
xwenliang committed
75
		let pickerBtnStyle = props.pickerBtnStyle;
76 77
		let pickerTitle = props.pickerTitle;
		let pickerTitleStyle = props.pickerTitleStyle;
xwenliang's avatar
xwenliang committed
78 79 80 81 82 83 84
		let pickerToolBarStyle = props.pickerToolBarStyle;
		let pickerItemStyle = props.pickerItemStyle;
		let pickerHeight = props.pickerHeight;
		let showDuration = props.showDuration;
		let pickerData = props.pickerData;
		let selectedValue = props.selectedValue;
		let onPickerDone = props.onPickerDone;
85
		let onPickerCancel = props.onPickerCancel;
xwenliang's avatar
xwenliang committed
86
		let onValueChange = props.onValueChange;
xwenliang's avatar
xwenliang committed
87

xwenliang's avatar
xwenliang committed
88 89 90
		let pickerStyle = pickerData.constructor === Array ? 'parallel' : 'cascade';
		let firstWheelData;
		let firstPickedData;
Zuxuan Liang's avatar
Debug  
Zuxuan Liang committed
91
		let secondPickedData;
xwenliang's avatar
xwenliang committed
92 93 94 95 96
		let secondWheelData;
		let secondPickedDataIndex;
		let thirdWheelData;
		let thirdPickedDataIndex;
		let cascadeData = {};
Jon's avatar
Jon committed
97 98
		let slideAnim = (this.state && this.state.slideAnim ? this.state.slideAnim : new Animated.Value(-props.pickerHeight));
		
xwenliang's avatar
xwenliang committed
99 100
		if(pickerStyle === 'parallel'){
			//compatible single wheel sence
xwenliang's avatar
xwenliang committed
101 102
			if(selectedValue.constructor !== Array){
				selectedValue = [selectedValue];
xwenliang's avatar
xwenliang committed
103 104 105 106 107 108 109 110
			}
			if(pickerData[0].constructor !== Array){
				pickerData = [pickerData];
			}
		}
		else if(pickerStyle === 'cascade'){
			//only support three stage
			firstWheelData = Object.keys(pickerData);
xwenliang's avatar
xwenliang committed
111 112 113
			firstPickedData = props.selectedValue[0];
			secondPickedData = props.selectedValue[1];
			cascadeData = this._getCascadeData(pickerData, selectedValue, firstPickedData, secondPickedData, true);
xwenliang's avatar
xwenliang committed
114
		}
xwenliang's avatar
xwenliang committed
115
		//save picked data
xwenliang's avatar
xwenliang committed
116
		this.pickedValue = JSON.parse(JSON.stringify(selectedValue));
xwenliang's avatar
xwenliang committed
117 118 119
		this.pickerStyle = pickerStyle;
		return {
			pickerBtnText,
120
			pickerCancelBtnText,
xwenliang's avatar
xwenliang committed
121
			pickerBtnStyle,
122 123
			pickerTitle,
			pickerTitleStyle,
xwenliang's avatar
xwenliang committed
124 125 126 127
			pickerToolBarStyle,
			pickerItemStyle,
			pickerHeight,
			showDuration,
xwenliang's avatar
xwenliang committed
128
			pickerData,
xwenliang's avatar
xwenliang committed
129 130
			selectedValue,
			onPickerDone,
131
			onPickerCancel,
xwenliang's avatar
xwenliang committed
132
			onValueChange,
xwenliang's avatar
xwenliang committed
133 134 135 136
			//list of first wheel data
			firstWheelData,
			//first wheel selected value
			firstPickedData,
Jon's avatar
Jon committed
137
			slideAnim,
xwenliang's avatar
xwenliang committed
138 139 140 141 142 143
			//list of second wheel data and pickedDataIndex
			secondWheelData: cascadeData.secondWheelData,
			secondPickedDataIndex: cascadeData.secondPickedDataIndex,
			//third wheel selected value and pickedDataIndex
			thirdWheelData: cascadeData.thirdWheelData,
			thirdPickedDataIndex: cascadeData.thirdPickedDataIndex
xwenliang's avatar
xwenliang committed
144
		};
xwenliang's avatar
bugs  
xwenliang committed
145 146 147 148 149 150 151 152
	}

	_slideUp(){
		this.isMoving = true;
		Animated.timing(
			this.state.slideAnim,
			{
				toValue: 0,
xwenliang's avatar
xwenliang committed
153
				duration: this.state.showDuration,
xwenliang's avatar
bugs  
xwenliang committed
154 155 156 157 158 159 160 161 162 163 164 165 166 167
			}
		).start((evt) => {
			if(evt.finished) {
				this.isMoving = false;
				this.isPickerShow = true;
			}
		});
	}

	_slideDown(){
		this.isMoving = true;
		Animated.timing(
			this.state.slideAnim,
			{
xwenliang's avatar
xwenliang committed
168 169
				toValue: -this.state.pickerHeight,
				duration: this.state.showDuration,
xwenliang's avatar
bugs  
xwenliang committed
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
			}
		).start((evt) => {
			if(evt.finished) {
				this.isMoving = false;
				this.isPickerShow = false;
			}
		});
	}

	_toggle(){
		if(this.isMoving) {
			return;
		}
		if(this.isPickerShow) {
			this._slideDown();
		}
		else{
			this._slideUp();
		}
	}
	//向父组件提供方法
	toggle(){
		this._toggle();
	}
xwenliang's avatar
xwenliang committed
194 195

	_prePressHandle(callback){
xwenliang's avatar
bugs  
xwenliang committed
196 197 198
		//通知子组件往上滚
		this.pickerWheel.moveUp();
	}
xwenliang's avatar
xwenliang committed
199 200

	_nextPressHandle(callback){
xwenliang's avatar
bugs  
xwenliang committed
201 202 203 204
		//通知子组件往下滚
		this.pickerWheel.moveDown();
	}

205 206 207 208 209
	_pickerCancel() {
		this._toggle();
		this.state.onPickerCancel();
	}

xwenliang's avatar
bugs  
xwenliang committed
210 211
	_pickerFinish(){
		this._toggle();
xwenliang's avatar
xwenliang committed
212
		this.state.onPickerDone(this.pickedValue);
xwenliang's avatar
xwenliang committed
213 214 215 216 217 218
	}

	_renderParallelWheel(pickerData){
		let me = this;
		return pickerData.map((item, index) => {
			return (
xwenliang's avatar
xwenliang committed
219
				<View style={styles.pickerWheel} key={index}>
xwenliang's avatar
xwenliang committed
220 221 222 223
					<Picker
						selectedValue={me.state.selectedValue[index]}
						onValueChange={value => {
							me.pickedValue.splice(index, 1, value);
xwenliang's avatar
xwenliang committed
224 225 226 227
							//do not set state to another object!! why?
							// me.setState({
							// 	selectedValue: me.pickedValue
							// });
xwenliang's avatar
xwenliang committed
228
							me.setState({
xwenliang's avatar
xwenliang committed
229
								selectedValue: JSON.parse(JSON.stringify(me.pickedValue))
xwenliang's avatar
xwenliang committed
230
							});
xwenliang's avatar
xwenliang committed
231
							me.state.onValueChange(JSON.parse(JSON.stringify(me.pickedValue)), index);
xwenliang's avatar
xwenliang committed
232 233 234 235 236
						}} >
						{item.map((value, index) => (
							<PickerItem
								key={index}
								value={value}
xwenliang's avatar
xwenliang committed
237
								label={value.toString()}
xwenliang's avatar
xwenliang committed
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
							/>)
						)}
					</Picker>
				</View>
			);
		});
	}

	_getCascadeData(pickerData, pickedValue, firstPickedData, secondPickedData, onInit){
		let secondWheelData;
		let secondPickedDataIndex;
		let thirdWheelData;
		let thirdPickedDataIndex;
		//only support two and three stage
		for(let key in pickerData){
			//two stage
			if(pickerData[key].constructor === Array){
				secondWheelData = pickerData[firstPickedData];
				if(onInit){
					secondWheelData.forEach(function(v, k){
						if(v === pickedValue[1]){
							secondPickedDataIndex = k;
						}
					}.bind(this));
				}
				else{
					secondPickedDataIndex = 0;
				}
				break;
			}
			//three stage
			else{
				secondWheelData = Object.keys(pickerData[firstPickedData]);
				if(onInit){
					secondWheelData.forEach(function(v, k){
						if(v === pickedValue[1]){
							secondPickedDataIndex = k;
						}
					}.bind(this));
				}
				else{
					secondPickedDataIndex = 0;
				}
				thirdWheelData = pickerData[firstPickedData][secondPickedData];
				if(onInit){
					thirdWheelData.forEach(function(v, k){
						if(v === pickedValue[2]){
							thirdPickedDataIndex = k;
						}
					})
				}
				else{
					thirdPickedDataIndex = 0;
				}
				break;
			}
		}

		return {
			secondWheelData,
			secondPickedDataIndex,
			thirdWheelData,
			thirdPickedDataIndex
		}
	}

	_renderCascadeWheel(pickerData){
		let me = this;
		let thirdWheel = me.state.thirdWheelData && (
			<View style={styles.pickerWheel}>
				<Picker
					ref={'thirdWheel'}
					selectedValue={me.state.thirdPickedDataIndex}
					onValueChange={(index) => {
						//on ios platform 'this' refers to Picker?
						me.pickedValue.splice(2, 1, me.state.thirdWheelData[index]);
						me.setState({
xwenliang's avatar
xwenliang committed
315 316
							thirdPickedDataIndex: index,
							selectedValue: 'wheel3'+index
xwenliang's avatar
xwenliang committed
317
						});
xwenliang's avatar
xwenliang committed
318
						me.state.onValueChange(JSON.parse(JSON.stringify(me.pickedValue)), 2);
xwenliang's avatar
xwenliang committed
319 320 321 322 323
					}} >
					{me.state.thirdWheelData.map((value, index) => (
						<PickerItem
							key={index}
							value={index}
xwenliang's avatar
xwenliang committed
324
							label={value.toString()}
xwenliang's avatar
xwenliang committed
325 326 327 328 329 330 331 332 333 334 335 336 337 338
						/>)
					)}
				</Picker>
			</View>
		);

		return (
			<View style={styles.pickerWrap}>
				<View style={styles.pickerWheel}>
					<Picker
						ref={'firstWheel'}
						selectedValue={me.state.firstPickedData}
						onValueChange={value => {
							let secondWheelData = Object.keys(pickerData[value]);
Zuxuan Liang's avatar
Debug  
Zuxuan Liang committed
339
							let cascadeData = me._getCascadeData(pickerData, me.pickedValue, value, secondWheelData[0]);
xwenliang's avatar
xwenliang committed
340 341 342 343 344 345 346 347 348 349
							//when onPicked, this.pickedValue will pass to the parent
							//when firstWheel changed, second and third will also change
							if(cascadeData.thirdWheelData){
								me.pickedValue.splice(0, 3, value, cascadeData.secondWheelData[0], cascadeData.thirdWheelData[0]);
							}
							else{
								me.pickedValue.splice(0, 2, value, cascadeData.secondWheelData[0]);
							}
							
							me.setState({
xwenliang's avatar
xwenliang committed
350
								selectedValue: 'wheel1'+value,
xwenliang's avatar
xwenliang committed
351 352 353 354 355 356
								firstPickedData: value,
								secondWheelData: cascadeData.secondWheelData,
								secondPickedDataIndex: 0,
								thirdWheelData: cascadeData.thirdWheelData,
								thirdPickedDataIndex: 0
							});
xwenliang's avatar
xwenliang committed
357
							me.state.onValueChange(JSON.parse(JSON.stringify(me.pickedValue)), 0);
xwenliang's avatar
xwenliang committed
358 359 360 361 362 363 364
							me.refs.secondWheel && me.refs.secondWheel.moveTo && me.refs.secondWheel.moveTo(0);
							me.refs.thirdWheel && me.refs.thirdWheel.moveTo && me.refs.thirdWheel.moveTo(0);
						}} >
						{me.state.firstWheelData.map((value, index) => (
							<PickerItem
								key={index}
								value={value}
xwenliang's avatar
xwenliang committed
365
								label={value.toString()}
xwenliang's avatar
xwenliang committed
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
							/>)
						)}
					</Picker>
				</View>
				<View style={styles.pickerWheel}>
					<Picker
						ref={'secondWheel'}
						selectedValue={me.state.secondPickedDataIndex}
						onValueChange={(index) => {
							let thirdWheelData = pickerData[me.state.firstPickedData][me.state.secondWheelData[index]];
							if(thirdWheelData){
								me.pickedValue.splice(1, 2, me.state.secondWheelData[index], thirdWheelData[0]);	
							}
							else{
								me.pickedValue.splice(1, 1, me.state.secondWheelData[index]);
							}
							
							me.setState({
								secondPickedDataIndex: index,
								thirdWheelData,
xwenliang's avatar
xwenliang committed
386 387
								thirdPickedDataIndex: 0,
								selectedValue: 'wheel2'+index
xwenliang's avatar
xwenliang committed
388
							});
xwenliang's avatar
xwenliang committed
389
							me.state.onValueChange(JSON.parse(JSON.stringify(me.pickedValue)), 1);
xwenliang's avatar
xwenliang committed
390 391 392 393 394 395
							me.refs.thirdWheel && me.refs.thirdWheel.moveTo && me.refs.thirdWheel.moveTo(0);
						}} >
						{me.state.secondWheelData.map((value, index) => (
							<PickerItem
								key={index}
								value={index}
xwenliang's avatar
xwenliang committed
396
								label={value.toString()}
xwenliang's avatar
xwenliang committed
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
							/>)
						)}
					</Picker>
				</View>
				{thirdWheel}
			</View>
		);
	}

	_renderWheel(pickerData){
		/*
			some sences:
			1.	single wheel:
				[1,2,3,4]
			2.	two or more:
				[
					[1,2,3,4],
					[5,6,7,8],
					...
				]
			3.	two stage cascade:
				{
					a: [1,2,3,4],
					b: [5,6,7,8],
					...
				}
			4.	three stage cascade:
				{
					a: {
						a1: [1,2,3,4],
						a2: [5,6,7,8],
						a3: [9,10,11,12]
					},
					b: {
						b1: [1,2,3,4],
						b2: [5,6,7,8],
						b3: [9,10,12,12]
					}
					...
				}
			we call 1、2 parallel and 3、4 cascade
		*/
		let wheel = null;
		if(this.pickerStyle === 'parallel'){
			wheel = this._renderParallelWheel(pickerData);
		}
		else if(this.pickerStyle === 'cascade'){
			wheel = this._renderCascadeWheel(pickerData);
		}
		return wheel;
xwenliang's avatar
bugs  
xwenliang committed
447
	}
xwenliang's avatar
xwenliang committed
448 449
	
	render(){
xwenliang's avatar
xwenliang committed
450
		/*let pickerBtn = Platform.OS === 'ios' ? null : (
xwenliang's avatar
bugs  
xwenliang committed
451 452 453
			<View style={styles.pickerBtnView}>
				<Text style={styles.pickerMoveBtn} onPress={this._prePressHandle.bind(this)}>上一个</Text>
				<Text style={styles.pickerMoveBtn} onPress={this._nextPressHandle.bind(this)}>下一个</Text>
xwenliang's avatar
xwenliang committed
454
			</View>
xwenliang's avatar
xwenliang committed
455
		);*/
456
		// let pickerBtn = null;
xwenliang's avatar
bugs  
xwenliang committed
457 458
		return (
			<Animated.View style={[styles.picker, {
xwenliang's avatar
xwenliang committed
459
				height: this.state.pickerHeight,
xwenliang's avatar
bugs  
xwenliang committed
460 461
				bottom: this.state.slideAnim
			}]}>
xwenliang's avatar
xwenliang committed
462
				<View style={[styles.pickerToolbar, this.state.pickerToolBarStyle]}>
463 464
					<View style={styles.pickerCancelBtn}>
						<Text style={[styles.pickerFinishBtnText, this.state.pickerBtnStyle]}
465
							onPress={this._pickerCancel.bind(this)}>{this.state.pickerCancelBtnText}</Text>
466 467 468 469
					</View>
					<Text style={[styles.pickerTitle, this.state.pickerTitleStyle]} numberOfLines={1}>
						{this.state.pickerTitle}
					</Text>
xwenliang's avatar
bugs  
xwenliang committed
470
					<View style={styles.pickerFinishBtn}>
xwenliang's avatar
xwenliang committed
471 472
						<Text style={[styles.pickerFinishBtnText, this.state.pickerBtnStyle]}
							onPress={this._pickerFinish.bind(this)}>{this.state.pickerBtnText}</Text>
xwenliang's avatar
bugs  
xwenliang committed
473 474
					</View>
				</View>
xwenliang's avatar
xwenliang committed
475 476 477
				<View style={styles.pickerWrap}>
					{this._renderWheel(this.state.pickerData)}
				</View>
xwenliang's avatar
bugs  
xwenliang committed
478 479 480
			</Animated.View>
		);
	}
xwenliang's avatar
xwenliang committed
481 482 483
};

let styles = StyleSheet.create({
xwenliang's avatar
bugs  
xwenliang committed
484
	picker: {
xwenliang's avatar
xwenliang committed
485
		width: width,
xwenliang's avatar
xwenliang committed
486 487 488
		position: 'absolute',
		bottom: 0,
		left: 0,
xwenliang's avatar
bugs  
xwenliang committed
489
		backgroundColor: '#bdc0c7',
xwenliang's avatar
xwenliang committed
490 491 492
		overflow: 'hidden'
	},
	pickerWrap: {
xwenliang's avatar
bugs  
xwenliang committed
493
		width: width,
xwenliang's avatar
xwenliang committed
494 495 496 497
		flexDirection: 'row'
	},
	pickerWheel: {
		flex: 1
xwenliang's avatar
xwenliang committed
498 499 500 501 502 503 504 505
	},
	pickerToolbar: {
		height: 30,
		width: width,
		backgroundColor: '#e6e6e6',
		flexDirection: 'row',
		borderTopWidth: 1,
		borderBottomWidth: 1,
xwenliang's avatar
xwenliang committed
506 507
		borderColor: '#c3c3c3',
		alignItems: 'center'
xwenliang's avatar
xwenliang committed
508 509 510 511 512
	},
	pickerBtnView: {
		flex: 1,
		flexDirection: 'row',
		justifyContent: 'flex-start',
xwenliang's avatar
bugs  
xwenliang committed
513
		alignItems: 'center'
xwenliang's avatar
xwenliang committed
514 515 516 517
	},
	pickerMoveBtn: {
		color: '#149be0',
		fontSize: 16,
xwenliang's avatar
bugs  
xwenliang committed
518
		marginLeft: 20
xwenliang's avatar
xwenliang committed
519
	},
520 521 522 523 524 525 526 527
	pickerCancelBtn: {
		flex: 1,
		flexDirection: 'row',
		justifyContent: 'flex-start',
		alignItems: 'center',
		marginLeft: 20
	},
	pickerTitle: {
xwenliang's avatar
xwenliang committed
528
		flex: 4,
529 530 531
		color: 'black',
		textAlign: 'center'
	},
xwenliang's avatar
xwenliang committed
532 533 534 535 536
	pickerFinishBtn: {
		flex: 1,
		flexDirection: 'row',
		justifyContent: 'flex-end',
		alignItems: 'center',
xwenliang's avatar
bugs  
xwenliang committed
537
		marginRight: 20
xwenliang's avatar
xwenliang committed
538 539 540 541 542 543
	},
	pickerFinishBtnText: {
		fontSize: 16,
		color: '#149be0'
	}
});