37
var getScroll = function () {
38
var translate3d = tabs.style.webkitTransform.match(/translate3d\(([^,]*)/);
39
return parseInt(translate3d ? translate3d[1] : 0)
42
var setTouchInProgress = function (val) {
44
//Add or remove event listeners depending on touch status
46
tabs.addEventListener(UI.touchEvents.touchMove, onTouchMove);
47
tabs.addEventListener(UI.touchEvents.touchEnd, onTouchEnd);
49
// we only have leave events on desktop, we manually calcuate
50
// leave on touch as its not supported in webkit
51
if (UI.touchEvents.touchLeave) {
52
tabs.addEventListener(UI.touchEvents.touchLeave, onTouchLeave);
55
tabs.removeEventListener(UI.touchEvents.touchMove, onTouchMove, false);
56
tabs.removeEventListener(UI.touchEvents.touchEnd, onTouchEnd, false);
58
// we only have leave events on desktop, we manually calcuate
59
// leave on touch as its not supported in webkit
60
if (UI.touchEvents.touchLeave) {
61
tabs.removeEventListener(UI.touchEvents.touchLeave, onTouchLeave, false);
66
var onTouchStart = function (e) {
68
window.clearTimeout(t1);
69
window.clearTimeout(t2);
70
isScrolling = undefined;
71
tabsWidth = tabs.offsetWidth;
80
pageX = e.touches[0].pageX;
81
pageY = e.touches[0].pageY;
83
tabs.style['-webkit-transition-duration'] = 0;
84
setTouchInProgress(true);
87
var onTouchMove = function (e) {
94
deltaX = e.touches[0].pageX - pageX;
95
deltaY = e.touches[0].pageY - pageY;
96
pageX = e.touches[0].pageX;
97
pageY = e.touches[0].pageY;
99
if (typeof isScrolling == 'undefined') {
100
isScrolling = Math.abs(deltaY) > Math.abs(deltaX);
103
if (isScrolling) return;
104
offsetX = (deltaX / resistance) + getScroll();
106
tabs.style.webkitTransform = 'translate3d(' + offsetX + 'px,0,0)';
109
var onTouchEnd = function (e) {
110
if (!tabs || isScrolling) return;
111
setTouchInProgress(false);
113
activeTab = document.querySelector('[data-role="tab"].active');
114
t1 = window.setTimeout(function() {
115
offsetX = activeTab.offsetLeft;
116
tabs.style['-webkit-transition-duration'] = '.3s';
117
tabs.style.webkitTransform = 'translate3d(-' + offsetX + 'px,0,0)';
118
[].forEach.call(document.querySelectorAll('[data-role="tab"]:not(.active)'), function (el) {
119
el.classList.toggle('inactive');
124
var onTouchLeave = function (e) {};
126
var onClicked = function (e) {
128
if ((this.className).indexOf('inactive') > -1) {
129
window.clearTimeout(t2);
130
activeTab = document.querySelector('[data-role="tab"].active');
131
offsetX = this.offsetLeft;
132
tabs.style['-webkit-transition-duration'] = '.3s';
133
tabs.style.webkitTransform = 'translate3d(-' + offsetX + 'px,0,0)';
134
activeTab.classList.remove('inactive');
135
activeTab.classList.remove('active');
136
this.classList.remove('inactive');
137
this.classList.add('active');
139
[].forEach.call(document.querySelectorAll('[data-role="tab"]:not(.active)'), function (el) {
140
el.classList.remove('inactive');
143
/*FIXME : We need to try to implement the infinite sliding
144
Array.prototype.slice.call(
145
document.querySelectorAll('ul[data-role=tabs] li:nth-child(-n+3)')
146
).map(function(element) {
147
return element.cloneNode(true);
148
}).forEach(function(element) {
149
element.classList.remove('active');
150
tabs.appendChild(element);
154
[].forEach.call(document.querySelectorAll('[data-role="tab"]:not(.active)'), function (el) {
155
el.classList.toggle('inactive');
157
t2 = window.setTimeout(function() {
158
[].forEach.call(document.querySelectorAll('[data-role="tab"]:not(.active)'), function (el) {
159
el.classList.toggle('inactive');
166
tabs.addEventListener(UI.touchEvents.touchStart, onTouchStart);
167
tabs.addEventListener(UI.touchEvents.touchMove, onTouchMove);
168
tabs.addEventListener(UI.touchEvents.touchEnd, onTouchEnd);
170
[].forEach.call(document.querySelectorAll('[data-role="tab"]'), function (el) {
171
el.addEventListener('click', onClicked, false);
b'\\ No newline at end of file'
69
transitioning_to_navigation: 1,
72
var state = STATES.basic;
74
function Tabs (tabs, touchInfoDelegate) {
75
if (tabs == null || touchInfoDelegate == null) {
81
width: this.__getTabHeadersWidth(),
82
count: this._tabs.querySelectorAll('li').length
84
this._touchInfoDelegate = touchInfoDelegate;
86
var touchEvents = touchInfoDelegate.touchEvents;
87
this._tabs.addEventListener(touchEvents.touchStart,
88
this.__onTouchStart.bind(this));
89
this._tabs.addEventListener(touchEvents.touchMove, this.__onTouchMove.bind(this));
90
this._tabs.addEventListener(touchEvents.touchEnd, this.__onTouchEnd.bind(this));
92
// we only have leave events on desktop, we manually calcuate
93
// leave on touch as its not supported in webkit
94
if (touchEvents.touchLeave) {
95
this._tabs.addEventListener(touchEvents.touchLeave, this.__onTouchLeave.bind(this));
98
// initialize default page
99
this.__setupInitialTabVisibility();
104
* Return the index of the selected tab
105
* @property selectedTabIndex
106
* @return {Integer} - The index of the element in the list of tabs or -1
108
get selectedTabIndex() {
109
return [].prototype.slice.call(this._tabs.querySelector('ul').children).indexOf(activeTab);
113
* Return the currently selected tab element
114
* @property selectedTab
115
* @return {Element} - The currently selected element or null
122
* Return the page associated with the currently selected tab
123
* @property currentPage
124
* @return {Element} - Page DOM element associated with the currently selected tab or null
127
return this.selectedTab ? this.selectedTab.querySelector('page') : null;
131
* Return the number of tab elements in the header
133
* @return {Integer} - Number of tab elements
136
return this.tabChildren.length;
140
* Return the list of DOM elements of the tab
141
* @property tabChildren
142
* @return {Elements} - List of DOM elements in the tab
145
return this._tabs.querySelectorAll('li');
151
__setupInitialTabVisibility: function() {
152
var tab = this._tabs.querySelector('[data-role="tab"]:first-child');
153
tab.classList.add('active');
154
var updateDisplayStyle = function(tab, value) {
155
var targetPage = document.getElementById(tab.getAttribute('data-page'));
157
targetPage.style.display=value;
160
call(this._tabs.querySelectorAll('[data-role="tab"]:not(:first-child)')).
161
forEach(function(element) {
162
updateDisplayStyle(element, 'none');
169
__getScroll: function () {
170
var translate3d = this._tabs.style.webkitTransform.match(/translate3d\(([^,]*)/);
171
return parseInt(translate3d ? translate3d[1] : 0)
177
__getTabHeadersWidth: function() {
178
return Array.prototype.slice.call(document.querySelectorAll('header li')).reduce(function(prev, cur) { return prev + cur.clientWidth;}, 0);
184
__getHeaderWidth: function() {
185
return this._tabs.clientWidth;
191
__clearInternalState: function() {
192
if (navigationTimer) window.clearTimeout(navigationTimer);
193
if (t2) window.clearTimeout(t2);
194
navigationTimer = undefined;
196
isScrolling = undefined;
197
tabsWidth = this._tabs.offsetWidth;
205
__onTouchStart: function (e) {
206
console.log('touchstart');
207
if (!this._tabs) return;
208
this.__clearInternalState();
210
if (state === STATES.basic) {
211
state = STATES.transitioning_to_navigation;
212
this.__showNavigation();
215
var _e = this._touchInfoDelegate.translateTouchEvent(e);
216
pageX = _e.touches[0].pageX;
217
pageY = _e.touches[0].pageY;
219
startTimestamp = _e.timeStamp;
221
this._tabs.style['-webkit-transition-duration'] = 0;
227
__onTouchMove: function (e) {
228
var _e = this._touchInfoDelegate.translateTouchEvent(e);
229
deltaX = _e.touches[0].pageX - pageX;
230
deltaY = _e.touches[0].pageY - pageY;
232
// TODO: account for DPR
233
var MIN_JITTER_THRESHOLD = 20;
235
var plusDeltaX = Math.abs(deltaX);
236
var plusDeltaY = Math.abs(deltaY);
238
// Account for clicks w/ slight touch jitter
239
isScrolling = plusDeltaY > plusDeltaX &&
240
plusDeltaY > MIN_JITTER_THRESHOLD;
241
if (isScrolling) return;
243
offsetX = (deltaX / resistance) + this.__getScroll();
245
var maxLeftReached = this._tabs.querySelector('li:first-child').getBoundingClientRect().left >= 0 &&
247
var maxRightReached = this._tabs.querySelector('li:last-child').getBoundingClientRect().right <= this.__getHeaderWidth() &&
249
if (this.__getTabHeadersWidth() > this.__getHeaderWidth() && !maxLeftReached && !maxRightReached) {
250
this._tabs.style.webkitTransform = 'translate3d(' + offsetX + 'px,0,0)';
257
__onTouchEnd: function (e) {
258
if (!this._tabs || isScrolling) return;
260
var _e = this._touchInfoDelegate.translateTouchEvent(e);
262
var MIN_JITTER_THRESHOLD = 20;
263
if (state === STATES.transitioning_to_navigation) {
264
state = STATES.navigating;
266
else if (state === STATES.navigating && Math.abs((_e.changedTouches[0].pageX - pageX)) < MIN_JITTER_THRESHOLD) {
267
this.__onClicked(_e);
268
// Timer should have been cancelled, back to basic
269
state = STATES.basic;
273
navigationTimer = window.setTimeout(function () {
274
self.__endNavigation();
275
state = STATES.basic;
282
__endNavigation: function () {
283
if (state !== STATES.navigating) return;
285
var activeTab = document.querySelector('[data-role="tab"].active');
286
if (! activeTab) return;
288
var offsetX = activeTab.offsetLeft;
289
this._tabs.style['-webkit-transition-duration'] = '.3s';
290
this._tabs.style.webkitTransform = 'translate3d(-' + offsetX + 'px,0,0)';
292
[].forEach.call(document.querySelectorAll('[data-role="tab"]:not(.active)'), function (el) {
293
el.classList.toggle('inactive');
300
__showNavigation: function () {
301
if (state !== STATES.transitioning_to_navigation) return;
303
// TODO constraint a bit the selector
304
[].forEach.call(document.querySelectorAll('[data-role="tab"]:not(.active)'), function (el) {
305
el.classList.toggle('inactive');
312
__updateActiveTab: function(newActiveTab, oldActiveTab) {
314
oldActiveTab.classList.remove('inactive');
315
oldActiveTab.classList.remove('active');
316
newActiveTab.classList.remove('inactive');
317
newActiveTab.classList.add('active');
323
__onTouchLeave: function (e) {},
328
__dispatchTabChangedEvent: function (pageId) {
329
this._evt = document.createEvent('Event');
330
this._evt.initEvent('tabchanged',true,true);
331
this._evt.infos = {pageId: pageId};
332
this._tabs.dispatchEvent(this._evt);
338
__onClicked: function (e) {
339
if (state !== STATES.navigating)
341
if (e.changedTouches.length === 0)
343
var touch = e.changedTouches[0];
344
var selectedTab = document.elementFromPoint(touch.pageX, touch.pageY);
345
if (selectedTab == null)
347
if (selectedTab.getAttribute("data-role") !== 'tab')
349
if ((selectedTab.className).indexOf('inactive') > -1) {
350
window.clearTimeout(t2);
352
activeTab = this._tabs.querySelector('[data-role="tab"].active');
353
offsetX = this.offsetLeft;
354
this._tabs.style['-webkit-transition-duration'] = '.3s';
355
this._tabs.style.webkitTransform = 'translate3d(-' + offsetX + 'px,0,0)';
357
this.__updateActiveTab(selectedTab, activeTab);
359
[].forEach.call(this._tabs.querySelectorAll('[data-role="tab"]:not(.active)'), function (e) {
360
e.classList.remove('inactive');
363
var targetPageId = selectedTab.getAttribute('data-page');
364
this.activate(targetPageId);
365
this.__dispatchTabChangedEvent(targetPageId);
368
[].forEach.call(this._tabs.querySelectorAll('[data-role="tab"]:not(.active)'), function (el) {
369
el.classList.toggle('inactive');
371
t2 = window.setTimeout(function () {
372
[].forEach.call(this._tabs.querySelectorAll('[data-role="tab"]:not(.active)'), function (el) {
373
el.classList.toggle('inactive');
383
activate: function (id) {
384
if (!id || typeof (id) !== 'string')
386
activeTab = this._tabs.querySelector('[data-page="'+ id +'"]');
390
[].forEach.call(this._tabs.querySelectorAll('[data-role="tab"]'), function (e) {
391
e.classList.remove('active');
392
e.classList.remove('inactive');
395
activeTab.classList.add('active');
397
offsetX = activeTab.offsetLeft;
398
this._tabs.style['-webkit-transition-duration'] = '.3s';
399
this._tabs.style.webkitTransform = 'translate3d(-' + offsetX + 'px,0,0)';
405
onTabChanged: function(callback){
406
this._tabs.addEventListener("tabchanged", callback);