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