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