Export 0.2.3
[platform/framework/web/web-ui-fw.git] / src / widgets / common / js / jquery.mobile.tizen.scrollview.js
1 /*
2 * jQuery Mobile Framework : scrollview plugin
3 * Copyright (c) 2010 Adobe Systems Incorporated - Kin Blas (jblas@adobe.com)
4 * Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
5 * Note: Code is in draft form and is subject to change
6 * Modified by Koeun Choi <koeun.choi@samsung.com>
7 * Modified by Minkyu Kang <mk7.kang@samsung.com>
8 */
9
10 (function ( $, window, document, undefined ) {
11
12         function resizePageContentHeight( page ) {
13                 var $page = $( page ),
14                         $content = $page.children(".ui-content"),
15                         hh = $page.children(".ui-header").outerHeight() || 0,
16                         fh = $page.children(".ui-footer").outerHeight() || 0,
17                         pt = parseFloat( $content.css("padding-top") ),
18                         pb = parseFloat( $content.css("padding-bottom") ),
19                         wh = $( window ).height();
20
21                 $content.height( wh - (hh + fh) - (pt + pb) );
22         }
23
24         function MomentumTracker( options ) {
25                 this.options = $.extend( {}, options );
26                 this.easing = "easeOutQuad";
27                 this.reset();
28         }
29
30         var tstates = {
31                 scrolling: 0,
32                 overshot:  1,
33                 snapback:  2,
34                 done:      3
35         };
36
37         function getCurrentTime() {
38                 return Date.now();
39         }
40
41         jQuery.widget( "tizen.scrollview", jQuery.mobile.widget, {
42                 options: {
43                         direction:         null,  // "x", "y", or null for both.
44
45                         timerInterval:     10,
46                         scrollDuration:    1000,  // Duration of the scrolling animation in msecs.
47                         overshootDuration: 250,   // Duration of the overshoot animation in msecs.
48                         snapbackDuration:  500,   // Duration of the snapback animation in msecs.
49
50                         moveThreshold:     30,   // User must move this many pixels in any direction to trigger a scroll.
51                         moveIntervalThreshold:     150,   // Time between mousemoves must not exceed this threshold.
52
53                         scrollMethod:      "translate",  // "translate", "position"
54                         startEventName:    "scrollstart",
55                         updateEventName:   "scrollupdate",
56                         stopEventName:     "scrollstop",
57
58                         eventType:         $.support.touch ? "touch" : "mouse",
59
60                         showScrollBars:    true,
61                         overshootEnable:   false,
62                         outerScrollEnable: true,
63                         overflowEnable:    true,
64                         scrollJump:        false,
65                 },
66
67                 _getViewHeight: function () {
68                         return this._$view.height() + this._view_offset;
69                 },
70
71                 _makePositioned: function ( $ele ) {
72                         if ( $ele.css("position") === "static" ) {
73                                 $ele.css( "position", "relative" );
74                         }
75                 },
76
77                 _create: function () {
78                         var direction,
79                                 self = this;
80
81                         this._$clip = $( this.element ).addClass("ui-scrollview-clip");
82
83                         if ( this._$clip.children(".ui-scrollview-view").length ) {
84                                 this._$view = this._$clip.children(".ui-scrollview-view");
85                         } else {
86                                 this._$view = this._$clip.wrapInner("<div></div>").children()
87                                                         .addClass("ui-scrollview-view");
88                         }
89
90                         if ( this.options.scrollMethod === "translate" ) {
91                                 if ( this._$view.css("transform") === undefined ) {
92                                         this.options.scrollMethod = "position";
93                                 }
94                         }
95
96                         this._$clip.css( "overflow", "hidden" );
97                         this._makePositioned( this._$clip );
98
99                         this._makePositioned( this._$view );
100                         this._$view.css( { left: 0, top: 0 } );
101
102                         this._view_offset = this._$view.offset().top - this._$clip.offset().top;
103                         this._view_height = this._getViewHeight();
104
105                         this._sx = 0;
106                         this._sy = 0;
107
108                         direction = this.options.direction;
109
110                         this._hTracker = ( direction !== "y" ) ?
111                                         new MomentumTracker( this.options ) : null;
112                         this._vTracker = ( direction !== "x" ) ?
113                                         new MomentumTracker( this.options ) : null;
114
115                         this._timerInterval = this.options.timerInterval;
116                         this._timerID = 0;
117
118                         this._timerCB = function () {
119                                 self._handleMomentumScroll();
120                         };
121
122                         this._add_event();
123                         this._add_scrollbar();
124                         this._add_scroll_jump();
125                         this._add_overflow_indicator();
126                 },
127
128                 _startMScroll: function ( speedX, speedY ) {
129                         var keepGoing = false,
130                                 duration = this.options.scrollDuration,
131                                 ht = this._hTracker,
132                                 vt = this._vTracker,
133                                 c,
134                                 v;
135
136                         this._stopMScroll();
137                         this._showScrollBars();
138                         this._showOverflowIndicator();
139
140                         this._$clip.trigger( this.options.startEventName );
141
142                         if ( ht ) {
143                                 c = this._$clip.width();
144                                 v = this._$view.width();
145
146                                 if ( (( this._sx === 0 && speedX > 0 ) ||
147                                         ( this._sx === -(v - c) && speedX < 0 )) &&
148                                                 v > c ) {
149                                         return;
150                                 }
151
152                                 ht.start( this._sx, speedX,
153                                         duration, (v > c) ? -(v - c) : 0, 0 );
154                                 keepGoing = !ht.done();
155                         }
156
157                         if ( vt ) {
158                                 c = this._$clip.height();
159                                 v = this._getViewHeight();
160
161                                 if ( (( this._sy === 0 && speedY > 0 ) ||
162                                         ( this._sy === -(v - c) && speedY < 0 )) &&
163                                                 v > c ) {
164                                         return;
165                                 }
166
167                                 vt.start( this._sy, speedY,
168                                         duration, (v > c) ? -(v - c) : 0, 0 );
169                                 keepGoing = keepGoing || !vt.done();
170                         }
171
172                         if ( keepGoing ) {
173                                 this._timerID = setTimeout( this._timerCB, this._timerInterval );
174                         } else {
175                                 this._stopMScroll();
176                         }
177                 },
178
179                 _stopMScroll: function () {
180                         if ( this._timerID ) {
181                                 this._$clip.trigger( this.options.stopEventName );
182                                 clearTimeout( this._timerID );
183                         }
184                         this._timerID = 0;
185
186                         if ( this._vTracker ) {
187                                 this._vTracker.reset();
188                         }
189
190                         if ( this._hTracker ) {
191                                 this._hTracker.reset();
192                         }
193
194                         this._hideScrollBars();
195                         this._hideOverflowIndicator();
196                 },
197
198                 _handleMomentumScroll: function () {
199                         var keepGoing = false,
200                                 x = 0,
201                                 y = 0,
202                                 scroll_height = 0,
203                                 self = this,
204                                 end_effect = function ( dir ) {
205                                         setTimeout( function () {
206                                                 self._effect_dir = dir;
207                                                 self._setEndEffect( "in" );
208                                         }, 100 );
209
210                                         setTimeout( function () {
211                                                 self._setEndEffect( "out" );
212                                         }, 350 );
213                                 },
214                                 vt = this._vTracker,
215                                 ht = this._hTracker;
216
217                         if ( this._outerScrolling ) {
218                                 return;
219                         }
220
221                         if ( vt ) {
222                                 vt.update( this.options.overshootEnable );
223                                 y = vt.getPosition();
224                                 keepGoing = !vt.done();
225
226                                 if ( vt.getRemained() > this.options.overshootDuration ) {
227                                         scroll_height = this._getViewHeight() - this._$clip.height();
228
229                                         if ( !vt.isAvail() ) {
230                                                 if ( this._speedY > 0 ) {
231                                                         this._outerScroll( vt.getRemained() / 3, scroll_height );
232                                                 } else {
233                                                         this._outerScroll( y - vt.getRemained() / 3, scroll_height );
234                                                 }
235                                         } else if ( vt.isMin() ) {
236                                                 this._outerScroll( y - vt.getRemained() / 3, scroll_height );
237
238                                                 if ( scroll_height > 0 ) {
239                                                         end_effect( 1 );
240                                                 }
241                                         } else if ( vt.isMax() ) {
242                                                 this._outerScroll( vt.getRemained() / 3, scroll_height );
243
244                                                 if ( scroll_height > 0 ) {
245                                                         end_effect( 0 );
246                                                 }
247                                         }
248                                 }
249                         }
250
251                         if ( ht ) {
252                                 ht.update( this.options.overshootEnable );
253                                 x = ht.getPosition();
254                                 keepGoing = keepGoing || !ht.done();
255                         }
256
257                         this._setScrollPosition( x, y );
258                         this._$clip.trigger( this.options.updateEventName,
259                                         [ { x: x, y: y } ] );
260
261                         if ( keepGoing ) {
262                                 this._timerID = setTimeout( this._timerCB, this._timerInterval );
263                         } else {
264                                 this._stopMScroll();
265                         }
266                 },
267
268                 _setElementTransform: function ( $ele, x, y, duration ) {
269                         var translate,
270                                 transition;
271
272                         if ( this._endEffect ) {
273                                 return;
274                         }
275
276                         if ( !duration || duration === undefined ) {
277                                 transition = "none";
278                         } else {
279                                 transition =  "-webkit-transform " + duration / 1000 + "s ease-out";
280                         }
281
282                         if ( $.support.cssTransform3d ) {
283                                 translate = "translate3d(" + x + "," + y + ", 0px)";
284                         } else {
285                                 translate = "translate(" + x + "," + y + ")";
286                         }
287
288                         $ele.css({
289                                 "-moz-transform": translate,
290                                 "-webkit-transform": translate,
291                                 "-ms-transform": translate,
292                                 "-o-transform": translate,
293                                 "transform": translate,
294                                 "-webkit-transition": transition
295                         });
296                 },
297
298                 _setEndEffect: function ( dir ) {
299                         var scroll_height = this._getViewHeight() - this._$clip.height();
300
301                         if ( this._softkeyboard ) {
302                                 if ( this._effect_dir ) {
303                                         this._outerScroll( -scroll_height - this._softkeyboardHeight,
304                                                         scroll_height );
305                                 } else {
306                                         this._outerScroll( this._softkeyboardHeight, scroll_height );
307                                 }
308                                 return;
309                         }
310
311                         if ( dir === "in" ) {
312                                 if ( this._endEffect ) {
313                                         return;
314                                 }
315
316                                 this._endEffect = true;
317                                 this._setOverflowIndicator( this._effect_dir );
318                         } else if ( dir === "out" ) {
319                                 if ( !this._endEffect ) {
320                                         return;
321                                 }
322
323                                 this._endEffect = false;
324                                 this._setOverflowIndicator( this._effect_dir );
325                         }
326                 },
327
328                 _setCalibration: function ( x, y ) {
329                         if ( this.options.overshootEnable ) {
330                                 this._sx = x;
331                                 this._sy = y;
332                                 return;
333                         }
334
335                         var $v = this._$view,
336                                 $c = this._$clip,
337                                 dirLock = this._directionLock,
338                                 scroll_height = 0,
339                                 scroll_width = 0;
340
341                         if ( dirLock !== "y" && this._hTracker ) {
342                                 scroll_width = $v.width() - $c.width();
343
344                                 if ( x >= 0 ) {
345                                         this._sx = 0;
346                                 } else if ( x < -scroll_width ) {
347                                         this._sx = -scroll_width;
348                                 } else {
349                                         this._sx = x;
350                                 }
351
352                                 if ( scroll_width < 0 ) {
353                                         this._sx = 0;
354                                 }
355                         }
356
357                         if ( dirLock !== "x" && this._vTracker ) {
358                                 scroll_height = this._getViewHeight() - $c.height();
359
360                                 if ( y > 0 ) {
361                                         this._sy = 0;
362
363                                         if ( this._didDrag && scroll_height > 0 ) {
364                                                 this._effect_dir = 0;
365                                                 this._setEndEffect( "in" );
366                                         }
367                                 } else if ( y < -scroll_height ) {
368                                         this._sy = -scroll_height;
369
370                                         if ( this._didDrag && scroll_height > 0 ) {
371                                                 this._effect_dir = 1;
372                                                 this._setEndEffect( "in" );
373                                         }
374                                 } else {
375                                         if ( this._endEffect && this._sy !== y ) {
376                                                 this._endEffect = false;
377                                                 this._setOverflowIndicator();
378                                         }
379
380                                         this._sy = y;
381                                 }
382
383                                 if ( scroll_height < 0 ) {
384                                         this._sy = 0;
385                                 }
386                         }
387                 },
388
389                 _setScrollPosition: function ( x, y, duration ) {
390                         var $v = this._$view,
391                                 sm = this.options.scrollMethod,
392                                 $vsb = this._$vScrollBar,
393                                 $hsb = this._$hScrollBar,
394                                 $sbt;
395
396                         this._setCalibration( x, y );
397
398                         x = this._sx;
399                         y = this._sy;
400
401                         if ( sm === "translate" ) {
402                                 this._setElementTransform( $v, x + "px", y + "px", duration );
403                         } else {
404                                 $v.css( {left: x + "px", top: y + "px"} );
405                         }
406
407                         if ( $vsb ) {
408                                 $sbt = $vsb.find(".ui-scrollbar-thumb");
409
410                                 if ( sm === "translate" ) {
411                                         this._setElementTransform( $sbt, "0px",
412                                                 -y / this._getViewHeight() * $sbt.parent().height() + "px",
413                                                 duration );
414                                 } else {
415                                         $sbt.css( "top", -y / this._getViewHeight() * 100 + "%" );
416                                 }
417                         }
418
419                         if ( $hsb ) {
420                                 $sbt = $hsb.find(".ui-scrollbar-thumb");
421
422                                 if ( sm === "translate" ) {
423                                         this._setElementTransform( $sbt,
424                                                 -x / $v.width() * $sbt.parent().width() + "px", "0px",
425                                                 duration);
426                                 } else {
427                                         $sbt.css("left", -x / $v.width() * 100 + "%");
428                                 }
429                         }
430                 },
431
432                 _outerScroll: function ( y, scroll_height ) {
433                         var self = this,
434                                 top = $( window ).scrollTop() - window.screenTop,
435                                 sy = 0,
436                                 duration = this.options.snapbackDuration,
437                                 start = getCurrentTime(),
438                                 tfunc;
439
440                         if ( !this.options.outerScrollEnable ) {
441                                 return;
442                         }
443
444                         if ( this._$clip.jqmData("scroll") !== "y" ) {
445                                 return;
446                         }
447
448                         if ( this._outerScrolling ) {
449                                 return;
450                         }
451
452                         if ( y > 0 ) {
453                                 sy = ( window.screenTop ? window.screenTop : -y );
454                         } else if ( y < -scroll_height ) {
455                                 sy = -y - scroll_height;
456                         } else {
457                                 return;
458                         }
459
460                         tfunc = function () {
461                                 var elapsed = getCurrentTime() - start;
462
463                                 if ( elapsed >= duration ) {
464                                         window.scrollTo( 0, top + sy );
465                                         self._outerScrolling = undefined;
466
467                                         self._stopMScroll();
468                                 } else {
469                                         ec = $.easing.easeOutQuad( elapsed / duration,
470                                                         elapsed, 0, 1, duration );
471
472                                         window.scrollTo( 0, top + ( sy * ec ) );
473                                         self._outerScrolling = setTimeout( tfunc, self._timerInterval );
474                                 }
475                         };
476                         this._outerScrolling = setTimeout( tfunc, self._timerInterval );
477                 },
478
479                 _scrollTo: function ( x, y, duration ) {
480                         var self = this,
481                                 start = getCurrentTime(),
482                                 efunc = $.easing.easeOutQuad,
483                                 sx = this._sx,
484                                 sy = this._sy,
485                                 dx = x - sx,
486                                 dy = y - sy,
487                                 tfunc;
488
489                         x = -x;
490                         y = -y;
491
492                         tfunc = function () {
493                                 var elapsed = getCurrentTime() - start,
494                                     ec;
495
496                                 if ( elapsed >= duration ) {
497                                         self._timerID = 0;
498                                         self._setScrollPosition( x, y );
499                                 } else {
500                                         ec = efunc( elapsed / duration, elapsed, 0, 1, duration );
501
502                                         self._setScrollPosition( sx + ( dx * ec ), sy + ( dy * ec ) );
503                                         self._timerID = setTimeout( tfunc, self._timerInterval );
504                                 }
505                         };
506
507                         this._timerID = setTimeout( tfunc, this._timerInterval );
508                 },
509
510                 scrollTo: function ( x, y, duration ) {
511                         this._stopMScroll();
512
513                         if ( !duration || this.options.scrollMethod === "translate" ) {
514                                 this._setScrollPosition( x, y, duration );
515                         } else {
516                                 this._scrollTo( x, y, duration );
517                         }
518                 },
519
520                 getScrollPosition: function () {
521                         return { x: -this._sx, y: -this._sy };
522                 },
523
524                 _getScrollHierarchy: function () {
525                         var svh = [],
526                                 d;
527
528                         this._$clip.parents( ".ui-scrollview-clip").each( function () {
529                                 d = $( this ).jqmData("scrollview");
530                                 if ( d ) {
531                                         svh.unshift( d );
532                                 }
533                         } );
534                         return svh;
535                 },
536
537                 _getAncestorByDirection: function ( dir ) {
538                         var svh = this._getScrollHierarchy(),
539                                 n = svh.length,
540                                 sv,
541                                 svdir;
542
543                         while ( 0 < n-- ) {
544                                 sv = svh[n];
545                                 svdir = sv.options.direction;
546
547                                 if (!svdir || svdir === dir) {
548                                         return sv;
549                                 }
550                         }
551                         return null;
552                 },
553
554                 _handleDragStart: function ( e, ex, ey ) {
555                         this._stopMScroll();
556
557                         this._didDrag = false;
558                         this._skip_dragging = false;
559
560                         var target = $( e.target ),
561                                 self = this,
562                                 $c = this._$clip,
563                                 svdir = this.options.direction;
564
565                         /* should prevent the default behavior when click the button */
566                         this._is_button = target.is( '.ui-btn-text' ) ||
567                                         target.is( '.ui-btn-inner' ) ||
568                                         target.is( '.ui-btn-inner .ui-icon' );
569
570                         if ( this._is_button ) {
571                                 if ( target.parents('.ui-slider-handle').length ) {
572                                         this._skip_dragging = true;
573                                         return;
574                                 }
575                         }
576
577                         /*
578                          * We need to prevent the default behavior to
579                          * suppress accidental selection of text, etc.
580                          */
581                         this._is_inputbox = target.is(':input') ||
582                                         target.parents(':input').length > 0;
583
584                         if ( this._is_inputbox ) {
585                                 target.one( "resize.scrollview", function () {
586                                         if ( ey > $c.height() ) {
587                                                 self.scrollTo( -ex, self._sy - ey + $c.height(),
588                                                         self.options.snapbackDuration );
589                                         }
590                                 });
591                         }
592
593                         if ( this.options.eventType === "mouse" && !this._is_inputbox && !this._is_button ) {
594                                 e.preventDefault();
595                         }
596
597                         this._lastX = ex;
598                         this._lastY = ey;
599                         this._startY = ey;
600                         this._doSnapBackX = false;
601                         this._doSnapBackY = false;
602                         this._speedX = 0;
603                         this._speedY = 0;
604                         this._directionLock = "";
605
606                         this._lastMove = 0;
607                         this._enableTracking();
608
609                         this._set_scrollbar_size();
610                 },
611
612                 _propagateDragMove: function ( sv, e, ex, ey, dir ) {
613                         this._hideScrollBars();
614                         this._hideOverflowIndicator();
615                         this._disableTracking();
616                         sv._handleDragStart( e, ex, ey );
617                         sv._directionLock = dir;
618                         sv._didDrag = this._didDrag;
619                 },
620
621                 _handleDragMove: function ( e, ex, ey ) {
622                         if ( this._skip_dragging ) {
623                                 return;
624                         }
625
626                         if ( !this._dragging ) {
627                                 return;
628                         }
629
630                         if ( !this._is_inputbox && !this._is_button ) {
631                                 e.preventDefault();
632                         }
633
634                         var mt = this.options.moveThreshold,
635                                 dx = ex - this._lastX,
636                                 dy = ey - this._lastY,
637                                 svdir = this.options.direction,
638                                 dir = null,
639                                 x,
640                                 y,
641                                 sv,
642                                 scope,
643                                 newX,
644                                 newY,
645                                 dirLock;
646
647                         this._lastMove = getCurrentTime();
648
649                         if ( !this._directionLock ) {
650                                 x = Math.abs( dx );
651                                 y = Math.abs( dy );
652
653                                 if ( x < mt && y < mt ) {
654                                         return false;
655                                 }
656
657                                 if ( x < y && (x / y) < 0.5 ) {
658                                         dir = "y";
659                                 } else if ( x > y && (y / x) < 0.5 ) {
660                                         dir = "x";
661                                 }
662
663                                 if ( svdir && dir && svdir !== dir ) {
664                                         /*
665                                          * This scrollview can't handle the direction the user
666                                          * is attempting to scroll. Find an ancestor scrollview
667                                          * that can handle the request.
668                                          */
669
670                                         sv = this._getAncestorByDirection( dir );
671                                         if ( sv ) {
672                                                 this._propagateDragMove( sv, e, ex, ey, dir );
673                                                 return false;
674                                         }
675                                 }
676
677                                 this._directionLock = svdir || (dir || "none");
678                         }
679
680                         newX = this._sx;
681                         newY = this._sy;
682                         dirLock = this._directionLock;
683
684                         if ( dirLock !== "y" && this._hTracker ) {
685                                 x = this._sx;
686                                 this._speedX = dx;
687                                 newX = x + dx;
688
689                                 this._doSnapBackX = false;
690
691                                 scope = ( newX > 0 || newX < this._maxX );
692
693                                 if ( scope && dirLock === "x" ) {
694                                         sv = this._getAncestorByDirection("x");
695                                         if ( sv ) {
696                                                 this._setScrollPosition( newX > 0 ?
697                                                                 0 : this._maxX, newY );
698                                                 this._propagateDragMove( sv, e, ex, ey, dir );
699                                                 return false;
700                                         }
701
702                                         newX = x + ( dx / 2 );
703                                         this._doSnapBackX = true;
704                                 }
705                         }
706
707                         if ( dirLock !== "x" && this._vTracker ) {
708                                 if ( Math.abs( this._startY - ey ) < mt && dirLock !== "xy" ) {
709                                         return;
710                                 }
711
712                                 y = this._sy;
713                                 this._speedY = dy;
714                                 newY = y + dy;
715
716                                 this._doSnapBackY = false;
717
718                                 scope = ( newY > 0 || newY < this._maxY );
719
720                                 if ( scope && dirLock === "y" ) {
721                                         sv = this._getAncestorByDirection("y");
722                                         if ( sv ) {
723                                                 this._setScrollPosition( newX,
724                                                                 newY > 0 ? 0 : this._maxY );
725                                                 this._propagateDragMove( sv, e, ex, ey, dir );
726                                                 return false;
727                                         }
728
729                                         newY = y + ( dy / 2 );
730                                         this._doSnapBackY = true;
731                                 }
732                         }
733
734                         if ( this.options.overshootEnable === false ) {
735                                 this._doSnapBackX = false;
736                                 this._doSnapBackY = false;
737                         }
738
739                         this._didDrag = true;
740                         this._lastX = ex;
741                         this._lastY = ey;
742
743                         this._setScrollPosition( newX, newY );
744
745                         this._showScrollBars();
746                         this._showOverflowIndicator();
747                 },
748
749                 _handleDragStop: function ( e ) {
750                         if ( this._skip_dragging ) {
751                                 return;
752                         }
753
754                         var l = this._lastMove,
755                                 t = getCurrentTime(),
756                                 doScroll = (l && (t - l) <= this.options.moveIntervalThreshold),
757                                 sx = ( this._hTracker && this._speedX && doScroll ) ?
758                                                 this._speedX : ( this._doSnapBackX ? 1 : 0 ),
759                                 sy = ( this._vTracker && this._speedY && doScroll ) ?
760                                                 this._speedY : ( this._doSnapBackY ? 1 : 0 ),
761                                 svdir = this.options.direction,
762                                 x,
763                                 y;
764
765                         if ( sx || sy ) {
766                                 if ( !this._setGestureScroll( sx, sy ) ) {
767                                         this._startMScroll( sx, sy );
768                                 }
769                         } else {
770                                 this._hideScrollBars();
771                                 this._hideOverflowIndicator();
772                         }
773
774                         this._disableTracking();
775
776                         if ( this._endEffect ) {
777                                 this._setEndEffect( "out" );
778                                 this._hideScrollBars();
779                                 this._hideOverflowIndicator();
780                         }
781
782                         return !this._didDrag;
783                 },
784
785                 _setGestureScroll: function ( sx, sy ) {
786                         var self = this,
787                                 reset = function () {
788                                         clearTimeout( self._gesture_timer );
789                                         self._gesture_dir = 0;
790                                         self._gesture_count = 0;
791                                         self._gesture_timer = undefined;
792                                 };
793
794                         if ( !sy ) {
795                                 return false;
796                         }
797
798                         dir = sy > 0 ? 1 : -1;
799
800                         if ( !this._gesture_timer ) {
801                                 this._gesture_count = 1;
802                                 this._gesture_dir = dir;
803
804                                 this._gesture_timer = setTimeout( function () {
805                                         reset();
806                                 }, 1000 );
807
808                                 return false;
809                         }
810
811                         if ( this._gesture_dir !== dir ) {
812                                 reset();
813                                 return false;
814                         }
815
816                         this._gesture_count++;
817
818                         if ( this._gesture_count === 3 ) {
819                                 if ( dir > 0 ) {
820                                         this.scrollTo( 0, 0, this.options.overshootDuration );
821                                 } else {
822                                         this.scrollTo( 0, -( this._getViewHeight() - this._$clip.height() ),
823                                                         this.options.overshootDuration );
824                                 }
825                                 reset();
826
827                                 return true;
828                         }
829
830                         return false;
831                 },
832
833                 _enableTracking: function () {
834                         this._dragging = true;
835                 },
836
837                 _disableTracking: function () {
838                         this._dragging = false;
839                 },
840
841                 _showScrollBars: function ( interval ) {
842                         var vclass = "ui-scrollbar-visible",
843                                 self = this;
844
845                         if ( !this.options.showScrollBars ) {
846                                 return;
847                         }
848                         if ( this._scrollbar_showed ) {
849                                 return;
850                         }
851
852                         if ( this._$vScrollBar ) {
853                                 this._$vScrollBar.addClass( vclass );
854                         }
855                         if ( this._$hScrollBar ) {
856                                 this._$hScrollBar.addClass( vclass );
857                         }
858
859                         this._scrollbar_showed = true;
860
861                         if ( interval ) {
862                                 setTimeout( function () {
863                                         self._hideScrollBars();
864                                 }, interval );
865                         }
866                 },
867
868                 _hideScrollBars: function () {
869                         var vclass = "ui-scrollbar-visible";
870
871                         if ( !this.options.showScrollBars ) {
872                                 return;
873                         }
874                         if ( !this._scrollbar_showed ) {
875                                 return;
876                         }
877
878                         if ( this._$vScrollBar ) {
879                                 this._$vScrollBar.removeClass( vclass );
880                         }
881                         if ( this._$hScrollBar ) {
882                                 this._$hScrollBar.removeClass( vclass );
883                         }
884
885                         this._scrollbar_showed = false;
886                 },
887
888                 _resetOverflowIndicator: function () {
889                         if ( !this.options.overflowEnable || !this._overflowAvail || this._softkeyboard ) {
890                                 return;
891                         }
892
893                         this._overflow_top.css( "-webkit-animation", "" );
894                         this._overflow_bottom.css( "-webkit-animation", "" );
895                 },
896
897                 _setOverflowIndicator: function ( dir ) {
898                         if ( dir === 1 ) {
899                                 this._opacity_top = "0.2";
900                                 this._opacity_bottom = "0.8";
901                         } else if ( dir === 0 ) {
902                                 this._opacity_top = "0.8";
903                                 this._opacity_bottom = "0.2";
904                         } else {
905                                 this._opacity_top = "0.5";
906                                 this._opacity_bottom = "0.5";
907                         }
908                 },
909
910                 _getOverflowIndicator: function ( opacity ) {
911                         if ( opacity === "0.2" ) {
912                                 return "-lite";
913                         } else if ( opacity === "0.8" ) {
914                                 return "-dark";
915                         }
916                         return "";
917                 },
918
919                 _showOverflowIndicator: function () {
920                         if ( !this.options.overflowEnable || !this._overflowAvail || this._softkeyboard ) {
921                                 return;
922                         }
923
924                         this._overflow_top.css( "opacity", this._opacity_top );
925                         this._overflow_bottom.css( "opacity", this._opacity_bottom );
926
927                         if ( this._overflow_showed === true ) {
928                                 return;
929                         }
930
931                         this._overflow_top.css( "-webkit-animation", "ui-overflow-show" +
932                                         this._getOverflowIndicator( this._opacity_top ) + " 0.3s 1 ease" );
933                         this._overflow_bottom.css( "-webkit-animation", "ui-overflow-show" +
934                                         this._getOverflowIndicator( this._opacity_bottom ) + " 0.3s 1 ease" );
935
936                         this._overflow_showed = true;
937                 },
938
939                 _hideOverflowIndicator: function () {
940                         var opacity_top,
941                                 opacity_bottom;
942
943                         if ( !this.options.overflowEnable || !this._overflowAvail || this._softkeyboard ) {
944                                 return;
945                         }
946
947                         if ( this._overflow_showed === false ) {
948                                 return;
949                         }
950
951                         opacity_top = this._overflow_top.css( "opacity" );
952                         opacity_bottom = this._overflow_bottom.css( "opacity" );
953
954                         this._overflow_top.css( "opacity", "0" );
955                         this._overflow_bottom.css( "opacity", "0" );
956
957                         this._overflow_top.css( "-webkit-animation", "ui-overflow-hide" +
958                                         this._getOverflowIndicator( opacity_top ) + " 0.5s 1 ease" );
959                         this._overflow_bottom.css( "-webkit-animation", "ui-overflow-hide" +
960                                         this._getOverflowIndicator( opacity_bottom ) + " 0.5s 1 ease" );
961
962                         this._overflow_showed = false;
963                         this._setOverflowIndicator();
964                 },
965
966                 _add_event: function () {
967                         var self = this,
968                                 $c = this._$clip,
969                                 $v = this._$view;
970
971                         if ( this.options.eventType === "mouse" ) {
972                                 this._dragEvt = "mousedown mousemove mouseup click mousewheel";
973
974                                 this._dragCB = function ( e ) {
975                                         switch ( e.type ) {
976                                         case "mousedown":
977                                                 return self._handleDragStart( e,
978                                                                 e.clientX, e.clientY );
979
980                                         case "mousemove":
981                                                 return self._handleDragMove( e,
982                                                                 e.clientX, e.clientY );
983
984                                         case "mouseup":
985                                                 return self._handleDragStop( e );
986
987                                         case "click":
988                                                 return !self._didDrag;
989
990                                         case "mousewheel":
991                                                 var old = self.getScrollPosition();
992                                                 self.scrollTo( -old.x,
993                                                         -(old.y - e.originalEvent.wheelDelta) );
994                                                 break;
995                                         }
996                                 };
997                         } else {
998                                 this._dragEvt = "touchstart touchmove touchend click";
999
1000                                 this._dragCB = function ( e ) {
1001                                         var t;
1002
1003                                         switch ( e.type ) {
1004                                         case "touchstart":
1005                                                 t = e.originalEvent.targetTouches[0];
1006                                                 return self._handleDragStart( e,
1007                                                                 t.pageX, t.pageY );
1008
1009                                         case "touchmove":
1010                                                 t = e.originalEvent.targetTouches[0];
1011                                                 return self._handleDragMove( e,
1012                                                                 t.pageX, t.pageY );
1013
1014                                         case "touchend":
1015                                                 return self._handleDragStop( e );
1016
1017                                         case "click":
1018                                                 return !self._didDrag;
1019                                         }
1020                                 };
1021                         }
1022
1023                         $v.bind( this._dragEvt, this._dragCB );
1024
1025                         $v.bind( "keydown", function ( e ) {
1026                                 var elem,
1027                                         elem_top,
1028                                         screen_h;
1029
1030                                 if ( e.keyCode == 9 ) {
1031                                         return false;
1032                                 }
1033
1034                                 elem = $c.find(".ui-focus");
1035
1036                                 if ( elem === undefined ) {
1037                                         return;
1038                                 }
1039
1040                                 elem_top = elem.offset().top;
1041                                 screen_h = $c.offset().top + $c.height() - elem.height();
1042
1043                                 if ( self._softkeyboard ) {
1044                                         screen_h -= self._softkeyboardHeight;
1045                                 }
1046
1047                                 if ( ( elem_top < 0 ) || ( elem_top > screen_h ) ) {
1048                                         self.scrollTo( 0, self._sy - elem_top +
1049                                                 elem.height() + $c.offset().top, 0);
1050                                 }
1051
1052                                 return;
1053                         });
1054
1055                         $v.bind( "keyup", function ( e ) {
1056                                 var input,
1057                                         elem,
1058                                         elem_top,
1059                                         screen_h;
1060
1061                                 if ( e.keyCode != 9 ) {
1062                                         return;
1063                                 }
1064
1065                                 /* Tab Key */
1066
1067                                 input = $( this ).find(":input");
1068
1069                                 for ( i = 0; i < input.length; i++ ) {
1070                                         if ( !$( input[i] ).hasClass("ui-focus") ) {
1071                                                 continue;
1072                                         }
1073
1074                                         if ( i + 1 == input.length ) {
1075                                                 elem = $( input[0] );
1076                                         } else {
1077                                                 elem = $( input[i + 1] );
1078                                         }
1079
1080                                         elem_top = elem.offset().top;
1081                                         screen_h = $c.offset().top + $c.height() - elem.height();
1082
1083                                         if ( self._softkeyboard ) {
1084                                                 screen_h -= self._softkeyboardHeight;
1085                                         }
1086
1087                                         if ( ( elem_top < 0 ) || ( elem_top > screen_h ) ) {
1088                                                 self.scrollTo( 0, self._sy - elem_top +
1089                                                         elem.height() + $c.offset().top, 0);
1090                                         }
1091
1092                                         elem.focus();
1093
1094                                         break;
1095                                 }
1096
1097                                 return false;
1098                         });
1099
1100                         $c.bind( "updatelayout", function ( e ) {
1101                                 var sy,
1102                                         vh,
1103                                         view_h = self._getViewHeight();
1104
1105                                 if ( !$c.height() || !view_h ) {
1106                                         self.scrollTo( 0, 0, 0 );
1107                                         return;
1108                                 }
1109
1110                                 sy = $c.height() - view_h;
1111                                 vh = view_h - self._view_height;
1112
1113                                 self._view_height = view_h;
1114
1115                                 if ( vh == 0 || vh > $c.height() / 2 ) {
1116                                         return;
1117                                 }
1118
1119                                 if ( sy > 0 ) {
1120                                         self.scrollTo( 0, 0, 0 );
1121                                 } else if ( self._sy - sy <= -vh ) {
1122                                         self.scrollTo( 0, self._sy,
1123                                                 self.options.snapbackDuration );
1124                                 } else if ( self._sy - sy <= vh + self.options.moveThreshold ) {
1125                                         self.scrollTo( 0, sy,
1126                                                 self.options.snapbackDuration );
1127                                 }
1128                         });
1129
1130                         $( window ).bind( "resize", function ( e ) {
1131                                 var focused,
1132                                         view_h = self._getViewHeight();
1133
1134                                 if ( $(".ui-page-active").get(0) !== $c.closest(".ui-page").get(0) ) {
1135                                         return;
1136                                 }
1137
1138                                 if ( !$c.height() || !view_h ) {
1139                                         return;
1140                                 }
1141
1142                                 focused = $c.find(".ui-focus");
1143
1144                                 if ( focused ) {
1145                                         focused.trigger("resize.scrollview");
1146                                 }
1147
1148                                 /* calibration - after triggered throttledresize */
1149                                 setTimeout( function () {
1150                                         if ( self._sy < $c.height() - self._getViewHeight() ) {
1151                                                 self.scrollTo( 0, $c.height() - self._getViewHeight(),
1152                                                         self.options.overshootDuration );
1153                                         }
1154                                 }, 260 );
1155
1156                                 self._view_height = view_h;
1157                         });
1158
1159                         $( window ).bind( "vmouseout", function ( e ) {
1160                                 var drag_stop = false;
1161
1162                                 if ( $(".ui-page-active").get(0) !== $c.closest(".ui-page").get(0) ) {
1163                                         return;
1164                                 }
1165
1166                                 if ( !self._dragging ) {
1167                                         return;
1168                                 }
1169
1170                                 if ( e.pageX < 0 || e.pageX > $( window ).width() ) {
1171                                         drag_stop = true;
1172                                 }
1173
1174                                 if ( e.pageY < 0 || e.pageY > $( window ).height() ) {
1175                                         drag_stop = true;
1176                                 }
1177
1178                                 if ( drag_stop ) {
1179                                         self._hideScrollBars();
1180                                         self._hideOverflowIndicator();
1181                                         self._disableTracking();
1182                                 }
1183                         });
1184
1185                         this._softkeyboard = false;
1186                         this._softkeyboardHeight = 0;
1187
1188                         window.addEventListener( "softkeyboardchange", function ( e ) {
1189                                 if ( $(".ui-page-active").get(0) !== $c.closest(".ui-page").get(0) ) {
1190                                         return;
1191                                 }
1192
1193                                 self._softkeyboard = ( e.state === "on" ? true : false );
1194                                 self._softkeyboardHeight = e.height;
1195                         });
1196
1197                         $c.closest(".ui-page")
1198                                 .one( "pageshow", function ( e ) {
1199                                         self._view_offset = self._$view.offset().top - self._$clip.offset().top;
1200                                         self._view_height = self._getViewHeight();
1201                                 })
1202                                 .bind( "pageshow", function ( e ) {
1203                                         /* should be called after pagelayout */
1204                                         setTimeout( function () {
1205                                                 self._set_scrollbar_size();
1206                                                 self._setScrollPosition( self._sx, self._sy );
1207                                                 self._showScrollBars( 2000 );
1208                                                 self._resetOverflowIndicator();
1209                                         }, 0 );
1210                                 });
1211                 },
1212
1213                 _add_scrollbar: function () {
1214                         var $c = this._$clip,
1215                                 prefix = "<div class=\"ui-scrollbar ui-scrollbar-",
1216                                 suffix = "\"><div class=\"ui-scrollbar-track\"><div class=\"ui-scrollbar-thumb\"></div></div></div>";
1217
1218                         if ( !this.options.showScrollBars ) {
1219                                 return;
1220                         }
1221
1222                         if ( this._vTracker ) {
1223                                 $c.append( prefix + "y" + suffix );
1224                                 this._$vScrollBar = $c.children(".ui-scrollbar-y");
1225                         }
1226                         if ( this._hTracker ) {
1227                                 $c.append( prefix + "x" + suffix );
1228                                 this._$hScrollBar = $c.children(".ui-scrollbar-x");
1229                         }
1230
1231                         this._scrollbar_showed = false;
1232                 },
1233
1234                 _add_scroll_jump: function () {
1235                         var $c = this._$clip,
1236                                 self = this,
1237                                 top_btn,
1238                                 left_btn;
1239
1240                         if ( !this.options.scrollJump ) {
1241                                 return;
1242                         }
1243
1244                         if ( this._vTracker ) {
1245                                 top_btn = $( '<div class="ui-scroll-jump-top-bg">' +
1246                                                 '<div data-role="button" data-inline="true" data-icon="jumptop" style="width:37px;height:37px">.</div></div>' );
1247                                 $c.append( top_btn ).trigger("create");
1248
1249                                 top_btn.bind( "vclick", function () {
1250                                         self.scrollTo( 0, 0, self.options.overshootDuration );
1251                                 } );
1252                         }
1253
1254                         if ( this._hTracker ) {
1255                                 left_btn = $( '<div class="ui-scroll-jump-left-bg">' +
1256                                                 '<div data-role="button" data-inline="true" data-icon="jumpleft" style="width:37px;height:37px">.</div></div>' );
1257                                 $c.append( left_btn ).trigger("create");
1258
1259                                 left_btn.bind( "vclick", function () {
1260                                         self.scrollTo( 0, 0, self.options.overshootDuration );
1261                                 } );
1262                         }
1263                 },
1264
1265                 _add_overflow_indicator: function () {
1266                         if ( !this.options.overflowEnable ) {
1267                                 return;
1268                         }
1269
1270                         this._overflow_top = $( '<div class="ui-overflow-indicator-top"></div>' );
1271                         this._overflow_bottom = $( '<div class="ui-overflow-indicator-bottom"></div>' );
1272
1273                         this._$clip.append( this._overflow_top );
1274                         this._$clip.append( this._overflow_bottom );
1275
1276                         this._opacity_top = "0.5";
1277                         this._opacity_bottom = "0.5";
1278                         this._overflow_showed = false;
1279                 },
1280
1281                 _set_scrollbar_size: function () {
1282                         var $c = this._$clip,
1283                                 $v = this._$view,
1284                                 cw = 0,
1285                                 vw = 0,
1286                                 ch = 0,
1287                                 vh = 0,
1288                                 thumb;
1289
1290                         if ( !this.options.showScrollBars ) {
1291                                 return;
1292                         }
1293
1294                         if ( this._hTracker ) {
1295                                 cw = $c.width();
1296                                 vw = $v.width();
1297                                 this._maxX = cw - vw;
1298
1299                                 if ( this._maxX > 0 ) {
1300                                         this._maxX = 0;
1301                                 }
1302                                 if ( this._$hScrollBar && vw ) {
1303                                         thumb = this._$hScrollBar.find(".ui-scrollbar-thumb");
1304                                         thumb.css( "width", (cw >= vw ? "0" :
1305                                                         (Math.floor(cw / vw * 100) || 1) + "%") );
1306                                 }
1307                         }
1308
1309                         if ( this._vTracker ) {
1310                                 ch = $c.height();
1311                                 vh = this._getViewHeight();
1312                                 this._maxY = ch - vh;
1313
1314                                 if ( this._maxY > 0 ) {
1315                                         this._maxY = 0;
1316                                 }
1317                                 if ( this._$vScrollBar && vh ) {
1318                                         thumb = this._$vScrollBar.find(".ui-scrollbar-thumb");
1319                                         thumb.css( "height", (ch >= vh ? "0" :
1320                                                         (Math.floor(ch / vh * 100) || 1) + "%") );
1321
1322                                         this._overflowAvail = !!thumb.height();
1323                                 }
1324                         }
1325                 }
1326         });
1327
1328         $.extend( MomentumTracker.prototype, {
1329                 start: function ( pos, speed, duration, minPos, maxPos ) {
1330                         var tstate = ( pos < minPos || pos > maxPos ) ?
1331                                         tstates.snapback : tstates.scrolling,
1332                                 pos_temp;
1333
1334                         this.state = ( speed !== 0 ) ? tstate : tstates.done;
1335                         this.pos = pos;
1336                         this.speed = speed;
1337                         this.duration = ( this.state === tstates.snapback ) ?
1338                                         this.options.snapbackDuration : duration;
1339                         this.minPos = minPos;
1340                         this.maxPos = maxPos;
1341
1342                         this.fromPos = ( this.state === tstates.snapback ) ? this.pos : 0;
1343                         pos_temp = ( this.pos < this.minPos ) ? this.minPos : this.maxPos;
1344                         this.toPos = ( this.state === tstates.snapback ) ? pos_temp : 0;
1345
1346                         this.startTime = getCurrentTime();
1347                 },
1348
1349                 reset: function () {
1350                         this.state = tstates.done;
1351                         this.pos = 0;
1352                         this.speed = 0;
1353                         this.minPos = 0;
1354                         this.maxPos = 0;
1355                         this.duration = 0;
1356                         this.remained = 0;
1357                 },
1358
1359                 update: function ( overshootEnable ) {
1360                         var state = this.state,
1361                                 cur_time = getCurrentTime(),
1362                                 duration = this.duration,
1363                                 elapsed =  cur_time - this.startTime,
1364                                 dx,
1365                                 x,
1366                                 didOverShoot;
1367
1368                         if ( state === tstates.done ) {
1369                                 return this.pos;
1370                         }
1371
1372                         elapsed = elapsed > duration ? duration : elapsed;
1373
1374                         this.remained = duration - elapsed;
1375
1376                         if ( state === tstates.scrolling || state === tstates.overshot ) {
1377                                 dx = this.speed *
1378                                         ( 1 - $.easing[this.easing]( elapsed / duration,
1379                                                                 elapsed, 0, 1, duration ) );
1380
1381                                 x = this.pos + dx;
1382
1383                                 didOverShoot = ( state === tstates.scrolling ) &&
1384                                         ( x < this.minPos || x > this.maxPos );
1385
1386                                 if ( didOverShoot ) {
1387                                         x = ( x < this.minPos ) ? this.minPos : this.maxPos;
1388                                 }
1389
1390                                 this.pos = x;
1391
1392                                 if ( state === tstates.overshot ) {
1393                                         if ( !overshootEnable ) {
1394                                                 this.state = tstates.done;
1395                                         }
1396                                         if ( elapsed >= duration ) {
1397                                                 this.state = tstates.snapback;
1398                                                 this.fromPos = this.pos;
1399                                                 this.toPos = ( x < this.minPos ) ?
1400                                                                 this.minPos : this.maxPos;
1401                                                 this.duration = this.options.snapbackDuration;
1402                                                 this.startTime = cur_time;
1403                                                 elapsed = 0;
1404                                         }
1405                                 } else if ( state === tstates.scrolling ) {
1406                                         if ( didOverShoot && overshootEnable ) {
1407                                                 this.state = tstates.overshot;
1408                                                 this.speed = dx / 2;
1409                                                 this.duration = this.options.overshootDuration;
1410                                                 this.startTime = cur_time;
1411                                         } else if ( elapsed >= duration ) {
1412                                                 this.state = tstates.done;
1413                                         }
1414                                 }
1415                         } else if ( state === tstates.snapback ) {
1416                                 if ( elapsed >= duration ) {
1417                                         this.pos = this.toPos;
1418                                         this.state = tstates.done;
1419                                 } else {
1420                                         this.pos = this.fromPos + (( this.toPos - this.fromPos ) *
1421                                                 $.easing[this.easing]( elapsed / duration,
1422                                                         elapsed, 0, 1, duration ));
1423                                 }
1424                         }
1425
1426                         return this.pos;
1427                 },
1428
1429                 done: function () {
1430                         return this.state === tstates.done;
1431                 },
1432
1433                 isMin: function () {
1434                         return this.pos === this.minPos;
1435                 },
1436
1437                 isMax: function () {
1438                         return this.pos === this.maxPos;
1439                 },
1440
1441                 isAvail: function () {
1442                         return !( this.minPos === this.maxPos );
1443                 },
1444
1445                 getRemained: function () {
1446                         return this.remained;
1447                 },
1448
1449                 getPosition: function () {
1450                         return this.pos;
1451                 }
1452         });
1453
1454         $( document ).bind( 'pagecreate create', function ( e ) {
1455                 var $page = $( e.target ),
1456                         content_scroll = $page.find(".ui-content").jqmData("scroll");
1457
1458                 /* content scroll */
1459                 if ( $.support.scrollview === undefined ) {
1460                         $.support.scrollview = true;
1461                 }
1462
1463                 if ( $.support.scrollview === true && content_scroll === undefined ) {
1464                         content_scroll = "y";
1465                 }
1466
1467                 if ( content_scroll !== "y" ) {
1468                         content_scroll = "none";
1469                 }
1470
1471                 $page.find(".ui-content").attr( "data-scroll", content_scroll );
1472
1473                 $page.find(":jqmData(scroll):not(.ui-scrollview-clip)").each( function () {
1474                         if ( $( this ).hasClass("ui-scrolllistview") ) {
1475                                 $( this ).scrolllistview();
1476                         } else {
1477                                 var st = $( this ).jqmData("scroll"),
1478                                         dir = st && ( st.search(/^[xy]/) !== -1 ) ? st : null,
1479                                         content = $(this).hasClass("ui-content"),
1480                                         opts;
1481
1482                                 if ( st === "none" ) {
1483                                         return;
1484                                 }
1485
1486                                 opts = {
1487                                         direction: dir || undefined,
1488                                         overflowEnable: content,
1489                                         scrollMethod: $( this ).jqmData("scroll-method") || undefined,
1490                                         scrollJump: $( this ).jqmData("scroll-jump") || undefined
1491                                 };
1492
1493                                 $( this ).scrollview( opts );
1494                         }
1495                 });
1496         });
1497
1498         $( document ).bind( 'pageshow', function ( e ) {
1499                 var $page = $( e.target ),
1500                         scroll = $page.find(".ui-content").jqmData("scroll");
1501
1502                 if ( scroll === "y" ) {
1503                         resizePageContentHeight( e.target );
1504                 }
1505         });
1506
1507 }( jQuery, window, document ) );