5 ABS = new Function( 'v', 'return v<0?-v:v' );
\r
7 function Options(){};
\r
9 X_Class_override( Options.prototype, {
\r
17 lockDirection : true,
\r
18 useTransform : true,
\r
19 useTransition : true,
\r
21 checkDOMChanges : false, // Experimental
\r
27 fixedScrollbar : X_UA.Android,
\r
28 hideScrollbar : X_UA.iOS,
\r
29 fadeScrollbar : X_UA.iOS, //&& has3d,
\r
30 scrollbarClass : '',
\r
37 wheelAction : 'scroll',
\r
41 snapThreshold : 1 //,
\r
44 function Scrollbar( owner, dir ){
\r
46 this.options = owner.options;
\r
50 this.widthOrHeight = 'width';
\r
51 this.transrateXorY = 'translateX(';
\r
54 X_Class_override( Scrollbar.prototype, {
\r
59 widthOrHeight : 'height',
\r
60 transrateXorY : 'translateY(',
\r
62 xnodeWrapper : null,
\r
63 xnodeIndicator : null,
\r
69 update : function(){
\r
72 if( this.xnodeWrapper ){
\r
73 X.CSS.transform && this.xnodeIndicator.css( 'transform', '' );
\r
74 this.xnodeWrapper.css( 'display', 'none' );
\r
80 if( !this.xnodeWrapper ){
\r
81 // Create the scrollbar wrapper
\r
82 this.xnodeWrapper = this.owner.xnodeTarget.create( 'div' )
\r
84 this.options.scrollbarClass ?
\r
85 this.options.scrollbarClass + this.dir.toUpperCase() :
\r
87 ( this.vScrollbar && this.vScrollbar.active ? 'ScrollBox-Scrollbar-Wrapper-V-withH' : 'ScrollBox-Scrollbar-Wrapper-H' ) :
\r
88 ( this.hScrollbar && this.hScrollbar.active ? 'ScrollBox-Scrollbar-Wrapper-H-withV' : 'ScrollBox-Scrollbar-Wrapper-V' )
\r
91 this.options.fadeScrollbar &&
\r
92 this.xnodeWrapper.css(
\r
95 transitionProperty : 'opacity',
\r
96 transitionDuration : '350ms'
\r
100 // Create the scrollbar indicator
\r
102 this.xnodeIndicator = this.xnodeWrapper.create( 'div' );
\r
104 !this.options.scrollbarClass &&
\r
105 this.xnodeIndicator.className(
\r
107 'ScrollBox-Scrollbar-Indicator-H' :
\r
108 'ScrollBox-Scrollbar-Indicator-V'
\r
110 //if (this.options.useTransition) bar.style.cssText += ';' + cssVendor + 'transition-timing-function:cubic-bezier(0.33,0.66,0.66,1)';
\r
113 if( this.dir === 'h' ){
\r
114 this.wrapperSize = this.hScrollbarWrapper.clientWidth;
\r
115 this.indicatorSize = m.max( m.round( wrapperSize * wrapperSize / this.owner.scrollerW ), 8 );
\r
116 //this.hScrollbarIndicator.style.width = this.hScrollbarIndicatorSize + 'px';
\r
117 this.maxScroll = wrapperSize - indicatorSize;
\r
118 this.scrollPercent = maxScroll / this.owner.maxScrollX;
\r
120 this.wrapperSize = this.vScrollbarWrapper.clientHeight;
\r
121 this.indicatorSize = m.max( m.round( wrapperSize * wrapperSize / this.owner.scrollerH ), 8);
\r
122 // this.vScrollbarIndicator.style.height = indicatorSize + 'px';
\r
123 this.maxScroll = this.vScrollbarSize - indicatorSize;
\r
124 this.scrollPercent = maxScroll / this.owner.maxScrollY;
\r
126 this.xnodeIndicator.css( this.widthOrHeight, size + 'px' );
\r
128 this.updatePosition( this.owner[ this.XorY ], true );
\r
131 updatePosition : function( pos, hidden ){
\r
133 pos = this.scrollPercent * pos;
\r
136 if( !this.options.fixedScrollbar ){
\r
137 size = this.indicatorSize + m.round( pos * 3 );
\r
138 if( size < 8 ) size = 8;
\r
139 //this.xnodeIndicator.style[dir == 'h' ? 'width' : 'height'] = size + 'px';
\r
140 this.xnodeIndicator.css( this.widthOrHeight, size + 'px' );
\r
144 if( this.maxScroll < pos ){
\r
145 if( !this.options.fixedScrollbar ){
\r
146 size = this.indicatorSize - m.round( ( pos - this.maxScroll ) * 3 );
\r
147 if( size < 8 ) size = 8;
\r
148 //this.xnodeIndicator.style[dir == 'h' ? 'width' : 'height'] = size + 'px';
\r
149 this.xnodeIndicator.css( this.widthOrHeight, size + 'px' );
\r
150 pos = this.maxScroll + this.indicatorSize - size;
\r
152 pos = this.maxScroll;
\r
156 if (this.options.useTransition){
\r
157 this.xnodeWrapper.css( {
\r
158 transitionDelay : '0',
\r
159 opacity : hidden && this.options.hideScrollbar ? '0' : '1'
\r
161 //this.xnodeIndicator.style[transform] = 'translate(' + (dir == 'h' ? pos + 'px,0)' : '0,' + pos + 'px)') + translateZ;
\r
162 this.xnodeIndicator.anime( this.dir === 'h' ? { x : pos } : { y : pos } );
\r
168 function iScroll( uinodeRoot, uinodeTarget, xnodeTarget, xnodeScroller, options ){
\r
171 this.uinodeRoot = uinodeRoot;
\r
172 this.uinodeTarget = uinodeTarget;
\r
173 this.xnodeTarget = xnodeTarget;
\r
174 this.xnodeScroller = xnodeScroller;
\r
177 this.options = new Options();
\r
179 // User defined options
\r
180 if( options ) for (i in options) X_EMPTY_OBJECT[ k ] || ( this.options[i] = options[i] );
\r
182 this.options.hScroll && ( this.hScrollbar = new Scrollbar( 'h', this ) );
\r
183 this.options.vScroll && ( this.vScrollbar = new Scrollbar( 'v', this ) );
\r
185 // Set starting position
\r
186 this.x = this.options.x;
\r
187 this.y = this.options.y;
\r
189 // Normalize options
\r
190 this.options.useTransform = X.CSS.transform && this.options.useTransform;
\r
191 this.options.hScrollbar = this.options.hScroll && this.options.hScrollbar;
\r
192 this.options.vScrollbar = this.options.vScroll && this.options.vScrollbar;
\r
193 this.options.zoom = this.options.useTransform && this.options.zoom;
\r
194 this.options.useTransition = X.CSS.transition && this.options.useTransition;
\r
196 // Helpers FIX ANDROID BUG!
\r
197 // translate3d and scale doesn't work together!
\r
198 // Ignoring 3d ONLY WHEN YOU SET this.options.zoom
\r
199 //if ( this.options.zoom && X_UA.isAndroid ){
\r
200 // translateZ = '';
\r
203 // Set some default styles
\r
205 if (this.options.useTransform){
\r
206 this.scroller.style[X.CSS.transform] = 'translate(' + this.x + 'px,' + this.y + 'px)' + translateZ;
\r
207 this.scroller.style[X.CSS.transformOrigin] = '0 0';
\r
209 this.scroller.style.cssText += ';position:absolute;top:' + this.y + 'px;left:' + this.x + 'px';
\r
212 if (this.options.useTransition){
\r
213 this.scroller.style[X.CSS.transition.Property] = this.options.useTransform ? X.CSS.cssVendor + 'transform' : 'top left';
\r
214 this.scroller.style[X.CSS.transition.Duration] = '0';
\r
215 this.scroller.style[X.CSS.transition.TimingFunction] = 'cubic-bezier(0.33,0.66,0.66,1)';
\r
216 this.options.fixedScrollbar = true;
\r
221 //this._bind(RESIZE_EV, window);
\r
222 X.Dom.Event.add( window, RESIZE_EV, this );
\r
223 //this._bind(START_EV);
\r
224 uinodeTarget.listen( X.UI.Event.DRAG_START, this );
\r
228 X_Class_override( iScroll.prototype, {
\r
230 uinodeTarget : null,
\r
231 xnodeTarget : null,
\r
232 xnodeScroller : null,
\r
244 wheelZoomCount : 0,
\r
255 hScrollbar : false,
\r
256 vScrollbar : false,
\r
257 wrapperOffsetLeft : 0,
\r
258 wrapperOffsetTop : 0,
\r
271 absStartX : false, // Needed by snap threshold
\r
279 handleEvent: function (e) {
\r
281 case X.UI.Event.DRAG :
\r
282 return this._move(e);
\r
283 case X.UI.Event.WHEEL :
\r
284 return this._wheel(e);
\r
285 case X.UI.Event.DRAG_START :
\r
286 //if (!hasTouch && e.button !== 0) return;
\r
287 return this._start(e);
\r
288 case X.UI.Event.DRAG_END :
\r
289 return this._end(e);
\r
290 case X.UI.Event.ANIME_END :
\r
291 return this._transitionEnd(e);
\r
293 return this._resize();
\r
297 _trigger : function( type, e ){
\r
299 return this.uinodeTarget.dispatch( );
\r
302 _resize: function () {
\r
303 X.Timer.once( X_UA.Android ? 200 : 0, this, this.refresh );
\r
304 // setTimeout( this.refresh(), isAndroid ? 200 : 0);
\r
307 _updateScrollPosition: function( x, y, time ){
\r
308 if (this.zoomed) return;
\r
310 this.xnodeScroller.anime({
\r
311 x : this.x = this.hScrollbar && this.hScrollbar.active ? m.round(x) : 0,
\r
312 y : this.y = this.vScrollbar && this.vScrollbar.active ? m.round(y) : 0
\r
315 this.hScrollbar && this.hScrollbar.active && this.hScrollbar.updatePosition( this.x );
\r
316 this.vScrollbar && this.vScrollbar.active && this.vScrollbar.updatePosition( this.y );
\r
319 _start: function (e) {
\r
320 var point = hasTouch ? e.touches[0] : e,
\r
325 if (!this.enabled) return;
\r
327 //if (this.options.onBeforeScrollStart) this.options.onBeforeScrollStart.call(this, e);
\r
328 if( ( ret = this._trigger( X.UI.Event.SCROLL_BEFORE_START ) ) & X.Callback.PREVENT_DEFAULT ){
\r
332 //if (this.options.useTransition || this.options.zoom) this._transitionTime(0);
\r
334 this.moved = false;
\r
335 this.animating = false;
\r
336 this.zoomed = false;
\r
344 if (this.options.momentum) {
\r
345 if (this.options.useTransform) {
\r
346 // Very lame general purpose alternative to CSSMatrix
\r
347 matrix = getComputedStyle(this.scroller, null)[transform].replace(/[^0-9\-.,]/g, '').split(',');
\r
348 x = +(matrix[12] || matrix[4]);
\r
349 y = +(matrix[13] || matrix[5]);
\r
351 x = +getComputedStyle(this.scroller, null).left.replace(/[^0-9-]/g, '');
\r
352 y = +getComputedStyle(this.scroller, null).top.replace(/[^0-9-]/g, '');
\r
355 if (x !== this.x || y !== this.y) {
\r
356 if (this.options.useTransition){
\r
357 //this._unbind(TRNEND_EV);
\r
358 X.Dom.Event.remove( this.scroller, TRNEND_EV, this );
\r
360 X.Timer.cancelFrame(this.animeTimerID);
\r
362 this.steps = this.steps ? ( this.steps.length = 0 ) : [];
\r
363 this._updateScrollPosition( x, y, 0 );
\r
364 //if (this.options.onScrollEnd) this.options.onScrollEnd.call(this);
\r
365 return this._trigger( X.UI.Event.SCROLL_END, e );
\r
369 this.absStartX = this.x; // Needed by snap threshold
\r
370 this.absStartY = this.y;
\r
372 this.startX = this.x;
\r
373 this.startY = this.y;
\r
374 this.pointX = point.pageX;
\r
375 this.pointY = point.pageY;
\r
377 this.startTime = e.timeStamp || X.Timer.now();
\r
379 this.uinodeRoot.listen( X.UI.Event.DRAG, this );
\r
380 this.uinodeRoot.listen( X.UI.Event.DRAG_END, this );
\r
382 return this._trigger( X.UI.Event.SCROLL_START, e );
\r
385 _move: function (e) {
\r
386 var point = hasTouch ? e.touches[0] : e,
\r
387 deltaX = point.pageX - this.pointX,
\r
388 deltaY = point.pageY - this.pointY,
\r
389 newX = this.x + deltaX,
\r
390 newY = this.y + deltaY,
\r
392 timestamp = e.timeStamp ||X.Timer.now(), ret;
\r
394 //if (this.options.onBeforeScrollMove) this.options.onBeforeScrollMove.call(this, e);
\r
395 if( ( ret = this._trigger( X.UI.Event.SCROLL_BEFORE_MOVE ) ) & X.Callback.PREVENT_DEFAULT ){
\r
399 this.pointX = point.pageX;
\r
400 this.pointY = point.pageY;
\r
402 // Slow down if outside of the boundaries
\r
403 if (newX > 0 || newX < this.maxScrollX) {
\r
404 newX = this.options.bounce ? this.x + (deltaX / 2) : newX >= 0 || this.maxScrollX >= 0 ? 0 : this.maxScrollX;
\r
406 if (newY > this.minScrollY || newY < this.maxScrollY) {
\r
407 newY = this.options.bounce ? this.y + (deltaY / 2) : newY >= this.minScrollY || this.maxScrollY >= 0 ? this.minScrollY : this.maxScrollY;
\r
410 this.distX += deltaX;
\r
411 this.distY += deltaY;
\r
412 this.absDistX = ABS(this.distX);
\r
413 this.absDistY = ABS(this.distY);
\r
415 if (this.absDistX < 6 && this.absDistY < 6) {
\r
420 if (this.options.lockDirection) {
\r
421 if (this.absDistX > this.absDistY + 5) {
\r
424 } else if (this.absDistY > this.absDistX + 5) {
\r
431 this._updateScrollPosition(newX, newY, 0);
\r
432 this.dirX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0;
\r
433 this.dirY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0;
\r
435 if (timestamp - this.startTime > 300) {
\r
436 this.startTime = timestamp;
\r
437 this.startX = this.x;
\r
438 this.startY = this.y;
\r
441 //if (this.options.onScrollMove) this.options.onScrollMove.call(this, e);
\r
442 return this._trigger( X.UI.Event.SCROLL_MOVE, e );
\r
445 _end: function (e) {
\r
446 if (hasTouch && e.touches.length !== 0) return;
\r
448 var point = hasTouch ? e.changedTouches[0] : e,
\r
449 momentumX = { dist:0, time:0 },
\r
450 momentumY = { dist:0, time:0 },
\r
451 duration = ( e.timeStamp ||X.Timer.now() ) - this.startTime,
\r
459 this.uinodeRoot.unlisten( X.UI.Event.DRAG, this );
\r
460 this.uinodeRoot.unlisten( X.UI.Event.DRAG_END, this );
\r
462 //this._unbind(MOVE_EV, window);
\r
463 //this._unbind(END_EV, window);
\r
464 //this._unbind(CANCEL_EV, window);
\r
466 //if (this.options.onBeforeScrollEnd) this.options.onBeforeScrollEnd.call(this, e);
\r
472 this._resetPos(400);
\r
474 //if (this.options.onTouchEnd) this.options.onTouchEnd.call(this, e);
\r
478 if (duration < 300 && this.options.momentum) {
\r
479 momentumX = newPosX ? this._momentum(newPosX - this.startX, duration, -this.x, this.scrollerW - this.wrapperW + this.x, this.options.bounce ? this.wrapperW : 0) : momentumX;
\r
480 momentumY = newPosY ? this._momentum(newPosY - this.startY, duration, -this.y, (this.maxScrollY < 0 ? this.scrollerH - this.wrapperH + this.y - this.minScrollY : 0), this.options.bounce ? this.wrapperH : 0) : momentumY;
\r
482 newPosX = this.x + momentumX.dist;
\r
483 newPosY = this.y + momentumY.dist;
\r
485 if ((this.x > 0 && newPosX > 0) || (this.x < this.maxScrollX && newPosX < this.maxScrollX)) momentumX = { dist:0, time:0 };
\r
486 if ((this.y > this.minScrollY && newPosY > this.minScrollY) || (this.y < this.maxScrollY && newPosY < this.maxScrollY)) momentumY = { dist:0, time:0 };
\r
488 if (momentumX.dist || momentumY.dist) {
\r
489 newDuration = m.max(m.max(momentumX.time, momentumY.time), 10);
\r
491 this.scrollTo(m.round(newPosX), m.round(newPosY), newDuration);
\r
493 //if (this.options.onTouchEnd) this.options.onTouchEnd.call(this, e);
\r
498 this._resetPos(200);
\r
499 //if (this.options.onTouchEnd) this.options.onTouchEnd.call(this, e);
\r
503 _onZoomEndEvent : null,
\r
504 _onZoomEndTimerComplete : function(){
\r
505 this.options.onZoomEnd.call( this, this._onZoomEndEvent );
\r
510 _onDobleTapTimerPoint : null,
\r
511 _onDobleTapTimerComplete : function () {
\r
512 var point = this._onDobleTapTimerPoint,
\r
514 this.doubleTapTimer = null;
\r
516 // Find the last touched element
\r
517 target = point.target;
\r
518 while (target.nodeType !== 1) target = target.parentNode;
\r
520 if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') {
\r
521 ev = document.createEvent('MouseEvents');
\r
522 ev.initMouseEvent('click', true, true, e.view, 1,
\r
523 point.screenX, point.screenY, point.clientX, point.clientY,
\r
524 e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
\r
527 target.dispatchEvent(ev);
\r
531 _resetPos: function (time) {
\r
535 this.x < this.maxScrollX ?
\r
539 this.minScrollY <= this.y || 0 < this.maxScrollY ?
\r
541 this.y < this.maxScrollY ?
\r
545 if( resetX === this.x && resetY === this.y ){
\r
547 this.moved = false;
\r
548 this._trigger( X.UI.Event.SCROLL_END );
\r
549 //if (this.options.onScrollEnd) this.options.onScrollEnd.call(this); // Execute custom code on scroll end
\r
551 if( this.options.hideScrollbar ){
\r
552 this.hScrollbar && this.hScrollbar.active &&
\r
554 //if (vendor == 'webkit') this.hScrollbarWrapper.style[transitionDelay] = '300ms';
\r
555 //this.hScrollbarWrapper.style.opacity = '0';
\r
556 this.hScrollbar.xnodeWraper.anime( {
\r
559 this.vScrollbar && this.vScrollbar.active &&
\r
560 //if (vendor == 'webkit') this.vScrollbarWrapper.style[transitionDelay] = '300ms';
\r
561 //this.vScrollbarWrapper.style.opacity = '0';
\r
562 this.hScrollbar.xnodeWraper.anime( {
\r
568 this.scrollTo(resetX, resetY, time || 0);
\r
571 _wheel: function (e) {
\r
572 var wheelDeltaX, wheelDeltaY,
\r
576 if ('wheelDeltaX' in e) {
\r
577 wheelDeltaX = e.wheelDeltaX / 12;
\r
578 wheelDeltaY = e.wheelDeltaY / 12;
\r
579 } else if('wheelDelta' in e) {
\r
580 wheelDeltaX = wheelDeltaY = e.wheelDelta / 12;
\r
581 } else if ('detail' in e) {
\r
582 wheelDeltaX = wheelDeltaY = -e.detail * 3;
\r
587 deltaX = this.x + e.wheelDeltaX;
\r
588 deltaY = this.y + e.wheelDeltaY;
\r
593 deltaX < this.maxScrollX ?
\r
594 this.maxScrollX : deltaX;
\r
597 this.minScrollY < deltaY ?
\r
599 deltaY < this.maxScrollY ?
\r
600 this.maxScrollY : deltaY;
\r
602 this.maxScrollY < 0 && this.scrollTo(deltaX, deltaY, 0);
\r
606 _wheelTimerCompleteEvent : null,
\r
607 _wheelTimerComplete : function() {
\r
608 this.wheelZoomCount--;
\r
609 if (!this.wheelZoomCount && this.options.onZoomEnd) this.options.onZoomEnd.call(this, this._wheelTimerCompleteEvent );
\r
613 _transitionEnd: function( e ){
\r
614 if( e.target !== this.xnodeScroller ) return;
\r
616 //this._unbind(TRNEND_EV);
\r
617 //X.Dom.Event.remove( this.scroller, TRNEND_EV, this );
\r
618 this.animating = false;
\r
620 this._startAnime();
\r
622 return X.Callback.UN_LISTEN;
\r
631 _startAnime: function () {
\r
632 var startX = this.x,
\r
636 if (this.animating) return;
\r
638 if (!this.steps.length) {
\r
639 this._resetPos(400);
\r
643 step = this.steps.shift();
\r
645 if( step.x === startX && step.y === startY ) step.time = 0;
\r
647 this.animating = true;
\r
650 //if (this.options.useTransition) {
\r
651 //this._transitionTime(step.time);
\r
652 this._updateScrollPosition( step.x, step.y, step.time );
\r
653 //this.animating = false;
\r
654 this.xnodeScroller.listenOnce( X.UI.Event.ANIME_END, this );
\r
655 //step.time ? X.Dom.Event.add( this.scroller, TRNEND_EV, this ) /* this._bind(TRNEND_EV) */ : this._resetPos(0);
\r
658 //this._doAnimate( X.Timer.now(), step, startX, startY );
\r
662 _doAnimate : function( startTime, step, startX, startY ){
\r
663 var now =X.Timer.now(),
\r
664 easeOut, newX, newY;
\r
666 if (now >= startTime + step.time) {
\r
667 this._updateScrollPosition(step.x, step.y);
\r
668 this.animating = false;
\r
669 //if (this.options.onAnimationEnd) this.options.onAnimationEnd.call( this ); // Execute custom code on animation end
\r
670 this._startAnime();
\r
674 now = (now - startTime) / step.time - 1;
\r
675 easeOut = m.sqrt(1 - now * now);
\r
676 newX = (step.x - startX) * easeOut + startX;
\r
677 newY = (step.y - startY) * easeOut + startY;
\r
678 this._updateScrollPosition(newX, newY);
\r
679 if( this.animating ) this.animeTimerID = X.Timer.requestFrame( this, this._doAnimate, [ startTime, step, startX, startY ] );
\r
683 _momentum: function (dist, time, maxDistUpper, maxDistLower, size) {
\r
684 var deceleration = 0.0006,
\r
685 speed = ABS(dist) / time,
\r
686 newDist = (speed * speed) / (2 * deceleration),
\r
687 newTime = 0, outsideDist = 0;
\r
689 // Proportinally reduce speed if we are outside of the boundaries
\r
690 if (dist > 0 && newDist > maxDistUpper) {
\r
691 outsideDist = size / (6 / (newDist / speed * deceleration));
\r
692 maxDistUpper = maxDistUpper + outsideDist;
\r
693 speed = speed * maxDistUpper / newDist;
\r
694 newDist = maxDistUpper;
\r
695 } else if (dist < 0 && newDist > maxDistLower) {
\r
696 outsideDist = size / (6 / (newDist / speed * deceleration));
\r
697 maxDistLower = maxDistLower + outsideDist;
\r
698 speed = speed * maxDistLower / newDist;
\r
699 newDist = maxDistLower;
\r
702 newDist = newDist * (dist < 0 ? -1 : 1);
\r
703 newTime = speed / deceleration;
\r
705 return { dist: newDist, time: m.round(newTime) };
\r
708 _offset: function (el) {
\r
709 var left = -el.offsetLeft,
\r
710 top = -el.offsetTop;
\r
712 while (el = el.offsetParent) {
\r
713 left -= el.offsetLeft;
\r
714 top -= el.offsetTop;
\r
717 if (el != this.wrapper) {
\r
718 left *= this.scale;
\r
722 return { left: left, top: top };
\r
726 _bind: function (type, el, bubble) {
\r
727 X.Dom.Event.add( el || this.scroller, type, this );
\r
730 _unbind: function (type, el, bubble) {
\r
731 X.Dom.Event.remove( el || this.scroller, type, this );
\r
740 destroy: function () {
\r
741 this.scroller.style[transform] = '';
\r
743 // Remove the scrollbars
\r
744 this.hScrollbar && this.hScrollbar.destroy();
\r
745 this.vScrollbar && this.vScrollbar.destroy();
\r
747 // Remove the event listeners
\r
748 X.Dom.Event.add( window, RESIZE_EV, this );
\r
749 this.uinodeTarget.unlisten( X.UI.Event.DRAG_START, this );
\r
750 this.uinodeRoot.unlisten( X.UI.Event.DRAG, this );
\r
751 this.uinodeRoot.unlisten( X.UI.Event.DRAG_END, this );
\r
753 //this._unbind(RESIZE_EV, window);
\r
754 //this._unbind(START_EV);
\r
755 //this._unbind(MOVE_EV, window);
\r
756 //this._unbind(END_EV, window);
\r
757 //this._unbind(CANCEL_EV, window);
\r
759 if (!this.options.hasTouch) {
\r
760 //this._unbind('wheel');
\r
763 // if (this.options.useTransition) this._unbind(TRNEND_EV);
\r
764 this.options.useTransition && X.Dom.Event.remove( this.scroller, TRNEND_EV, this );
\r
766 //if (this.options.checkDOMChanges) clearInterval(this.checkDOMTime);
\r
768 //if (this.options.onDestroy) this.options.onDestroy.call(this);
\r
771 refresh: function () {
\r
778 if (this.scale < this.options.zoomMin) this.scale = this.options.zoomMin;
\r
779 this.wrapperW = this.wrapper.clientWidth || 1;
\r
780 this.wrapperH = this.wrapper.clientHeight || 1;
\r
782 this.minScrollY = -this.options.topOffset || 0;
\r
783 this.scrollerW = m.round(this.scroller.offsetWidth * this.scale);
\r
784 this.scrollerH = m.round((this.scroller.offsetHeight + this.minScrollY) * this.scale);
\r
785 this.maxScrollX = this.wrapperW - this.scrollerW;
\r
786 this.maxScrollY = this.wrapperH - this.scrollerH + this.minScrollY;
\r
790 // if (this.options.onRefresh) this.options.onRefresh.call(this);
\r
791 this._trigger( X.UI.Event.SCROLL_REFRESH, {} );
\r
793 this.hScrollbar && ( this.hScrollbar.active = this.maxScrollX < 0 );
\r
794 this.vScrollbar && ( this.vScrollbar.active = !this.options.bounceLock && !this.hScroll || this.scrollerH > this.wrapperH );
\r
796 offset = this._offset(this.wrapper);
\r
797 this.wrapperOffsetLeft = -offset.left;
\r
798 this.wrapperOffsetTop = -offset.top;
\r
801 if (typeof this.options.snap == 'string') {
\r
804 els = this.scroller.querySelectorAll(this.options.snap);
\r
805 for (i=0, l=els.length; i<l; i++) {
\r
806 pos = this._offset(els[i]);
\r
807 pos.left += this.wrapperOffsetLeft;
\r
808 pos.top += this.wrapperOffsetTop;
\r
809 this.pagesX[i] = pos.left < this.maxScrollX ? this.maxScrollX : pos.left * this.scale;
\r
810 this.pagesY[i] = pos.top < this.maxScrollY ? this.maxScrollY : pos.top * this.scale;
\r
812 } else if (this.options.snap) {
\r
814 while (pos >= this.maxScrollX) {
\r
815 this.pagesX[page] = pos;
\r
816 pos = pos - this.wrapperW;
\r
819 if (this.maxScrollX%this.wrapperW) this.pagesX[this.pagesX.length] = this.maxScrollX - this.pagesX[this.pagesX.length-1] + this.pagesX[this.pagesX.length-1];
\r
824 while (pos >= this.maxScrollY) {
\r
825 this.pagesY[page] = pos;
\r
826 pos = pos - this.wrapperH;
\r
829 if (this.maxScrollY%this.wrapperH) this.pagesY[this.pagesY.length] = this.maxScrollY - this.pagesY[this.pagesY.length-1] + this.pagesY[this.pagesY.length-1];
\r
832 // Prepare the scrollbars
\r
833 this._scrollbar('h');
\r
834 this._scrollbar('v');
\r
836 if (!this.zoomed) {
\r
837 this.scroller.style[transitionDuration] = '0';
\r
838 this._resetPos(400);
\r
842 scrollTo: function (x, y, time, relative) {
\r
848 if( !step.length ) step = [{ x: x, y: y, time: time, relative: relative }];
\r
850 for( i = 0, l = step.length; i < l; ++i ){
\r
851 if( step[ i ].relative ){
\r
852 step[ i ].x = this.x - step[ i ].x;
\r
853 step[ i ].y = this.y - step[ i ].y;
\r
858 time : step[i].time || 0
\r
862 this._startAnime();
\r
865 disable: function () {
\r
868 this.enabled = false;
\r
870 // If disabled after touchstart we make sure that there are no left over events
\r
871 //this._unbind(MOVE_EV, window);
\r
872 //this._unbind(END_EV, window);
\r
873 //this._unbind(CANCEL_EV, window);
\r
874 this.uinodeRoot.unlisten( X.UI.Event.DRAG, this );
\r
875 this.uinodeRoot.unlisten( X.UI.Event.DRAG_END, this );
\r
878 enable: function () {
\r
879 this.enabled = true;
\r
882 stop: function () {
\r
883 //if (this.options.useTransition) this._unbind(TRNEND_EV);
\r
884 //else X.Timer.cancelFrame( this.animeTimerID );
\r
886 if (this.options.useTransition){
\r
887 X.Dom.Event.remove( this.scroller, TRNEND_EV, this );
\r
889 X.Timer.cancelFrame(this.animeTimerID);
\r
892 this.xnodeScroller.stop();
\r
893 if( this.steps ) this.steps.length = 0;
\r
894 this.moved = false;
\r
895 this.animating = false;
\r
898 isReady: function () {
\r
899 return !this.moved && !this.zoomed && !this.animating;
\r
905 X.UI._ScrollBox = X.UI._ChromeBox.inherits(
\r
907 X.Class.PRIVATE_DATA | X.Class.SUPER_ACCESS,
\r
909 //elmScroll : null,
\r
910 //elmScroller : null,
\r
911 //elmScrollbar : null,
\r
916 scrollXPercent : 0,
\r
917 scrollYPercent : 0,
\r
919 _containerNode : null,
\r
920 scrollManager : null,
\r
922 Constructor : function( layout, args ){
\r
923 this.Super( layout, args );
\r
924 this._containerNode = _X_Class_getPrivate( this.containerNode );
\r
927 creationComplete : function(){
\r
928 X.UI._AbstractUINode.prototype.creationComplete.call( this, arguments );
\r
929 this.scrollManager = new iScroll( this.root, this.User, this.rawElement, this._containerNode.rawElement );
\r
933 calculate : function(){
\r
934 X.UI._AbstractUINode.prototype.calculate.call( this, arguments );
\r
938 _check : function(){
\r
939 if( this.w < this._containerNode.w || this.h < this._containerNode.h ){
\r
941 if( this.scrolling ){
\r
942 // fix scroll position from scrollXPercent, scrollYPercent
\r
946 this.listen( X.UI.Event.POINTER_START, this );
\r
949 this._move( 0, 0 );
\r
951 this.scrolling = true;
\r
955 if( this.scrolling ){
\r
958 ( this._scrollX !== 0 || this._scrollY !== 0 ) && this._move( 0, 0 );
\r
960 delete this.scrolling;
\r
964 handleEvent : function( e ){
\r
966 case X.UI.Event.POINTER_START :
\r
967 this.listen( X.UI.Event.POINTER_MOVE, this );
\r
968 this.listen( X.UI.Event.POINTER_END, this );
\r
971 case X.UI.Event.POINTER_MOVE :
\r
974 case X.UI.Event.POINTER_END :
\r
975 this.unlisten( X.UI.Event.POINTER_MOVE, this );
\r
976 this.unlisten( X.UI.Event.POINTER_END, this );
\r
982 _move : function( x, y ){
\r
986 _remove : function(){
\r
987 X.UI._AbstractUINode.prototype._remove.call( this, arguments );
\r
988 if( this.scrolling ){
\r
998 X.UI.ScrollBox = X.UI.ChromeBox.inherits(
\r
1000 X.Class.SUPER_ACCESS,
\r
1003 Constructor : function(){
\r
1005 i = arguments.length,
\r
1009 arg = arguments[ --i ];
\r
1010 if( arg.instanceOf && arg.instanceOf( X.UI.Layout.Base ) ){
\r
1013 args[ args.length ] = arg;
\r
1017 this.style = DisplayNodeStyle( this,
\r
1018 X_Class_newPrivate(
\r
1020 X.UI.Layout.Canvas,
\r
1023 layout || VerticalLayoutManager,
\r
1025 name : 'ScrollBox-Scroller',
\r
1026 role : 'container'
\r
1033 this.style.addName( 'ScrollBox' ); */
\r
1035 scrollX : function(){
\r
1038 scrollY : function(){
\r
1041 scrollWidth : function(){
\r
1044 scrollHeight : function(){
\r
1047 scrollTo : function( nodeOrX, y ){
\r