5ac74debbd3bd3a8b0eef8b5904b81c9a4b2c977
[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                         fps:               60,    // Frames per second in msecs.
44                         direction:         null,  // "x", "y", or null for both.
45
46                         scrollDuration:    2000,  // 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                         scrollJump:        false,
63                 },
64
65                 _getViewHeight: function () {
66                         return this._$view.height() + this._view_offset;
67                 },
68
69                 _makePositioned: function ( $ele ) {
70                         if ( $ele.css("position") === "static" ) {
71                                 $ele.css( "position", "relative" );
72                         }
73                 },
74
75                 _create: function () {
76                         var direction,
77                                 self = this;
78
79                         this._$clip = $( this.element ).addClass("ui-scrollview-clip");
80                         this._$view = this._$clip.wrapInner("<div></div>").children()
81                                                         .addClass("ui-scrollview-view");
82
83                         if ( this.options.scrollMethod === "translate" ) {
84                                 if ( this._$view.css("transform") === undefined ) {
85                                         this.options.scrollMethod = "position";
86                                 }
87                         }
88
89                         this._$clip.css( "overflow", "hidden" );
90                         this._makePositioned( this._$clip );
91
92                         this._makePositioned( this._$view );
93                         this._$view.css( { left: 0, top: 0 } );
94
95                         this._view_offset = this._$view.offset().top - this._$clip.offset().top;
96                         this._view_height = this._getViewHeight();
97
98                         this._sx = 0;
99                         this._sy = 0;
100
101                         direction = this.options.direction;
102
103                         this._hTracker = ( direction !== "y" ) ?
104                                         new MomentumTracker( this.options ) : null;
105                         this._vTracker = ( direction !== "x" ) ?
106                                         new MomentumTracker( this.options ) : null;
107
108                         this._timerInterval = 1000 / this.options.fps;
109                         this._timerID = 0;
110
111                         this._timerCB = function () {
112                                 self._handleMomentumScroll();
113                         };
114
115                         this._add_event();
116                         this._add_scrollbar();
117                         this._add_scroll_jump();
118                 },
119
120                 _startMScroll: function ( speedX, speedY ) {
121                         var keepGoing = false,
122                                 duration = this.options.scrollDuration,
123                                 ht = this._hTracker,
124                                 vt = this._vTracker,
125                                 c,
126                                 v;
127
128                         this._stopMScroll();
129                         this._showScrollBars();
130
131                         this._$clip.trigger( this.options.startEventName );
132
133                         if ( ht ) {
134                                 c = this._$clip.width();
135                                 v = this._$view.width();
136
137                                 ht.start( this._sx, speedX,
138                                         duration, (v > c) ? -(v - c) : 0, 0 );
139                                 keepGoing = !ht.done();
140                         }
141
142                         if ( vt ) {
143                                 c = this._$clip.height();
144                                 v = this._getViewHeight();
145
146                                 vt.start( this._sy, speedY,
147                                         duration, (v > c) ? -(v - c) : 0, 0 );
148                                 keepGoing = keepGoing || !vt.done();
149                         }
150
151                         if ( keepGoing ) {
152                                 this._timerID = setTimeout( this._timerCB, this._timerInterval );
153                         } else {
154                                 this._stopMScroll();
155                         }
156                 },
157
158                 _stopMScroll: function () {
159                         if ( this._timerID ) {
160                                 this._$clip.trigger( this.options.stopEventName );
161                                 clearTimeout( this._timerID );
162                         }
163                         this._timerID = 0;
164
165                         if ( this._vTracker ) {
166                                 this._vTracker.reset();
167                         }
168
169                         if ( this._hTracker ) {
170                                 this._hTracker.reset();
171                         }
172
173                         this._hideScrollBars();
174                 },
175
176                 _handleMomentumScroll: function () {
177                         var keepGoing = false,
178                                 x = 0,
179                                 y = 0,
180                                 vt = this._vTracker,
181                                 ht = this._hTracker;
182
183                         if ( this._outerScrolling ) {
184                                 return;
185                         }
186
187                         if ( vt ) {
188                                 vt.update( this.options.overshootEnable );
189                                 y = vt.getPosition();
190                                 keepGoing = !vt.done();
191                         }
192
193                         if ( ht ) {
194                                 ht.update( this.options.overshootEnable );
195                                 x = ht.getPosition();
196                                 keepGoing = keepGoing || !ht.done();
197                         }
198
199                         this._setScrollPosition( x, y );
200                         this._$clip.trigger( this.options.updateEventName,
201                                         [ { x: x, y: y } ] );
202
203                         if ( keepGoing ) {
204                                 this._timerID = setTimeout( this._timerCB, this._timerInterval );
205                         } else {
206                                 this._stopMScroll();
207                         }
208                 },
209
210                 _setElementTransform: function ( $ele, x, y, duration ) {
211                         var translate,
212                                 transition;
213
214                         if ( !duration || duration === undefined ) {
215                                 transition = "none";
216                         } else {
217                                 transition =  "-webkit-transform " + duration / 1000 + "s";
218                         }
219
220                         if ( $.support.cssTransform3d ) {
221                                 translate = "translate3d(" + x + "," + y + ", 0px)";
222                         } else {
223                                 translate = "translate(" + x + "," + y + ")";
224                         }
225
226                         $ele.css({
227                                 "-moz-transform": translate,
228                                 "-webkit-transform": translate,
229                                 "-ms-transform": translate,
230                                 "-o-transform": translate,
231                                 "transform": translate,
232                                 "-webkit-transition": transition
233                         });
234                 },
235
236                 _setCalibration: function ( x, y ) {
237                         if ( this.options.overshootEnable ) {
238                                 this._sx = x;
239                                 this._sy = y;
240                                 return;
241                         }
242
243                         var $v = this._$view,
244                                 $c = this._$clip,
245                                 dirLock = this._directionLock,
246                                 scroll_height = 0,
247                                 scroll_width = 0;
248
249                         if ( dirLock !== "y" && this._hTracker ) {
250                                 scroll_width = $v.width() - $c.width();
251
252                                 if ( x >= 0 ) {
253                                         this._sx = 0;
254                                 } else if ( x < -scroll_width ) {
255                                         this._sx = -scroll_width;
256                                 } else {
257                                         this._sx = x;
258                                 }
259
260                                 if ( scroll_width < 0 ) {
261                                         this._sx = 0;
262                                 }
263                         }
264
265                         if ( dirLock !== "x" && this._vTracker ) {
266                                 scroll_height = this._getViewHeight() - $c.height();
267
268                                 this._outerScroll( y, scroll_height );
269
270                                 if ( y >= 0 ) {
271                                         this._sy = 0;
272                                 } else if ( y < -scroll_height ) {
273                                         this._sy = -scroll_height;
274                                 } else {
275                                         this._sy = y;
276                                 }
277
278                                 if ( scroll_height < 0 ) {
279                                         this._sy = 0;
280                                 }
281                         }
282                 },
283
284                 _setScrollPosition: function ( x, y, duration ) {
285                         var $v = this._$view,
286                                 sm = this.options.scrollMethod,
287                                 $vsb = this._$vScrollBar,
288                                 $hsb = this._$hScrollBar,
289                                 $sbt;
290
291                         this._setCalibration( x, y );
292
293                         x = this._sx;
294                         y = this._sy;
295
296                         if ( sm === "translate" ) {
297                                 this._setElementTransform( $v, x + "px", y + "px", duration );
298                         } else {
299                                 $v.css( {left: x + "px", top: y + "px"} );
300                         }
301
302                         if ( $vsb ) {
303                                 $sbt = $vsb.find(".ui-scrollbar-thumb");
304
305                                 if ( sm === "translate" ) {
306                                         this._setElementTransform( $sbt, "0px",
307                                                 -y / this._getViewHeight() * $sbt.parent().height() + "px",
308                                                 duration );
309                                 } else {
310                                         $sbt.css( "top", -y / this._getViewHeight() * 100 + "%" );
311                                 }
312                         }
313
314                         if ( $hsb ) {
315                                 $sbt = $hsb.find(".ui-scrollbar-thumb");
316
317                                 if ( sm === "translate" ) {
318                                         this._setElementTransform( $sbt,
319                                                 -x / $v.width() * $sbt.parent().width() + "px", "0px",
320                                                 duration);
321                                 } else {
322                                         $sbt.css("left", -x / $v.width() * 100 + "%");
323                                 }
324                         }
325                 },
326
327                 _outerScroll: function ( y, scroll_height ) {
328                         var self = this,
329                                 top = $( window ).scrollTop(),
330                                 sy = 0,
331                                 duration = this.options.snapbackDuration,
332                                 start = getCurrentTime(),
333                                 tfunc;
334
335                         if ( this._$clip.jqmData("scroll") !== "y" ) {
336                                 return;
337                         }
338
339                         if ( this._outerScrolling ) {
340                                 return;
341                         }
342
343                         if ( !this._dragging ) {
344                                 return;
345                         }
346
347                         if ( scroll_height < 0 ) {
348                                 return;
349                         }
350
351                         if ( y > 0 ) {
352                                 sy = -y;
353                         } else if ( y < -scroll_height ) {
354                                 sy = -y - scroll_height;
355                         } else {
356                                 return;
357                         }
358
359                         sy *= 10;
360
361                         tfunc = function () {
362                                 var elapsed = getCurrentTime() - start;
363
364                                 if ( elapsed >= duration ) {
365                                         window.scrollTo( 0, top + sy );
366                                         self._outerScrolling = undefined;
367                                 } else {
368                                         ec = $.easing.easeOutQuad( elapsed / duration, elapsed, 0, 1, duration );
369
370                                         window.scrollTo( 0, top + ( sy * ec ) );
371                                         self._outerScrolling = setTimeout( tfunc, self._timerInterval );
372                                 }
373                         };
374                         this._outerScrolling = setTimeout( tfunc, self._timerInterval );
375
376                         /* skip the srollview dragging */
377                         this._skip_dragging = true;
378                 },
379
380                 _scrollTo: function ( x, y, duration ) {
381                         var self = this,
382                                 start = getCurrentTime(),
383                                 efunc = $.easing.easeOutQuad,
384                                 sx = this._sx,
385                                 sy = this._sy,
386                                 dx = x - sx,
387                                 dy = y - sy,
388                                 tfunc;
389
390                         x = -x;
391                         y = -y;
392
393                         tfunc = function () {
394                                 var elapsed = getCurrentTime() - start,
395                                     ec;
396
397                                 if ( elapsed >= duration ) {
398                                         self._timerID = 0;
399                                         self._setScrollPosition( x, y );
400                                 } else {
401                                         ec = efunc( elapsed / duration, elapsed, 0, 1, duration );
402
403                                         self._setScrollPosition( sx + ( dx * ec ), sy + ( dy * ec ) );
404                                         self._timerID = setTimeout( tfunc, self._timerInterval );
405                                 }
406                         };
407
408                         this._timerID = setTimeout( tfunc, this._timerInterval );
409                 },
410
411                 scrollTo: function ( x, y, duration ) {
412                         this._stopMScroll();
413
414                         if ( !duration || this.options.scrollMethod === "translate" ) {
415                                 this._setScrollPosition( x, y, duration );
416                         } else {
417                                 this._scrollTo( x, y, duration );
418                         }
419                 },
420
421                 getScrollPosition: function () {
422                         return { x: -this._sx, y: -this._sy };
423                 },
424
425                 _getScrollHierarchy: function () {
426                         var svh = [],
427                                 d;
428
429                         this._$clip.parents( ".ui-scrollview-clip").each( function () {
430                                 d = $( this ).jqmData("scrollview");
431                                 if ( d ) {
432                                         svh.unshift( d );
433                                 }
434                         } );
435                         return svh;
436                 },
437
438                 _getAncestorByDirection: function ( dir ) {
439                         var svh = this._getScrollHierarchy(),
440                                 n = svh.length,
441                                 sv,
442                                 svdir;
443
444                         while ( 0 < n-- ) {
445                                 sv = svh[n];
446                                 svdir = sv.options.direction;
447
448                                 if (!svdir || svdir === dir) {
449                                         return sv;
450                                 }
451                         }
452                         return null;
453                 },
454
455                 _handleDragStart: function ( e, ex, ey ) {
456                         this._stopMScroll();
457
458                         this._didDrag = false;
459                         this._skip_dragging = false;
460
461                         var target = $( e.target ),
462                                 self = this,
463                                 $c = this._$clip,
464                                 svdir = this.options.direction;
465
466                         /* should prevent the default behavior when click the button */
467                         this._is_button = target.is( '.ui-btn-text' ) ||
468                                         target.is( '.ui-btn-inner' ) ||
469                                         target.is( '.ui-btn-inner .ui-icon' );
470
471                         if ( this._is_button ) {
472                                 if ( target.parents('.ui-slider-handle') ) {
473                                         this._skip_dragging = true;
474                                         return;
475                                 }
476                         }
477
478                         /*
479                          * We need to prevent the default behavior to
480                          * suppress accidental selection of text, etc.
481                          */
482                         this._is_inputbox = target.is(':input') ||
483                                         target.parents(':input').length > 0;
484
485                         if ( this._is_inputbox ) {
486                                 target.one( "resize.scrollview", function () {
487                                         if ( ey > $c.height() ) {
488                                                 self.scrollTo( -ex, self._sy - ey + $c.height(),
489                                                         self.options.snapbackDuration );
490                                         }
491                                 });
492                         }
493
494                         this._lastX = ex;
495                         this._lastY = ey;
496                         this._startY = ey;
497                         this._doSnapBackX = false;
498                         this._doSnapBackY = false;
499                         this._speedX = 0;
500                         this._speedY = 0;
501                         this._directionLock = "";
502
503                         this._lastMove = 0;
504                         this._enableTracking();
505
506                         this._set_scrollbar_size();
507                 },
508
509                 _propagateDragMove: function ( sv, e, ex, ey, dir ) {
510                         this._hideScrollBars();
511                         this._disableTracking();
512                         sv._handleDragStart( e, ex, ey );
513                         sv._directionLock = dir;
514                         sv._didDrag = this._didDrag;
515                 },
516
517                 _handleDragMove: function ( e, ex, ey ) {
518                         if ( this._skip_dragging ) {
519                                 return;
520                         }
521
522                         if ( !this._dragging ) {
523                                 return;
524                         }
525
526                         if ( !this._is_inputbox && !this._is_button ) {
527                                 e.preventDefault();
528                         }
529
530                         var mt = this.options.moveThreshold,
531                                 dx = ex - this._lastX,
532                                 dy = ey - this._lastY,
533                                 svdir = this.options.direction,
534                                 dir = null,
535                                 x,
536                                 y,
537                                 sv,
538                                 scope,
539                                 newX,
540                                 newY,
541                                 dirLock;
542
543                         if ( Math.abs( this._startY - ey ) < mt && !this._didDrag ) {
544                                 return;
545                         }
546
547                         this._lastMove = getCurrentTime();
548
549                         if ( !this._directionLock ) {
550                                 x = Math.abs( dx );
551                                 y = Math.abs( dy );
552
553                                 if ( x < mt && y < mt ) {
554                                         return false;
555                                 }
556
557                                 if ( x < y && (x / y) < 0.5 ) {
558                                         dir = "y";
559                                 } else if ( x > y && (y / x) < 0.5 ) {
560                                         dir = "x";
561                                 }
562
563                                 if ( svdir && dir && svdir !== dir ) {
564                                         /*
565                                          * This scrollview can't handle the direction the user
566                                          * is attempting to scroll. Find an ancestor scrollview
567                                          * that can handle the request.
568                                          */
569
570                                         sv = this._getAncestorByDirection( dir );
571                                         if ( sv ) {
572                                                 this._propagateDragMove( sv, e, ex, ey, dir );
573                                                 return false;
574                                         }
575                                 }
576
577                                 this._directionLock = svdir || (dir || "none");
578                         }
579
580                         newX = this._sx;
581                         newY = this._sy;
582                         dirLock = this._directionLock;
583
584                         if ( dirLock !== "y" && this._hTracker ) {
585                                 x = this._sx;
586                                 this._speedX = dx;
587                                 newX = x + dx;
588
589                                 this._doSnapBackX = false;
590
591                                 scope = ( newX > 0 || newX < this._maxX );
592
593                                 if ( scope && dirLock === "x" ) {
594                                         sv = this._getAncestorByDirection("x");
595                                         if ( sv ) {
596                                                 this._setScrollPosition( newX > 0 ?
597                                                                 0 : this._maxX, newY );
598                                                 this._propagateDragMove( sv, e, ex, ey, dir );
599                                                 return false;
600                                         }
601
602                                         newX = x + ( dx / 2 );
603                                         this._doSnapBackX = true;
604                                 }
605                         }
606
607                         if ( dirLock !== "x" && this._vTracker ) {
608                                 y = this._sy;
609                                 this._speedY = dy;
610                                 newY = y + dy;
611
612                                 this._doSnapBackY = false;
613
614                                 scope = ( newY > 0 || newY < this._maxY );
615
616                                 if ( scope && dirLock === "y" ) {
617                                         sv = this._getAncestorByDirection("y");
618                                         if ( sv ) {
619                                                 this._setScrollPosition( newX,
620                                                                 newY > 0 ? 0 : this._maxY );
621                                                 this._propagateDragMove( sv, e, ex, ey, dir );
622                                                 return false;
623                                         }
624
625                                         newY = y + ( dy / 2 );
626                                         this._doSnapBackY = true;
627                                 }
628                         }
629
630                         if ( this.options.overshootEnable === false ) {
631                                 this._doSnapBackX = false;
632                                 this._doSnapBackY = false;
633                         }
634
635                         this._didDrag = true;
636                         this._lastX = ex;
637                         this._lastY = ey;
638
639                         this._setScrollPosition( newX, newY );
640
641                         this._showScrollBars();
642                 },
643
644                 _handleDragStop: function ( e ) {
645                         if ( this._skip_dragging ) {
646                                 return;
647                         }
648
649                         var l = this._lastMove,
650                                 t = getCurrentTime(),
651                                 doScroll = (l && (t - l) <= this.options.moveIntervalThreshold),
652                                 sx = ( this._hTracker && this._speedX && doScroll ) ?
653                                                 this._speedX : ( this._doSnapBackX ? 1 : 0 ),
654                                 sy = ( this._vTracker && this._speedY && doScroll ) ?
655                                                 this._speedY : ( this._doSnapBackY ? 1 : 0 ),
656                                 svdir = this.options.direction,
657                                 x,
658                                 y;
659
660                         if ( sx || sy ) {
661                                 this._startMScroll( sx, sy );
662                         } else {
663                                 this._hideScrollBars();
664                         }
665
666                         this._disableTracking();
667
668                         return !this._didDrag;
669                 },
670
671                 _enableTracking: function () {
672                         this._dragging = true;
673                 },
674
675                 _disableTracking: function () {
676                         this._dragging = false;
677                 },
678
679                 _showScrollBars: function () {
680                         var vclass = "ui-scrollbar-visible";
681
682                         if ( !this.options.showScrollBars ) {
683                                 return;
684                         }
685                         if ( this._scrollbar_showed ) {
686                                 return;
687                         }
688
689                         if ( this._$vScrollBar ) {
690                                 this._$vScrollBar.addClass( vclass );
691                         }
692                         if ( this._$hScrollBar ) {
693                                 this._$hScrollBar.addClass( vclass );
694                         }
695
696                         this._scrollbar_showed = true;
697                 },
698
699                 _hideScrollBars: function () {
700                         var vclass = "ui-scrollbar-visible";
701
702                         if ( !this.options.showScrollBars ) {
703                                 return;
704                         }
705                         if ( !this._scrollbar_showed ) {
706                                 return;
707                         }
708
709                         if ( this._$vScrollBar ) {
710                                 this._$vScrollBar.removeClass( vclass );
711                         }
712                         if ( this._$hScrollBar ) {
713                                 this._$hScrollBar.removeClass( vclass );
714                         }
715
716                         this._scrollbar_showed = false;
717                 },
718
719                 _add_event: function () {
720                         var self = this,
721                                 $c = this._$clip,
722                                 $v = this._$view;
723
724                         if ( this.options.eventType === "mouse" ) {
725                                 this._dragEvt = "mousedown mousemove mouseup click mousewheel";
726
727                                 this._dragCB = function ( e ) {
728                                         switch ( e.type ) {
729                                         case "mousedown":
730                                                 return self._handleDragStart( e,
731                                                                 e.clientX, e.clientY );
732
733                                         case "mousemove":
734                                                 return self._handleDragMove( e,
735                                                                 e.clientX, e.clientY );
736
737                                         case "mouseup":
738                                                 return self._handleDragStop( e );
739
740                                         case "click":
741                                                 return !self._didDrag;
742
743                                         case "mousewheel":
744                                                 var old = self.getScrollPosition();
745                                                 self.scrollTo( -old.x,
746                                                         -(old.y - e.originalEvent.wheelDelta) );
747                                                 break;
748                                         }
749                                 };
750                         } else {
751                                 this._dragEvt = "touchstart touchmove touchend click";
752
753                                 this._dragCB = function ( e ) {
754                                         var t;
755
756                                         switch ( e.type ) {
757                                         case "touchstart":
758                                                 t = e.originalEvent.targetTouches[0];
759                                                 return self._handleDragStart( e,
760                                                                 t.pageX, t.pageY );
761
762                                         case "touchmove":
763                                                 t = e.originalEvent.targetTouches[0];
764                                                 return self._handleDragMove( e,
765                                                                 t.pageX, t.pageY );
766
767                                         case "touchend":
768                                                 return self._handleDragStop( e );
769
770                                         case "click":
771                                                 return !self._didDrag;
772                                         }
773                                 };
774                         }
775
776                         $v.bind( this._dragEvt, this._dragCB );
777
778                         if ( $c.jqmData("scroll") !== "y" ) {
779                                 return;
780                         }
781
782                         $c.bind( "updatelayout", function ( e ) {
783                                 var sy,
784                                         vh,
785                                         view_h = self._getViewHeight();
786
787                                 if ( !$c.height() || !view_h ) {
788                                         self.scrollTo( 0, 0, 0 );
789                                         return;
790                                 }
791
792                                 sy = $c.height() - view_h;
793                                 vh = view_h - self._view_height;
794
795                                 self._view_height = view_h;
796
797                                 if ( vh == 0 || vh > $c.height() / 2 ) {
798                                         return;
799                                 }
800
801                                 if ( self._sy - sy <= -vh ) {
802                                         self.scrollTo( 0, self._sy,
803                                                 self.options.snapbackDuration );
804                                 } else if ( self._sy - sy <= vh + self.options.moveThreshold ) {
805                                         self.scrollTo( 0, sy,
806                                                 self.options.snapbackDuration );
807                                 }
808                         });
809
810                         $( window ).bind( "resize", function ( e ) {
811                                 var focused,
812                                         view_h = self._getViewHeight();
813
814                                 if ( $(".ui-page-active").get(0) !== self._page.get(0) ) {
815                                         return;
816                                 }
817
818                                 if ( !$c.height() || !view_h ) {
819                                         return;
820                                 }
821
822                                 focused = $c.find(".ui-focus");
823
824                                 if ( focused ) {
825                                         focused.trigger("resize.scrollview");
826                                 }
827
828                                 /* calibration - after triggered throttledresize */
829                                 setTimeout( function () {
830                                         if ( self._sy < $c.height() - self._getViewHeight() ) {
831                                                 self.scrollTo( 0, self._sy,
832                                                         self.options.snapbackDuration );
833                                         }
834                                 }, 260 );
835
836                                 self._view_height = view_h;
837                         });
838
839                         $( document ).one( "pageshow", function ( e ) {
840                                 self._page = $(".ui-page-active");
841                                 self._view_offset = self._$view.offset().top - self._$clip.offset().top;
842                                 self._view_height = self._getViewHeight();
843                         });
844                 },
845
846                 _add_scrollbar: function () {
847                         var $c = this._$clip,
848                                 prefix = "<div class=\"ui-scrollbar ui-scrollbar-",
849                                 suffix = "\"><div class=\"ui-scrollbar-track\"><div class=\"ui-scrollbar-thumb\"></div></div></div>";
850
851                         if ( !this.options.showScrollBars ) {
852                                 return;
853                         }
854
855                         if ( this._vTracker ) {
856                                 $c.append( prefix + "y" + suffix );
857                                 this._$vScrollBar = $c.children(".ui-scrollbar-y");
858                         }
859                         if ( this._hTracker ) {
860                                 $c.append( prefix + "x" + suffix );
861                                 this._$hScrollBar = $c.children(".ui-scrollbar-x");
862                         }
863
864                         this._scrollbar_showed = false;
865                 },
866
867                 _add_scroll_jump: function () {
868                         var $c = this._$clip,
869                                 self = this,
870                                 top_btn,
871                                 left_btn;
872
873                         if ( !this.options.scrollJump ) {
874                                 return;
875                         }
876
877                         if ( this._vTracker ) {
878                                 top_btn = $( '<div class="ui-scroll-jump-top-bg ui-btn" data-theme="s">' +
879                                                 '<div class="ui-scroll-jump-top"></div></div>' );
880                                 $c.append( top_btn );
881
882                                 top_btn.bind( "vclick", function () {
883                                         self.scrollTo( 0, 0, self.options.overshootDuration );
884                                 } );
885                         }
886
887                         if ( this._hTracker ) {
888                                 left_btn = $( '<div class="ui-scroll-jump-left-bg ui-btn" data-theme="s">' +
889                                                 '<div class="ui-scroll-jump-left"></div></div>' );
890                                 $c.append( left_btn );
891
892                                 left_btn.bind( "vclick", function () {
893                                         self.scrollTo( 0, 0, self.options.overshootDuration );
894                                 } );
895                         }
896                 },
897
898                 _set_scrollbar_size: function () {
899                         var $c = this._$clip,
900                                 $v = this._$view,
901                                 cw = 0,
902                                 vw = 0,
903                                 ch = 0,
904                                 vh = 0,
905                                 thumb;
906
907                         if ( !this.options.showScrollBars ) {
908                                 return;
909                         }
910
911                         if ( this._hTracker ) {
912                                 cw = $c.width();
913                                 vw = $v.width();
914                                 this._maxX = cw - vw;
915
916                                 if ( this._maxX > 0 ) {
917                                         this._maxX = 0;
918                                 }
919                                 if ( this._$hScrollBar && vw ) {
920                                         thumb = this._$hScrollBar.find(".ui-scrollbar-thumb");
921                                         thumb.css( "width", (cw >= vw ? "100%" :
922                                                         (Math.floor(cw / vw * 100) || 1) + "%") );
923                                 }
924                         }
925
926                         if ( this._vTracker ) {
927                                 ch = $c.height();
928                                 vh = this._getViewHeight();
929                                 this._maxY = ch - vh;
930
931                                 if ( this._maxY > 0 ) {
932                                         this._maxY = 0;
933                                 }
934                                 if ( this._$vScrollBar && vh ) {
935                                         thumb = this._$vScrollBar.find(".ui-scrollbar-thumb");
936                                         thumb.css( "height", (ch >= vh ? "100%" :
937                                                         (Math.floor(ch / vh * 100) || 1) + "%") );
938                                 }
939                         }
940                 }
941         });
942
943         $.extend( MomentumTracker.prototype, {
944                 start: function ( pos, speed, duration, minPos, maxPos ) {
945                         var tstate = ( pos < minPos || pos > maxPos ) ?
946                                         tstates.snapback : tstates.scrolling,
947                                 pos_temp;
948
949                         this.state = ( speed !== 0 ) ? tstate : tstates.done;
950                         this.pos = pos;
951                         this.speed = speed;
952                         this.duration = ( this.state === tstates.snapback ) ?
953                                         this.options.snapbackDuration : duration;
954                         this.minPos = minPos;
955                         this.maxPos = maxPos;
956
957                         this.fromPos = ( this.state === tstates.snapback ) ? this.pos : 0;
958                         pos_temp = ( this.pos < this.minPos ) ? this.minPos : this.maxPos;
959                         this.toPos = ( this.state === tstates.snapback ) ? pos_temp : 0;
960
961                         this.startTime = getCurrentTime();
962                 },
963
964                 reset: function () {
965                         this.state = tstates.done;
966                         this.pos = 0;
967                         this.speed = 0;
968                         this.minPos = 0;
969                         this.maxPos = 0;
970                         this.duration = 0;
971                 },
972
973                 update: function ( overshootEnable ) {
974                         var state = this.state,
975                                 cur_time = getCurrentTime(),
976                                 duration = this.duration,
977                                 elapsed =  cur_time - this.startTime,
978                                 dx,
979                                 x,
980                                 didOverShoot;
981
982                         if ( state === tstates.done ) {
983                                 return this.pos;
984                         }
985
986                         elapsed = elapsed > duration ? duration : elapsed;
987
988                         if ( state === tstates.scrolling || state === tstates.overshot ) {
989                                 dx = this.speed *
990                                         ( 1 - $.easing[this.easing]( elapsed / duration,
991                                                                 elapsed, 0, 1, duration ) );
992
993                                 x = this.pos + dx;
994
995                                 didOverShoot = ( state === tstates.scrolling ) &&
996                                         ( x < this.minPos || x > this.maxPos );
997
998                                 if ( didOverShoot ) {
999                                         x = ( x < this.minPos ) ? this.minPos : this.maxPos;
1000                                 }
1001
1002                                 this.pos = x;
1003
1004                                 if ( state === tstates.overshot ) {
1005                                         if ( elapsed >= duration ) {
1006                                                 this.state = tstates.snapback;
1007                                                 this.fromPos = this.pos;
1008                                                 this.toPos = ( x < this.minPos ) ?
1009                                                                 this.minPos : this.maxPos;
1010                                                 this.duration = this.options.snapbackDuration;
1011                                                 this.startTime = cur_time;
1012                                                 elapsed = 0;
1013                                         }
1014                                 } else if ( state === tstates.scrolling ) {
1015                                         if ( didOverShoot && overshootEnable ) {
1016                                                 this.state = tstates.overshot;
1017                                                 this.speed = dx / 2;
1018                                                 this.duration = this.options.overshootDuration;
1019                                                 this.startTime = cur_time;
1020                                         } else if ( elapsed >= duration ) {
1021                                                 this.state = tstates.done;
1022                                         }
1023                                 }
1024                         } else if ( state === tstates.snapback ) {
1025                                 if ( elapsed >= duration ) {
1026                                         this.pos = this.toPos;
1027                                         this.state = tstates.done;
1028                                 } else {
1029                                         this.pos = this.fromPos + (( this.toPos - this.fromPos ) *
1030                                                 $.easing[this.easing]( elapsed / duration,
1031                                                         elapsed, 0, 1, duration ));
1032                                 }
1033                         }
1034
1035                         return this.pos;
1036                 },
1037
1038                 done: function () {
1039                         return this.state === tstates.done;
1040                 },
1041
1042                 getPosition: function () {
1043                         return this.pos;
1044                 }
1045         });
1046
1047         $( document ).bind( 'pagecreate create', function ( e ) {
1048                 var $page = $( e.target ),
1049                         content_scroll = $page.find(".ui-content").jqmData("scroll");
1050
1051                 /* content scroll */
1052                 if ( $.support.scrollview === undefined ) {
1053                         $.support.scrollview = true;
1054                 }
1055
1056                 if ( $.support.scrollview === true && content_scroll === undefined ) {
1057                         content_scroll = "y";
1058                 }
1059
1060                 if ( content_scroll !== "y" ) {
1061                         content_scroll = "none";
1062                 }
1063
1064                 $page.find(".ui-content").attr( "data-scroll", content_scroll );
1065
1066                 $page.find(":jqmData(scroll):not(.ui-scrollview-clip)").each( function () {
1067                         if ( $( this ).hasClass("ui-scrolllistview") ) {
1068                                 $( this ).scrolllistview();
1069                         } else {
1070                                 var st = $( this ).jqmData("scroll"),
1071                                         dir = st && ( st.search(/^[xy]/) !== -1 ) ? st.charAt(0) : null,
1072                                         opts;
1073
1074                                 if ( st === "none" ) {
1075                                         return;
1076                                 }
1077
1078                                 opts = {
1079                                         direction: dir || undefined,
1080                                         scrollMethod: $( this ).jqmData("scroll-method") || undefined,
1081                                         scrollJump: $( this ).jqmData("scroll-jump") || undefined
1082                                 };
1083
1084                                 $( this ).scrollview( opts );
1085                         }
1086                 });
1087         });
1088
1089         $( document ).bind( 'pageshow', function ( e ) {
1090                 var $page = $( e.target ),
1091                         scroll = $page.find(".ui-content").jqmData("scroll");
1092
1093                 if ( scroll === "y" ) {
1094                         resizePageContentHeight( e.target );
1095                 }
1096         });
1097
1098 }( jQuery, window, document ) );