index.js 12.6 KB
Newer Older
xwenliang's avatar
xwenliang committed
1 2
'use strict';

xwenliang's avatar
xwenliang committed
3 4
import React, {Component, PropTypes} from 'react';
import {
5 6
	StyleSheet,
	View,
xwenliang's avatar
xwenliang committed
7 8 9
	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;
18
let {width, height} = Dimensions.get('window');
xwenliang's avatar
xwenliang committed
19

20 21 22
const longSide = width > height ? width : height;
const shortSide = width > height ? height : width;

23
export default class PickerAny extends Component {
xwenliang's avatar
xwenliang committed
24

xwenliang's avatar
bugs  
xwenliang committed
25
	static propTypes = {
26
		style: View.propTypes.style,
xwenliang's avatar
xwenliang committed
27
		pickerElevation: PropTypes.number,
xwenliang's avatar
xwenliang committed
28
		pickerBtnText: PropTypes.string,
29
		pickerCancelBtnText: PropTypes.string,
30
		pickerBtnStyle: Text.propTypes.style,
31
		pickerTitle: PropTypes.string,
32 33
		pickerTitleStyle: Text.propTypes.style,
		pickerToolBarStyle: View.propTypes.style,
xwenliang's avatar
xwenliang committed
34
		showMask: PropTypes.bool,
xwenliang's avatar
bugs  
xwenliang committed
35
		showDuration: PropTypes.number,
xwenliang's avatar
xwenliang committed
36 37
		pickerData: PropTypes.any.isRequired,
		selectedValue: PropTypes.any.isRequired,
38
		onPickerDone: PropTypes.func,
xwenliang's avatar
xwenliang committed
39 40
		onPickerCancel: PropTypes.func,
		onValueChange: PropTypes.func
xwenliang's avatar
xwenliang committed
41
	};
xwenliang's avatar
bugs  
xwenliang committed
42 43

	static defaultProps = {
xwenliang's avatar
xwenliang committed
44 45 46 47 48 49
		style: {
			width: width
		},
		pickerBtnText: 'Done',
		pickerCancelBtnText: 'Cancel',
		showMask: false,
xwenliang's avatar
bugs  
xwenliang committed
50
		showDuration: 300,
51
		onPickerDone: ()=>{},
xwenliang's avatar
xwenliang committed
52
		onPickerCancel: ()=>{},
xwenliang's avatar
xwenliang committed
53
		onValueChange: ()=>{}
xwenliang's avatar
xwenliang committed
54
	};
xwenliang's avatar
bugs  
xwenliang committed
55

xwenliang's avatar
xwenliang committed
56 57
	constructor(props, context){
		super(props, context);
xwenliang's avatar
xwenliang committed
58 59 60 61 62 63 64 65 66 67 68
	}

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

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

xwenliang's avatar
xwenliang committed
69 70 71
	shouldComponentUpdate(nextProps, nextState, context){
		return true;
	}
xwenliang's avatar
xwenliang committed
72

xwenliang's avatar
xwenliang committed
73
	_getStateFromProps(props){
xwenliang's avatar
xwenliang committed
74
		//the pickedValue must looks like [wheelone's, wheeltwo's, ...]
xwenliang's avatar
xwenliang committed
75
		//this.state.selectedValue may be the result of the first pickerWheel
76
		let {pickerData, selectedValue} = props;
xwenliang's avatar
xwenliang committed
77 78 79
		let pickerStyle = pickerData.constructor === Array ? 'parallel' : 'cascade';
		let firstWheelData;
		let firstPickedData;
Zuxuan Liang's avatar
Debug  
Zuxuan Liang committed
80
		let secondPickedData;
xwenliang's avatar
xwenliang committed
81 82 83 84 85
		let secondWheelData;
		let secondPickedDataIndex;
		let thirdWheelData;
		let thirdPickedDataIndex;
		let cascadeData = {};
xwenliang's avatar
xwenliang committed
86
		let slideAnim = (this.state && this.state.slideAnim ? this.state.slideAnim : new Animated.Value(-height));
87

xwenliang's avatar
xwenliang committed
88 89
		if(pickerStyle === 'parallel'){
			//compatible single wheel sence
xwenliang's avatar
xwenliang committed
90 91
			if(selectedValue.constructor !== Array){
				selectedValue = [selectedValue];
xwenliang's avatar
xwenliang committed
92 93 94 95 96 97 98 99
			}
			if(pickerData[0].constructor !== Array){
				pickerData = [pickerData];
			}
		}
		else if(pickerStyle === 'cascade'){
			//only support three stage
			firstWheelData = Object.keys(pickerData);
xwenliang's avatar
xwenliang committed
100 101 102
			firstPickedData = props.selectedValue[0];
			secondPickedData = props.selectedValue[1];
			cascadeData = this._getCascadeData(pickerData, selectedValue, firstPickedData, secondPickedData, true);
xwenliang's avatar
xwenliang committed
103
		}
xwenliang's avatar
xwenliang committed
104
		//save picked data
xwenliang's avatar
xwenliang committed
105
		this.pickedValue = JSON.parse(JSON.stringify(selectedValue));
xwenliang's avatar
xwenliang committed
106 107
		this.pickerStyle = pickerStyle;
		return {
108
			...props,
xwenliang's avatar
xwenliang committed
109
			pickerData,
xwenliang's avatar
xwenliang committed
110
			selectedValue,
xwenliang's avatar
xwenliang committed
111 112 113 114
			//list of first wheel data
			firstWheelData,
			//first wheel selected value
			firstPickedData,
Jon's avatar
Jon committed
115
			slideAnim,
xwenliang's avatar
xwenliang committed
116 117 118 119 120 121
			//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
122
		};
xwenliang's avatar
bugs  
xwenliang committed
123 124 125
	}

	_slideUp(){
xwenliang's avatar
xwenliang committed
126
		this._isMoving = true;
xwenliang's avatar
bugs  
xwenliang committed
127 128 129 130
		Animated.timing(
			this.state.slideAnim,
			{
				toValue: 0,
xwenliang's avatar
xwenliang committed
131
				duration: this.state.showDuration,
xwenliang's avatar
bugs  
xwenliang committed
132 133 134
			}
		).start((evt) => {
			if(evt.finished) {
xwenliang's avatar
xwenliang committed
135 136
				this._isMoving = false;
				this._isPickerShow = true;
xwenliang's avatar
bugs  
xwenliang committed
137 138 139 140 141
			}
		});
	}

	_slideDown(){
xwenliang's avatar
xwenliang committed
142
		this._isMoving = true;
xwenliang's avatar
bugs  
xwenliang committed
143 144 145
		Animated.timing(
			this.state.slideAnim,
			{
xwenliang's avatar
xwenliang committed
146
				toValue: -height,
xwenliang's avatar
xwenliang committed
147
				duration: this.state.showDuration,
xwenliang's avatar
bugs  
xwenliang committed
148 149 150
			}
		).start((evt) => {
			if(evt.finished) {
xwenliang's avatar
xwenliang committed
151 152
				this._isMoving = false;
				this._isPickerShow = false;
xwenliang's avatar
bugs  
xwenliang committed
153 154 155 156 157
			}
		});
	}

	_toggle(){
xwenliang's avatar
xwenliang committed
158
		if(this._isMoving) {
xwenliang's avatar
bugs  
xwenliang committed
159 160
			return;
		}
xwenliang's avatar
xwenliang committed
161
		if(this._isPickerShow) {
xwenliang's avatar
bugs  
xwenliang committed
162 163 164 165 166 167
			this._slideDown();
		}
		else{
			this._slideUp();
		}
	}
168
	
xwenliang's avatar
bugs  
xwenliang committed
169 170 171
	toggle(){
		this._toggle();
	}
xwenliang's avatar
xwenliang committed
172 173 174 175 176 177 178 179 180 181 182 183 184
	show(){
		if(!this._isPickerShow){
			this._slideUp();
		}
	}
	hide(){
		if(this._isPickerShow){
			this._slideDown();
		}
	}
	isPickerShow(){
		return this._isPickerShow;
	}
xwenliang's avatar
xwenliang committed
185 186

	_prePressHandle(callback){
xwenliang's avatar
bugs  
xwenliang committed
187 188
		this.pickerWheel.moveUp();
	}
xwenliang's avatar
xwenliang committed
189 190

	_nextPressHandle(callback){
xwenliang's avatar
bugs  
xwenliang committed
191 192 193
		this.pickerWheel.moveDown();
	}

xwenliang's avatar
xwenliang committed
194
	_pickerCancel(){
195 196 197 198
		this._toggle();
		this.state.onPickerCancel();
	}

xwenliang's avatar
bugs  
xwenliang committed
199 200
	_pickerFinish(){
		this._toggle();
xwenliang's avatar
xwenliang committed
201
		this.state.onPickerDone(this.pickedValue);
xwenliang's avatar
xwenliang committed
202 203 204 205 206
	}

	_renderParallelWheel(pickerData){
		return pickerData.map((item, index) => {
			return (
xwenliang's avatar
xwenliang committed
207
				<View style={styles.pickerWheel} key={index}>
xwenliang's avatar
xwenliang committed
208
					<Picker
xwenliang's avatar
xwenliang committed
209
						selectedValue={this.state.selectedValue[index]}
xwenliang's avatar
xwenliang committed
210
						onValueChange={value => {
xwenliang's avatar
xwenliang committed
211
							this.pickedValue.splice(index, 1, value);
xwenliang's avatar
xwenliang committed
212
							//do not set state to another object!! why?
xwenliang's avatar
xwenliang committed
213 214
							// this.setState({
							// 	selectedValue: this.pickedValue
xwenliang's avatar
xwenliang committed
215
							// });
xwenliang's avatar
xwenliang committed
216 217
							this.setState({
								selectedValue: JSON.parse(JSON.stringify(this.pickedValue))
xwenliang's avatar
xwenliang committed
218
							});
xwenliang's avatar
xwenliang committed
219
							this.state.onValueChange(JSON.parse(JSON.stringify(this.pickedValue)), index);
xwenliang's avatar
xwenliang committed
220 221 222 223 224
						}} >
						{item.map((value, index) => (
							<PickerItem
								key={index}
								value={value}
xwenliang's avatar
xwenliang committed
225
								label={value.toString()}
xwenliang's avatar
xwenliang committed
226 227 228 229 230 231 232 233 234 235 236 237 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
							/>)
						)}
					</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){
xwenliang's avatar
xwenliang committed
293
		let thirdWheel = this.state.thirdWheelData && (
xwenliang's avatar
xwenliang committed
294 295 296
			<View style={styles.pickerWheel}>
				<Picker
					ref={'thirdWheel'}
xwenliang's avatar
xwenliang committed
297
					selectedValue={this.state.thirdPickedDataIndex}
xwenliang's avatar
xwenliang committed
298
					onValueChange={(index) => {
xwenliang's avatar
xwenliang committed
299 300
						this.pickedValue.splice(2, 1, this.state.thirdWheelData[index]);
						this.setState({
xwenliang's avatar
xwenliang committed
301 302
							thirdPickedDataIndex: index,
							selectedValue: 'wheel3'+index
xwenliang's avatar
xwenliang committed
303
						});
xwenliang's avatar
xwenliang committed
304
						this.state.onValueChange(JSON.parse(JSON.stringify(this.pickedValue)), 2);
xwenliang's avatar
xwenliang committed
305
					}} >
xwenliang's avatar
xwenliang committed
306
					{this.state.thirdWheelData.map((value, index) => (
xwenliang's avatar
xwenliang committed
307 308 309
						<PickerItem
							key={index}
							value={index}
xwenliang's avatar
xwenliang committed
310
							label={value.toString()}
xwenliang's avatar
xwenliang committed
311 312 313 314 315 316 317
						/>)
					)}
				</Picker>
			</View>
		);

		return (
xwenliang's avatar
xwenliang committed
318
			<View style={[styles.pickerWrap, {width: this.state.style.width || width}]}>
xwenliang's avatar
xwenliang committed
319 320 321
				<View style={styles.pickerWheel}>
					<Picker
						ref={'firstWheel'}
xwenliang's avatar
xwenliang committed
322
						selectedValue={this.state.firstPickedData}
xwenliang's avatar
xwenliang committed
323 324
						onValueChange={value => {
							let secondWheelData = Object.keys(pickerData[value]);
xwenliang's avatar
xwenliang committed
325
							let cascadeData = this._getCascadeData(pickerData, this.pickedValue, value, secondWheelData[0]);
xwenliang's avatar
xwenliang committed
326 327 328
							//when onPicked, this.pickedValue will pass to the parent
							//when firstWheel changed, second and third will also change
							if(cascadeData.thirdWheelData){
xwenliang's avatar
xwenliang committed
329
								this.pickedValue.splice(0, 3, value, cascadeData.secondWheelData[0], cascadeData.thirdWheelData[0]);
xwenliang's avatar
xwenliang committed
330 331
							}
							else{
xwenliang's avatar
xwenliang committed
332
								this.pickedValue.splice(0, 2, value, cascadeData.secondWheelData[0]);
xwenliang's avatar
xwenliang committed
333
							}
334

xwenliang's avatar
xwenliang committed
335
							this.setState({
xwenliang's avatar
xwenliang committed
336
								selectedValue: 'wheel1'+value,
xwenliang's avatar
xwenliang committed
337 338 339 340 341 342
								firstPickedData: value,
								secondWheelData: cascadeData.secondWheelData,
								secondPickedDataIndex: 0,
								thirdWheelData: cascadeData.thirdWheelData,
								thirdPickedDataIndex: 0
							});
xwenliang's avatar
xwenliang committed
343 344 345
							this.state.onValueChange(JSON.parse(JSON.stringify(this.pickedValue)), 0);
							this.refs.secondWheel && this.refs.secondWheel.moveTo && this.refs.secondWheel.moveTo(0);
							this.refs.thirdWheel && this.refs.thirdWheel.moveTo && this.refs.thirdWheel.moveTo(0);
xwenliang's avatar
xwenliang committed
346
						}} >
xwenliang's avatar
xwenliang committed
347
						{this.state.firstWheelData.map((value, index) => (
xwenliang's avatar
xwenliang committed
348 349 350
							<PickerItem
								key={index}
								value={value}
xwenliang's avatar
xwenliang committed
351
								label={value.toString()}
xwenliang's avatar
xwenliang committed
352 353 354 355 356 357 358
							/>)
						)}
					</Picker>
				</View>
				<View style={styles.pickerWheel}>
					<Picker
						ref={'secondWheel'}
xwenliang's avatar
xwenliang committed
359
						selectedValue={this.state.secondPickedDataIndex}
xwenliang's avatar
xwenliang committed
360
						onValueChange={(index) => {
xwenliang's avatar
xwenliang committed
361
							let thirdWheelData = pickerData[this.state.firstPickedData][this.state.secondWheelData[index]];
xwenliang's avatar
xwenliang committed
362
							if(thirdWheelData){
363
								this.pickedValue.splice(1, 2, this.state.secondWheelData[index], thirdWheelData[0]);
xwenliang's avatar
xwenliang committed
364 365
							}
							else{
xwenliang's avatar
xwenliang committed
366
								this.pickedValue.splice(1, 1, this.state.secondWheelData[index]);
xwenliang's avatar
xwenliang committed
367
							}
368

xwenliang's avatar
xwenliang committed
369
							this.setState({
xwenliang's avatar
xwenliang committed
370 371
								secondPickedDataIndex: index,
								thirdWheelData,
xwenliang's avatar
xwenliang committed
372 373
								thirdPickedDataIndex: 0,
								selectedValue: 'wheel2'+index
xwenliang's avatar
xwenliang committed
374
							});
xwenliang's avatar
xwenliang committed
375 376
							this.state.onValueChange(JSON.parse(JSON.stringify(this.pickedValue)), 1);
							this.refs.thirdWheel && this.refs.thirdWheel.moveTo && this.refs.thirdWheel.moveTo(0);
xwenliang's avatar
xwenliang committed
377
						}} >
xwenliang's avatar
xwenliang committed
378
						{this.state.secondWheelData.map((value, index) => (
xwenliang's avatar
xwenliang committed
379 380 381
							<PickerItem
								key={index}
								value={index}
xwenliang's avatar
xwenliang committed
382
								label={value.toString()}
xwenliang's avatar
xwenliang committed
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
							/>)
						)}
					</Picker>
				</View>
				{thirdWheel}
			</View>
		);
	}

	_renderWheel(pickerData){
		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
401
	}
402

xwenliang's avatar
xwenliang committed
403
	render(){
xwenliang's avatar
xwenliang committed
404 405 406 407 408 409 410

		let mask = this.state.showMask ? (
			<View style={styles.mask} >
				<Text style={{width: width, height: height}} onPress={this._pickerCancel.bind(this)}></Text>
			</View>
		) : null;

xwenliang's avatar
bugs  
xwenliang committed
411 412
		return (
			<Animated.View style={[styles.picker, {
xwenliang's avatar
xwenliang committed
413
				elevation: this.state.pickerElevation,
414
				width: longSide,
xwenliang's avatar
xwenliang committed
415
				height: this.state.showMask ? height : this.state.style.height,
xwenliang's avatar
bugs  
xwenliang committed
416
				bottom: this.state.slideAnim
xwenliang's avatar
xwenliang committed
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
			}]}>
				{mask}
				<View style={[styles.pickerBox, this.state.style]}>
					<View style={[styles.pickerToolbar, this.state.pickerToolBarStyle, {width: this.state.style.width || width}]}>
						<View style={styles.pickerCancelBtn}>
							<Text style={[styles.pickerFinishBtnText, this.state.pickerBtnStyle]}
								onPress={this._pickerCancel.bind(this)}>{this.state.pickerCancelBtnText}</Text>
						</View>
						<Text style={[styles.pickerTitle, this.state.pickerTitleStyle]} numberOfLines={1}>
							{this.state.pickerTitle}
						</Text>
						<View style={styles.pickerFinishBtn}>
							<Text style={[styles.pickerFinishBtnText, this.state.pickerBtnStyle]}
								onPress={this._pickerFinish.bind(this)}>{this.state.pickerBtnText}</Text>
						</View>
432
					</View>
xwenliang's avatar
xwenliang committed
433 434
					<View style={[styles.pickerWrap, {width: this.state.style.width || width}]}>
						{this._renderWheel(this.state.pickerData)}
xwenliang's avatar
bugs  
xwenliang committed
435 436 437 438 439
					</View>
				</View>
			</Animated.View>
		);
	}
xwenliang's avatar
xwenliang committed
440 441 442
};

let styles = StyleSheet.create({
xwenliang's avatar
bugs  
xwenliang committed
443
	picker: {
xwenliang's avatar
xwenliang committed
444 445 446
		position: 'absolute',
		bottom: 0,
		left: 0,
xwenliang's avatar
xwenliang committed
447
		backgroundColor: 'transparent',
xwenliang's avatar
xwenliang committed
448
	},
xwenliang's avatar
xwenliang committed
449 450 451
	pickerBox: {
		position: 'absolute',
		bottom: 0,
xwenliang's avatar
xwenliang committed
452 453
		left: 0,
		backgroundColor: '#bdc0c7'
xwenliang's avatar
xwenliang committed
454 455 456 457 458 459 460 461
	},
	mask: {
		position: 'absolute',
		top: 0,
		backgroundColor: 'transparent',
		height: height,
		width: width
	},
xwenliang's avatar
xwenliang committed
462 463 464 465 466
	pickerWrap: {
		flexDirection: 'row'
	},
	pickerWheel: {
		flex: 1
xwenliang's avatar
xwenliang committed
467 468 469 470 471 472 473
	},
	pickerToolbar: {
		height: 30,
		backgroundColor: '#e6e6e6',
		flexDirection: 'row',
		borderTopWidth: 1,
		borderBottomWidth: 1,
xwenliang's avatar
xwenliang committed
474 475
		borderColor: '#c3c3c3',
		alignItems: 'center'
xwenliang's avatar
xwenliang committed
476 477 478 479 480
	},
	pickerBtnView: {
		flex: 1,
		flexDirection: 'row',
		justifyContent: 'flex-start',
xwenliang's avatar
bugs  
xwenliang committed
481
		alignItems: 'center'
xwenliang's avatar
xwenliang committed
482 483 484 485
	},
	pickerMoveBtn: {
		color: '#149be0',
		fontSize: 16,
xwenliang's avatar
bugs  
xwenliang committed
486
		marginLeft: 20
xwenliang's avatar
xwenliang committed
487
	},
488 489 490 491 492 493 494 495
	pickerCancelBtn: {
		flex: 1,
		flexDirection: 'row',
		justifyContent: 'flex-start',
		alignItems: 'center',
		marginLeft: 20
	},
	pickerTitle: {
xwenliang's avatar
xwenliang committed
496
		flex: 4,
497 498 499
		color: 'black',
		textAlign: 'center'
	},
xwenliang's avatar
xwenliang committed
500 501 502 503 504
	pickerFinishBtn: {
		flex: 1,
		flexDirection: 'row',
		justifyContent: 'flex-end',
		alignItems: 'center',
xwenliang's avatar
bugs  
xwenliang committed
505
		marginRight: 20
xwenliang's avatar
xwenliang committed
506 507 508 509 510 511
	},
	pickerFinishBtnText: {
		fontSize: 16,
		color: '#149be0'
	}
});