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