3
* Find more about the Spinning Wheel function at
4
* http://cubiq.org/spinning-wheel-on-webkit-for-iphone-ipod-touch/11
6
* Copyright (c) 2009 Matteo Spinelli, http://cubiq.org/
7
* Released under MIT license
8
* http://cubiq.org/dropbox/mit-license.txt
10
* Version 1.4 - Last updated: 2009.07.09
26
handleEvent: function (e) {
27
if (e.type == 'touchstart') {
29
if (e.currentTarget.id == 'sw-cancel' || e.currentTarget.id == 'sw-done') {
31
} else if (e.currentTarget.id == 'sw-frame') {
34
} else if (e.type == 'touchmove') {
37
if (e.currentTarget.id == 'sw-cancel' || e.currentTarget.id == 'sw-done') {
39
} else if (e.currentTarget.id == 'sw-frame') {
42
} else if (e.type == 'touchend') {
43
if (e.currentTarget.id == 'sw-cancel' || e.currentTarget.id == 'sw-done') {
45
} else if (e.currentTarget.id == 'sw-frame') {
48
} else if (e.type == 'webkitTransitionEnd') {
49
if (e.target.id == 'sw-wrapper') {
52
this.backWithinBoundaries(e);
54
} else if (e.type == 'orientationchange') {
55
this.onOrientationChange(e);
56
} else if (e.type == 'scroll') {
68
onOrientationChange: function (e) {
69
window.scrollTo(0, 0);
70
this.swWrapper.style.top = window.innerHeight + window.pageYOffset + 'px';
71
this.calculateSlotsWidth();
74
onScroll: function (e) {
75
this.swWrapper.style.top = window.innerHeight + window.pageYOffset + 'px';
78
lockScreen: function (e) {
93
this.activeSlot = null;
95
this.swWrapper = undefined;
96
this.swSlotWrapper = undefined;
97
this.swSlots = undefined;
98
this.swFrame = undefined;
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;
108
create: function () {
109
var i, l, out, ul, div;
111
this.reset(); // Initialize object variables
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>';
120
document.body.appendChild(div);
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
127
// Create HTML slot elements
128
for (l = 0; l < this.slotData.length; l += 1) {
130
ul = document.createElement('ul');
132
for (i in this.slotData[l].values) {
133
out += '<li>' + this.slotData[l].values[i] + '<' + '/li>';
137
div = document.createElement('div'); // Create slot container
138
div.className = this.slotData[l].style; // Add styles to the container
141
// Append the slot to the wrapper
142
this.swSlots.appendChild(div);
144
ul.slotPosition = l; // Save the slot position inside the wrapper
145
ul.slotYPosition = 0;
147
ul.slotMaxScroll = this.swSlotWrapper.clientHeight - ul.clientHeight - 86;
148
ul.style.webkitTransitionTimingFunction = 'cubic-bezier(0, 0, 0.2, 1)'; // Add default transition
150
this.slotEl.push(ul); // Save the slot for later use
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);
158
this.calculateSlotsWidth();
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
166
// Cancel/Done buttons events
167
document.getElementById('sw-cancel').addEventListener('touchstart', this, false);
168
document.getElementById('sw-done').addEventListener('touchstart', this, false);
170
// Add scrolling to the slots
171
this.swFrame.addEventListener('touchstart', this, false);
177
this.swWrapper.style.webkitTransitionTimingFunction = 'ease-out';
178
this.swWrapper.style.webkitTransitionDuration = '400ms';
179
this.swWrapper.style.webkitTransform = 'translate3d(0, -260px, 0)';
189
destroy: function () {
190
this.swWrapper.removeEventListener('webkitTransitionEnd', this, false);
192
this.swFrame.removeEventListener('touchstart', this, false);
194
document.getElementById('sw-cancel').removeEventListener('touchstart', this, false);
195
document.getElementById('sw-done').removeEventListener('touchstart', this, false);
197
document.removeEventListener('touchstart', this, false);
198
document.removeEventListener('touchmove', this, false);
199
window.removeEventListener('orientationchange', this, true);
200
window.removeEventListener('scroll', this, true);
203
this.cancelAction = function () {
207
this.cancelDone = function () {
213
document.body.removeChild(document.getElementById('sw-wrapper'));
217
this.swWrapper.style.webkitTransitionTimingFunction = 'ease-in';
218
this.swWrapper.style.webkitTransitionDuration = '400ms';
219
this.swWrapper.style.webkitTransform = 'translate3d(0, 0, 0)';
221
this.swWrapper.addEventListener('webkitTransitionEnd', this, false);
231
addSlot: function (values, style, defaultValue) {
236
style = style.split(' ');
238
for (var i = 0; i < style.length; i += 1) {
239
style[i] = 'sw-' + style[i];
242
style = style.join(' ');
244
var obj = { 'values': values, 'style': style, 'defaultValue': defaultValue };
245
this.slotData.push(obj);
248
getSelectedValues: function () {
251
keys = [], values = [];
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';
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);
264
index = -Math.round(this.slotEl[i].slotYPosition / this.cellHeight);
267
for (l in this.slotData[i].values) {
268
if (count == index) {
270
values.push(this.slotData[i].values[l]);
278
return { 'keys': keys, 'values': values };
288
setPosition: function (slot, pos) {
289
this.slotEl[slot].slotYPosition = pos;
290
this.slotEl[slot].style.webkitTransform = 'translate3d(0, ' + pos + 'px, 0)';
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)
299
for (var i = 0; i < this.slotEl.length; i += 1) {
300
slot += this.slotEl[i].slotWidth;
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);
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
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);
325
this.startY = e.targetTouches[0].clientY;
326
this.scrollStartY = this.slotEl[this.activeSlot].slotYPosition;
327
this.scrollStartTime = e.timeStamp;
329
this.swFrame.addEventListener('touchmove', this, false);
330
this.swFrame.addEventListener('touchend', this, false);
335
scrollMove: function (e) {
336
var topDelta = e.targetTouches[0].clientY - this.startY;
338
if (this.slotEl[this.activeSlot].slotYPosition > 0 || this.slotEl[this.activeSlot].slotYPosition < this.slotEl[this.activeSlot].slotMaxScroll) {
342
this.setPosition(this.activeSlot, this.slotEl[this.activeSlot].slotYPosition + topDelta);
343
this.startY = e.targetTouches[0].clientY;
345
// Prevent slingshot effect
346
if (e.timeStamp - this.scrollStartTime > 80) {
347
this.scrollStartY = this.slotEl[this.activeSlot].slotYPosition;
348
this.scrollStartTime = e.timeStamp;
352
scrollEnd: function (e) {
353
this.swFrame.removeEventListener('touchmove', this, false);
354
this.swFrame.removeEventListener('touchend', this, false);
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);
362
// Lame formula to calculate a fake deceleration
363
var scrollDistance = this.slotEl[this.activeSlot].slotYPosition - this.scrollStartY;
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');
374
var scrollDuration = e.timeStamp - this.scrollStartTime;
376
var newDuration = (2 * scrollDistance / scrollDuration) / this.friction;
377
var newScrollDistance = (this.friction / 2) * (newDuration * newDuration);
379
if (newDuration < 0) {
380
newDuration = -newDuration;
381
newScrollDistance = -newScrollDistance;
384
var newPosition = this.slotEl[this.activeSlot].slotYPosition + newScrollDistance;
386
if (newPosition > 0) {
387
// Prevent the slot to be dragged outside the visible area (top margin)
391
if (newPosition > this.swSlotWrapper.clientHeight / 4) {
392
newPosition = this.swSlotWrapper.clientHeight / 4;
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;
399
if (newPosition < this.slotEl[this.activeSlot].slotMaxScroll - this.swSlotWrapper.clientHeight / 4) {
400
newPosition = this.slotEl[this.activeSlot].slotMaxScroll - this.swSlotWrapper.clientHeight / 4;
403
newPosition = Math.round(newPosition / this.cellHeight) * this.cellHeight;
406
this.scrollTo(this.activeSlot, Math.round(newPosition), Math.round(newDuration) + 'ms');
411
scrollTo: function (slotNum, dest, runtime) {
412
this.slotEl[slotNum].style.webkitTransitionDuration = runtime ? runtime : '100ms';
413
this.setPosition(slotNum, dest ? dest : 0);
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);
421
scrollToValue: function (slot, value) {
424
this.slotEl[slot].removeEventListener('webkitTransitionEnd', this, false);
425
this.slotEl[slot].style.webkitTransitionDuration = '0';
428
for (i in this.slotData[slot].values) {
430
yPos = count * this.cellHeight;
431
this.setPosition(slot, yPos);
439
backWithinBoundaries: function (e) {
440
e.target.removeEventListener('webkitTransitionEnd', this, false);
442
this.scrollTo(e.target.slotPosition, e.target.slotYPosition > 0 ? 0 : e.target.slotMaxScroll, '150ms');
453
tapDown: function (e) {
454
e.currentTarget.addEventListener('touchmove', this, false);
455
e.currentTarget.addEventListener('touchend', this, false);
456
e.currentTarget.className = 'sw-pressed';
459
tapCancel: function (e) {
460
e.currentTarget.removeEventListener('touchmove', this, false);
461
e.currentTarget.removeEventListener('touchend', this, false);
462
e.currentTarget.className = '';
465
tapUp: function (e) {
468
if (e.currentTarget.id == 'sw-cancel') {
477
setCancelAction: function (action) {
478
this.cancelAction = action;
481
setDoneAction: function (action) {
482
this.doneAction = action;
485
cancelAction: function () {
489
cancelDone: function () {
b'\\ No newline at end of file'