/adei/trunk

To get this branch, use:
bzr branch http://darksoft.org/webbzr/adei/trunk
210.1.1 by Suren A. Chilingaryan
Support of Appled devices by Toni Pirhonen
1
/**
2
 * 
3
 * Find more about the Spinning Wheel function at
4
 * http://cubiq.org/spinning-wheel-on-webkit-for-iphone-ipod-touch/11
5
 *
6
 * Copyright (c) 2009 Matteo Spinelli, http://cubiq.org/
7
 * Released under MIT license
8
 * http://cubiq.org/dropbox/mit-license.txt
9
 * 
10
 * Version 1.4 - Last updated: 2009.07.09
11
 * 
12
 */
13
14
var SpinningWheel = {
15
	cellHeight: 44,
16
	friction: 0.003,
17
	slotData: [],
18
19
20
	/**
21
	 *
22
	 * Event handler
23
	 *
24
	 */
25
26
	handleEvent: function (e) {
27
		if (e.type == 'touchstart') {
28
			this.lockScreen(e);
29
			if (e.currentTarget.id == 'sw-cancel' || e.currentTarget.id == 'sw-done') {
30
				this.tapDown(e);
31
			} else if (e.currentTarget.id == 'sw-frame') {
32
				this.scrollStart(e);
33
			}
34
		} else if (e.type == 'touchmove') {
35
			this.lockScreen(e);
36
			
37
			if (e.currentTarget.id == 'sw-cancel' || e.currentTarget.id == 'sw-done') {
38
				this.tapCancel(e);
39
			} else if (e.currentTarget.id == 'sw-frame') {
40
				this.scrollMove(e);
41
			}
42
		} else if (e.type == 'touchend') {
43
			if (e.currentTarget.id == 'sw-cancel' || e.currentTarget.id == 'sw-done') {
44
				this.tapUp(e);
45
			} else if (e.currentTarget.id == 'sw-frame') {
46
				this.scrollEnd(e);
47
			}
48
		} else if (e.type == 'webkitTransitionEnd') {
49
			if (e.target.id == 'sw-wrapper') {
50
				this.destroy();
51
			} else {
52
				this.backWithinBoundaries(e);
53
			}
54
		} else if (e.type == 'orientationchange') {
55
			this.onOrientationChange(e);
56
		} else if (e.type == 'scroll') {
57
			this.onScroll(e);
58
		}
59
	},
60
61
62
	/**
63
	 *
64
	 * Global events
65
	 *
66
	 */
67
68
	onOrientationChange: function (e) {
69
		window.scrollTo(0, 0);
70
		this.swWrapper.style.top = window.innerHeight + window.pageYOffset + 'px';
71
		this.calculateSlotsWidth();
72
	},
73
	
74
	onScroll: function (e) {
75
		this.swWrapper.style.top = window.innerHeight + window.pageYOffset + 'px';
76
	},
77
78
	lockScreen: function (e) {
79
		e.preventDefault();
80
		e.stopPropagation();
81
	},
82
83
84
	/**
85
	 *
86
	 * Initialization
87
	 *
88
	 */
89
90
	reset: function () {
91
		this.slotEl = [];
92
93
		this.activeSlot = null;
94
		
95
		this.swWrapper = undefined;
96
		this.swSlotWrapper = undefined;
97
		this.swSlots = undefined;
98
		this.swFrame = undefined;
99
	},
100
101
	calculateSlotsWidth: function () {
102
		var div = this.swSlots.getElementsByTagName('div');
103
		for (var i = 0; i < div.length; i += 1) {
104
			this.slotEl[i].slotWidth = div[i].offsetWidth;
105
		}
106
	},
107
108
	create: function () {
109
		var i, l, out, ul, div;
110
111
		this.reset();	// Initialize object variables
112
113
		// Create the Spinning Wheel main wrapper
114
		div = document.createElement('div');
115
		div.id = 'sw-wrapper';
116
		div.style.top = window.innerHeight + window.pageYOffset + 'px';		// Place the SW down the actual viewing screen
117
		div.style.webkitTransitionProperty = '-webkit-transform';
118
		div.innerHTML = '<div id="sw-header"><div id="sw-cancel">Cancel</' + 'div><div id="sw-done">Done</' + 'div></' + 'div><div id="sw-slots-wrapper"><div id="sw-slots"></' + 'div></' + 'div><div id="sw-frame"></' + 'div>';
119
120
		document.body.appendChild(div);
121
122
		this.swWrapper = div;													// The SW wrapper
123
		this.swSlotWrapper = document.getElementById('sw-slots-wrapper');		// Slots visible area
124
		this.swSlots = document.getElementById('sw-slots');						// Pseudo table element (inner wrapper)
125
		this.swFrame = document.getElementById('sw-frame');						// The scrolling controller
126
127
		// Create HTML slot elements
128
		for (l = 0; l < this.slotData.length; l += 1) {
129
			// Create the slot
130
			ul = document.createElement('ul');
131
			out = '';
132
			for (i in this.slotData[l].values) {
133
				out += '<li>' + this.slotData[l].values[i] + '<' + '/li>';
134
			}
135
			ul.innerHTML = out;
136
137
			div = document.createElement('div');		// Create slot container
138
			div.className = this.slotData[l].style;		// Add styles to the container
139
			div.appendChild(ul);
140
	
141
			// Append the slot to the wrapper
142
			this.swSlots.appendChild(div);
143
			
144
			ul.slotPosition = l;			// Save the slot position inside the wrapper
145
			ul.slotYPosition = 0;
146
			ul.slotWidth = 0;
147
			ul.slotMaxScroll = this.swSlotWrapper.clientHeight - ul.clientHeight - 86;
148
			ul.style.webkitTransitionTimingFunction = 'cubic-bezier(0, 0, 0.2, 1)';		// Add default transition
149
			
150
			this.slotEl.push(ul);			// Save the slot for later use
151
			
152
			// Place the slot to its default position (if other than 0)
153
			if (this.slotData[l].defaultValue) {
154
				this.scrollToValue(l, this.slotData[l].defaultValue);	
155
			}
156
		}
157
		
158
		this.calculateSlotsWidth();
159
		
160
		// Global events
161
		document.addEventListener('touchstart', this, false);			// Prevent page scrolling
162
		document.addEventListener('touchmove', this, false);			// Prevent page scrolling
163
		window.addEventListener('orientationchange', this, true);		// Optimize SW on orientation change
164
		window.addEventListener('scroll', this, true);				// Reposition SW on page scroll
165
166
		// Cancel/Done buttons events
167
		document.getElementById('sw-cancel').addEventListener('touchstart', this, false);
168
		document.getElementById('sw-done').addEventListener('touchstart', this, false);
169
170
		// Add scrolling to the slots
171
		this.swFrame.addEventListener('touchstart', this, false);
172
	},
173
174
	open: function () {
175
		this.create();
176
177
		this.swWrapper.style.webkitTransitionTimingFunction = 'ease-out';
178
		this.swWrapper.style.webkitTransitionDuration = '400ms';
179
		this.swWrapper.style.webkitTransform = 'translate3d(0, -260px, 0)';
180
	},
181
	
182
	
183
	/**
184
	 *
185
	 * Unload
186
	 *
187
	 */
188
189
	destroy: function () {
190
		this.swWrapper.removeEventListener('webkitTransitionEnd', this, false);
191
192
		this.swFrame.removeEventListener('touchstart', this, false);
193
194
		document.getElementById('sw-cancel').removeEventListener('touchstart', this, false);
195
		document.getElementById('sw-done').removeEventListener('touchstart', this, false);
196
197
		document.removeEventListener('touchstart', this, false);
198
		document.removeEventListener('touchmove', this, false);
199
		window.removeEventListener('orientationchange', this, true);
200
		window.removeEventListener('scroll', this, true);
201
		
202
		this.slotData = [];
203
		this.cancelAction = function () {
204
			return false;
205
		};
206
		
207
		this.cancelDone = function () {
208
			return true;
209
		};
210
		
211
		this.reset();
212
		
213
		document.body.removeChild(document.getElementById('sw-wrapper'));
214
	},
215
	
216
	close: function () {
217
		this.swWrapper.style.webkitTransitionTimingFunction = 'ease-in';
218
		this.swWrapper.style.webkitTransitionDuration = '400ms';
219
		this.swWrapper.style.webkitTransform = 'translate3d(0, 0, 0)';
220
		
221
		this.swWrapper.addEventListener('webkitTransitionEnd', this, false);
222
	},
223
224
225
	/**
226
	 *
227
	 * Generic methods
228
	 *
229
	 */
230
231
	addSlot: function (values, style, defaultValue) {
232
		if (!style) {
233
			style = '';
234
		}
235
		
236
		style = style.split(' ');
237
238
		for (var i = 0; i < style.length; i += 1) {
239
			style[i] = 'sw-' + style[i];
240
		}
241
		
242
		style = style.join(' ');
243
244
		var obj = { 'values': values, 'style': style, 'defaultValue': defaultValue };
245
		this.slotData.push(obj);
246
	},
247
248
	getSelectedValues: function () {
249
		var index, count,
250
		    i, l,
251
			keys = [], values = [];
252
253
		for (i in this.slotEl) {
254
			// Remove any residual animation
255
			//this.slotEl[i].removeEventListener('webkitTransitionEnd', this, false);
256
			//this.slotEl[i].style.webkitTransitionDuration = '0';
257
258
			if (this.slotEl[i].slotYPosition > 0) {
259
				this.setPosition(i, 0);
260
			} else if (this.slotEl[i].slotYPosition < this.slotEl[i].slotMaxScroll) {
261
				this.setPosition(i, this.slotEl[i].slotMaxScroll);
262
			}
263
264
			index = -Math.round(this.slotEl[i].slotYPosition / this.cellHeight);
265
266
			count = 0;
267
			for (l in this.slotData[i].values) {
268
				if (count == index) {
269
					keys.push(l);
270
					values.push(this.slotData[i].values[l]);
271
					break;
272
				}
273
				
274
				count += 1;
275
			}
276
		}
277
278
		return { 'keys': keys, 'values': values };
279
	},
280
281
282
	/**
283
	 *
284
	 * Rolling slots
285
	 *
286
	 */
287
288
	setPosition: function (slot, pos) {
289
		this.slotEl[slot].slotYPosition = pos;
290
		this.slotEl[slot].style.webkitTransform = 'translate3d(0, ' + pos + 'px, 0)';
291
	},
292
	
293
	scrollStart: function (e) {
294
		// Find the clicked slot
295
		var xPos = e.targetTouches[0].clientX - this.swSlots.offsetLeft;	// Clicked position minus left offset (should be 11px)
296
297
		// Find tapped slot
298
		var slot = 0;
299
		for (var i = 0; i < this.slotEl.length; i += 1) {
300
			slot += this.slotEl[i].slotWidth;
301
			
302
			if (xPos < slot) {
303
				this.activeSlot = i;
304
				break;
305
			}
306
		}
307
308
		// If slot is readonly do nothing
309
		if (this.slotData[this.activeSlot].style.match('readonly')) {
310
			this.swFrame.removeEventListener('touchmove', this, false);
311
			this.swFrame.removeEventListener('touchend', this, false);
312
			return false;
313
		}
314
315
		this.slotEl[this.activeSlot].removeEventListener('webkitTransitionEnd', this, false);	// Remove transition event (if any)
316
		this.slotEl[this.activeSlot].style.webkitTransitionDuration = '0';		// Remove any residual transition
317
		
318
		// Stop and hold slot position
319
		var theTransform = window.getComputedStyle(this.slotEl[this.activeSlot]).webkitTransform;
320
		theTransform = new WebKitCSSMatrix(theTransform).m42;
321
		if (theTransform != this.slotEl[this.activeSlot].slotYPosition) {
322
			this.setPosition(this.activeSlot, theTransform);
323
		}
324
		
325
		this.startY = e.targetTouches[0].clientY;
326
		this.scrollStartY = this.slotEl[this.activeSlot].slotYPosition;
327
		this.scrollStartTime = e.timeStamp;
328
329
		this.swFrame.addEventListener('touchmove', this, false);
330
		this.swFrame.addEventListener('touchend', this, false);
331
		
332
		return true;
333
	},
334
335
	scrollMove: function (e) {
336
		var topDelta = e.targetTouches[0].clientY - this.startY;
337
338
		if (this.slotEl[this.activeSlot].slotYPosition > 0 || this.slotEl[this.activeSlot].slotYPosition < this.slotEl[this.activeSlot].slotMaxScroll) {
339
			topDelta /= 2;
340
		}
341
		
342
		this.setPosition(this.activeSlot, this.slotEl[this.activeSlot].slotYPosition + topDelta);
343
		this.startY = e.targetTouches[0].clientY;
344
345
		// Prevent slingshot effect
346
		if (e.timeStamp - this.scrollStartTime > 80) {
347
			this.scrollStartY = this.slotEl[this.activeSlot].slotYPosition;
348
			this.scrollStartTime = e.timeStamp;
349
		}
350
	},
351
	
352
	scrollEnd: function (e) {
353
		this.swFrame.removeEventListener('touchmove', this, false);
354
		this.swFrame.removeEventListener('touchend', this, false);
355
356
		// If we are outside of the boundaries, let's go back to the sheepfold
357
		if (this.slotEl[this.activeSlot].slotYPosition > 0 || this.slotEl[this.activeSlot].slotYPosition < this.slotEl[this.activeSlot].slotMaxScroll) {
358
			this.scrollTo(this.activeSlot, this.slotEl[this.activeSlot].slotYPosition > 0 ? 0 : this.slotEl[this.activeSlot].slotMaxScroll);
359
			return false;
360
		}
361
362
		// Lame formula to calculate a fake deceleration
363
		var scrollDistance = this.slotEl[this.activeSlot].slotYPosition - this.scrollStartY;
364
365
		// The drag session was too short
366
		if (scrollDistance < this.cellHeight / 1.5 && scrollDistance > -this.cellHeight / 1.5) {
367
			if (this.slotEl[this.activeSlot].slotYPosition % this.cellHeight) {
368
				this.scrollTo(this.activeSlot, Math.round(this.slotEl[this.activeSlot].slotYPosition / this.cellHeight) * this.cellHeight, '100ms');
369
			}
370
371
			return false;
372
		}
373
374
		var scrollDuration = e.timeStamp - this.scrollStartTime;
375
376
		var newDuration = (2 * scrollDistance / scrollDuration) / this.friction;
377
		var newScrollDistance = (this.friction / 2) * (newDuration * newDuration);
378
		
379
		if (newDuration < 0) {
380
			newDuration = -newDuration;
381
			newScrollDistance = -newScrollDistance;
382
		}
383
384
		var newPosition = this.slotEl[this.activeSlot].slotYPosition + newScrollDistance;
385
386
		if (newPosition > 0) {
387
			// Prevent the slot to be dragged outside the visible area (top margin)
388
			newPosition /= 2;
389
			newDuration /= 3;
390
391
			if (newPosition > this.swSlotWrapper.clientHeight / 4) {
392
				newPosition = this.swSlotWrapper.clientHeight / 4;
393
			}
394
		} else if (newPosition < this.slotEl[this.activeSlot].slotMaxScroll) {
395
			// Prevent the slot to be dragged outside the visible area (bottom margin)
396
			newPosition = (newPosition - this.slotEl[this.activeSlot].slotMaxScroll) / 2 + this.slotEl[this.activeSlot].slotMaxScroll;
397
			newDuration /= 3;
398
			
399
			if (newPosition < this.slotEl[this.activeSlot].slotMaxScroll - this.swSlotWrapper.clientHeight / 4) {
400
				newPosition = this.slotEl[this.activeSlot].slotMaxScroll - this.swSlotWrapper.clientHeight / 4;
401
			}
402
		} else {
403
			newPosition = Math.round(newPosition / this.cellHeight) * this.cellHeight;
404
		}
405
406
		this.scrollTo(this.activeSlot, Math.round(newPosition), Math.round(newDuration) + 'ms');
407
 
408
		return true;
409
	},
410
411
	scrollTo: function (slotNum, dest, runtime) {
412
		this.slotEl[slotNum].style.webkitTransitionDuration = runtime ? runtime : '100ms';
413
		this.setPosition(slotNum, dest ? dest : 0);
414
415
		// If we are outside of the boundaries go back to the sheepfold
416
		if (this.slotEl[slotNum].slotYPosition > 0 || this.slotEl[slotNum].slotYPosition < this.slotEl[slotNum].slotMaxScroll) {
417
			this.slotEl[slotNum].addEventListener('webkitTransitionEnd', this, false);
418
		}
419
	},
420
	
421
	scrollToValue: function (slot, value) {
422
		var yPos, count, i;
423
424
		this.slotEl[slot].removeEventListener('webkitTransitionEnd', this, false);
425
		this.slotEl[slot].style.webkitTransitionDuration = '0';
426
		
427
		count = 0;
428
		for (i in this.slotData[slot].values) {
429
			if (i == value) {
430
				yPos = count * this.cellHeight;
431
				this.setPosition(slot, yPos);
432
				break;
433
			}
434
			
435
			count -= 1;
436
		}
437
	},
438
	
439
	backWithinBoundaries: function (e) {
440
		e.target.removeEventListener('webkitTransitionEnd', this, false);
441
442
		this.scrollTo(e.target.slotPosition, e.target.slotYPosition > 0 ? 0 : e.target.slotMaxScroll, '150ms');
443
		return false;
444
	},
445
446
447
	/**
448
	 *
449
	 * Buttons
450
	 *
451
	 */
452
453
	tapDown: function (e) {
454
		e.currentTarget.addEventListener('touchmove', this, false);
455
		e.currentTarget.addEventListener('touchend', this, false);
456
		e.currentTarget.className = 'sw-pressed';
457
	},
458
459
	tapCancel: function (e) {
460
		e.currentTarget.removeEventListener('touchmove', this, false);
461
		e.currentTarget.removeEventListener('touchend', this, false);
462
		e.currentTarget.className = '';
463
	},
464
	
465
	tapUp: function (e) {
466
		this.tapCancel(e);
467
468
		if (e.currentTarget.id == 'sw-cancel') {
469
			this.cancelAction();
470
		} else {
471
			this.doneActionFunction(this.doneActionValues);
472
		}
473
		
474
		this.close();
475
	},
476
477
	setCancelAction: function (action) {
478
		this.cancelAction = action;
479
	},
480
481
	setDoneAction: function (action,values) {
482
		this.doneActionFunction = action;
483
		this.doneActionValues = values;
484
	},
485
	
486
	cancelAction: function () {
487
		return false;
488
	},
489
490
	cancelDone: function () {
491
		return true;
492
	}
493
};