Tizen 2.0 Release
[platform/framework/web/web-ui-fw.git] / src / widgets / common / js / jquery.mobile.tizen.scrollview.js
index 309dc08..30931d2 100644 (file)
 * Modified by Minkyu Kang <mk7.kang@samsung.com>
 */
 
-( function ( $, window, document, undefined ) {
-
-jQuery.widget( "tizen.scrollview", jQuery.mobile.widget, {
-       options: {
-               fps:               60,    // Frames per second in msecs.
-               direction:         null,  // "x", "y", or null for both.
-
-               scrollDuration:    2000,  // Duration of the scrolling animation in msecs.
-               overshootDuration: 250,   // Duration of the overshoot animation in msecs.
-               snapbackDuration:  500,   // Duration of the snapback animation in msecs.
+(function ( $, window, document, undefined ) {
+
+       function resizePageContentHeight( page ) {
+               var $page = $( page ),
+                       $content = $page.children(".ui-content"),
+                       hh = $page.children(".ui-header").outerHeight() || 0,
+                       fh = $page.children(".ui-footer").outerHeight() || 0,
+                       pt = parseFloat( $content.css("padding-top") ),
+                       pb = parseFloat( $content.css("padding-bottom") ),
+                       wh = $( window ).height();
+
+               $content.height( wh - (hh + fh) - (pt + pb) );
+       }
 
-               moveThreshold:     50,   // User must move this many pixels in any direction to trigger a scroll.
-               moveIntervalThreshold:     150,   // Time between mousemoves must not exceed this threshold.
+       function MomentumTracker( options ) {
+               this.options = $.extend( {}, options );
+               this.easing = "easeOutQuad";
+               this.reset();
+       }
 
-               scrollMethod:      "translate",  // "translate", "position", "scroll"
-               startEventName:    "scrollstart",
-               updateEventName:   "scrollupdate",
-               stopEventName:     "scrollstop",
+       var tstates = {
+               scrolling: 0,
+               overshot:  1,
+               snapback:  2,
+               done:      3
+       };
 
-               eventType:         $.support.touch ? "touch" : "mouse",
+       function getCurrentTime() {
+               return Date.now();
+       }
 
-               showScrollBars:    true,
+       jQuery.widget( "tizen.scrollview", jQuery.mobile.widget, {
+               options: {
+                       direction:         null,  // "x", "y", or null for both.
 
-               pagingEnabled:     false,
-               overshootEnable:   false,
+                       timerInterval:     10,
+                       scrollDuration:    1000,  // Duration of the scrolling animation in msecs.
+                       overshootDuration: 250,   // Duration of the overshoot animation in msecs.
+                       snapbackDuration:  500,   // Duration of the snapback animation in msecs.
 
-               delayedClickSelector: "a,input,textarea,select,button,.ui-btn",
-               delayedClickEnabled: false
-       },
+                       moveThreshold:     30,   // User must move this many pixels in any direction to trigger a scroll.
+                       moveIntervalThreshold:     150,   // Time between mousemoves must not exceed this threshold.
 
-       _makePositioned: function ( $ele ) {
-               if ( $ele.css("position") === "static" ) {
-                       $ele.css( "position", "relative" );
-               }
-       },
+                       scrollMethod:      "translate",  // "translate", "position"
+                       startEventName:    "scrollstart",
+                       updateEventName:   "scrollupdate",
+                       stopEventName:     "scrollstop",
 
-       _create: function () {
-               var $page = $('.ui-page');
-               this._$content = $page.children('.ui-content');
+                       eventType:         $.support.touch ? "touch" : "mouse",
 
-               this._$clip = $( this.element ).addClass("ui-scrollview-clip");
+                       showScrollBars:    true,
+                       overshootEnable:   false,
+                       outerScrollEnable: true,
+                       overflowEnable:    true,
+                       scrollJump:        false,
+               },
 
-               var $child = this._$clip.children();
+               _getViewHeight: function () {
+                       return this._$view.height();
+               },
 
-               if ( $child.length > 1 ) {
-                       $child = this._$clip.wrapInner("<div></div>").children();
-               }
+               _makePositioned: function ( $ele ) {
+                       if ( $ele.css("position") === "static" ) {
+                               $ele.css( "position", "relative" );
+                       }
+               },
 
-               this._$view = $child.addClass("ui-scrollview-view");
+               _create: function () {
+                       var direction,
+                               self = this;
 
-               this._$clip.css( "overflow",
-                       this.options.scrollMethod === "scroll" ? "scroll" : "hidden" );
+                       this._$clip = $( this.element ).addClass("ui-scrollview-clip");
 
-               this._makePositioned( this._$clip );
+                       if ( this._$clip.children(".ui-scrollview-view").length ) {
+                               this._$view = this._$clip.children(".ui-scrollview-view");
+                       } else {
+                               this._$view = this._$clip.wrapInner("<div></div>").children()
+                                                       .addClass("ui-scrollview-view");
+                       }
 
-               /*
-                * Turn off our faux scrollbars if we are using native scrolling
-                * to position the view.
-                */
-               if ( this.options.scrollMethod === "scroll" ) {
-                       this.options.showScrollBars = false;
-               }
+                       if ( this.options.scrollMethod === "translate" ) {
+                               if ( this._$view.css("transform") === undefined ) {
+                                       this.options.scrollMethod = "position";
+                               }
+                       }
 
-               /*
-                * We really don't need this if we are using a translate transformation
-                * for scrolling. We set it just in case the user wants to switch methods
-                * on the fly.
-                */
-               this._makePositioned( this._$view );
-               this._$view.css({ left: 0, top: 0 });
+                       this._$clip.css( "overflow", "hidden" );
+                       this._makePositioned( this._$clip );
 
-               this._sx = 0;
-               this._sy = 0;
+                       this._makePositioned( this._$view );
+                       this._$view.css( { left: 0, top: 0 } );
 
-               var direction = this.options.direction;
+                       this._view_height = this._getViewHeight();
 
-               this._hTracker = ( direction !== "y" ) ?
-                       new MomentumTracker( this.options ) : null;
-               this._vTracker = ( direction !== "x" ) ?
-                       new MomentumTracker( this.options ) : null;
+                       this._sx = 0;
+                       this._sy = 0;
 
-               this._timerInterval = 1000 / this.options.fps;
-               this._timerID = 0;
+                       direction = this.options.direction;
 
-               var self = this;
-               this._timerCB = function () {
-                       self._handleMomentumScroll();
-               };
+                       this._hTracker = ( direction !== "y" ) ?
+                                       new MomentumTracker( this.options ) : null;
+                       this._vTracker = ( direction !== "x" ) ?
+                                       new MomentumTracker( this.options ) : null;
 
-               this._addBehaviors();
-       },
+                       this._timerInterval = this.options.timerInterval;
+                       this._timerID = 0;
 
-       _startMScroll: function ( speedX, speedY ) {
-               this._stopMScroll();
-               this._showScrollBars();
+                       this._timerCB = function () {
+                               self._handleMomentumScroll();
+                       };
 
-               var keepGoing = false;
-               var duration = this.options.scrollDuration;
+                       this._add_event();
+                       this._add_scrollbar();
+                       this._add_scroll_jump();
+                       this._add_overflow_indicator();
+               },
+
+               _startMScroll: function ( speedX, speedY ) {
+                       var keepGoing = false,
+                               duration = this.options.scrollDuration,
+                               ht = this._hTracker,
+                               vt = this._vTracker,
+                               c,
+                               v;
+
+                       this._$clip.trigger( this.options.startEventName );
+
+                       if ( ht ) {
+                               c = this._$clip.width();
+                               v = this._$view.width();
+
+                               if ( (( this._sx === 0 && speedX > 0 ) ||
+                                       ( this._sx === -(v - c) && speedX < 0 )) &&
+                                               v > c ) {
+                                       return;
+                               }
 
-               this._$clip.trigger( this.options.startEventName );
-               $( document ).trigger("scrollview_scroll");
+                               ht.start( this._sx, speedX,
+                                       duration, (v > c) ? -(v - c) : 0, 0 );
+                               keepGoing = !ht.done();
+                       }
 
-               var ht = this._hTracker;
-               if ( ht ) {
-                       var c = this._$clip.width();
-                       var v = this._$view.width();
-                       ht.start( this._sx, speedX,
-                               duration, (v > c) ? -(v - c) : 0, 0 );
-                       keepGoing = !ht.done();
-               }
+                       if ( vt ) {
+                               c = this._$clip.height();
+                               v = this._getViewHeight();
 
-               var vt = this._vTracker;
-               if ( vt ) {
-                       var c = this._$clip.height();
-                       var v = this._$view.height() +
-                               parseFloat( this._$view.css("padding-top") );
+                               if ( (( this._sy === 0 && speedY > 0 ) ||
+                                       ( this._sy === -(v - c) && speedY < 0 )) &&
+                                               v > c ) {
+                                       return;
+                               }
 
-                       vt.start( this._sy, speedY,
-                               duration, (v > c) ? -(v - c) : 0, 0 );
-                       keepGoing = keepGoing || !vt.done();
-               }
+                               vt.start( this._sy, speedY,
+                                       duration, (v > c) ? -(v - c) : 0, 0 );
+                               keepGoing = keepGoing || !vt.done();
+                       }
 
-               if ( keepGoing ) {
-                       this._timerID = setTimeout( this._timerCB, this._timerInterval );
-               } else {
-                       this._stopMScroll();
-               }
-       },
+                       if ( keepGoing ) {
+                               this._timerID = setTimeout( this._timerCB, this._timerInterval );
+                       } else {
+                               this._stopMScroll();
+                       }
+               },
 
-       _stopMScroll: function () {
-               if ( this._timerID ) {
-                       this._$clip.trigger( this.options.stopEventName );
-                       clearTimeout( this._timerID );
-               }
-               this._timerID = 0;
+               _stopMScroll: function () {
+                       if ( this._timerID ) {
+                               this._$clip.trigger( this.options.stopEventName );
+                               clearTimeout( this._timerID );
+                       }
+                       this._timerID = 0;
 
-               if ( this._vTracker ) {
-                       this._vTracker.reset();
-               }
+                       if ( this._vTracker ) {
+                               this._vTracker.reset();
+                       }
 
-               if ( this._hTracker ) {
-                       this._hTracker.reset();
-               }
+                       if ( this._hTracker ) {
+                               this._hTracker.reset();
+                       }
 
-               this._hideScrollBars();
-       },
+                       this._hideScrollBars();
+                       this._hideOverflowIndicator();
+               },
+
+               _handleMomentumScroll: function () {
+                       var keepGoing = false,
+                               x = 0,
+                               y = 0,
+                               scroll_height = 0,
+                               self = this,
+                               end_effect = function ( dir ) {
+                                       setTimeout( function () {
+                                               self._effect_dir = dir;
+                                               self._setEndEffect( "in" );
+                                       }, 100 );
+
+                                       setTimeout( function () {
+                                               self._setEndEffect( "out" );
+                                       }, 350 );
+                               },
+                               vt = this._vTracker,
+                               ht = this._hTracker;
+
+                       if ( this._outerScrolling ) {
+                               return;
+                       }
 
-       _handleMomentumScroll: function () {
-               var keepGoing = false;
-               var v = this._$view;
+                       if ( vt ) {
+                               vt.update( this.options.overshootEnable );
+                               y = vt.getPosition();
+                               keepGoing = !vt.done();
+
+                               if ( vt.getRemained() > this.options.overshootDuration ) {
+                                       scroll_height = this._getViewHeight() - this._$clip.height();
+
+                                       if ( !vt.isAvail() ) {
+                                               if ( this._speedY > 0 ) {
+                                                       this._outerScroll( vt.getRemained() / 3, scroll_height );
+                                               } else {
+                                                       this._outerScroll( y - vt.getRemained() / 3, scroll_height );
+                                               }
+                                       } else if ( vt.isMin() ) {
+                                               this._outerScroll( y - vt.getRemained() / 3, scroll_height );
+
+                                               if ( scroll_height > 0 ) {
+                                                       end_effect( 1 );
+                                               }
+                                       } else if ( vt.isMax() ) {
+                                               this._outerScroll( vt.getRemained() / 3, scroll_height );
+
+                                               if ( scroll_height > 0 ) {
+                                                       end_effect( 0 );
+                                               }
+                                       }
+                               }
+                       }
 
-               var x = 0, y = 0;
+                       if ( ht ) {
+                               ht.update( this.options.overshootEnable );
+                               x = ht.getPosition();
+                               keepGoing = keepGoing || !ht.done();
+                       }
 
-               var vt = this._vTracker;
-               if ( vt ) {
-                       vt.update( this.options.overshootEnable );
-                       y = vt.getPosition();
-                       keepGoing = !vt.done();
-               }
+                       this._setScrollPosition( x, y );
+                       this._$clip.trigger( this.options.updateEventName,
+                                       [ { x: x, y: y } ] );
 
-               var ht = this._hTracker;
-               if ( ht ) {
-                       ht.update( this.options.overshootEnable );
-                       x = ht.getPosition();
-                       keepGoing = keepGoing || !ht.done();
-               }
+                       if ( keepGoing ) {
+                               this._timerID = setTimeout( this._timerCB, this._timerInterval );
+                       } else {
+                               this._stopMScroll();
+                       }
+               },
 
-               this._setScrollPosition( x, y );
-               this._$clip.trigger( this.options.updateEventName,
-                               [ { x: x, y: y } ] );
+               _setElementTransform: function ( $ele, x, y, duration ) {
+                       var translate,
+                               transition;
 
-               if ( keepGoing ) {
-                       this._timerID = setTimeout( this._timerCB, this._timerInterval );
-               } else {
-                       this._stopMScroll();
-               }
-       },
+                       if ( !duration || duration === undefined ) {
+                               transition = "none";
+                       } else {
+                               transition =  "-webkit-transform " + duration / 1000 + "s ease-out";
+                       }
 
-       _setCalibration: function ( x, y ) {
-               if ( this.options.overshootEnable ) {
-                       this._sx = x;
-                       this._sy = y;
-                       return;
-               }
+                       if ( $.support.cssTransform3d ) {
+                               translate = "translate3d(" + x + "," + y + ", 0px)";
+                       } else {
+                               translate = "translate(" + x + "," + y + ")";
+                       }
 
-               var v = this._$view;
-               var c = this._$clip;
-               var dirLock = this._directionLock;
+                       $ele.css({
+                               "-moz-transform": translate,
+                               "-webkit-transform": translate,
+                               "-ms-transform": translate,
+                               "-o-transform": translate,
+                               "transform": translate,
+                               "-webkit-transition": transition
+                       });
+               },
+
+               _setEndEffect: function ( dir ) {
+                       var scroll_height = this._getViewHeight() - this._$clip.height();
+
+                       if ( this._softkeyboard ) {
+                               if ( this._effect_dir ) {
+                                       this._outerScroll( -scroll_height - this._softkeyboardHeight,
+                                                       scroll_height );
+                               } else {
+                                       this._outerScroll( this._softkeyboardHeight, scroll_height );
+                               }
+                               return;
+                       }
 
-               if ( dirLock !== "y" && this._hTracker ) {
-                       this._sx = x;
-               }
+                       if ( dir === "in" ) {
+                               if ( this._endEffect ) {
+                                       return;
+                               }
 
-               if ( dirLock !== "x" && this._vTracker ) {
-                       var scroll_height = v.height() - c.height() +
-                               parseFloat( c.css("padding-top") ) +
-                               parseFloat( c.css("padding-bottom") );
+                               this._endEffect = true;
+                               this._setOverflowIndicator( this._effect_dir );
+                               this._showOverflowIndicator();
+                       } else if ( dir === "out" ) {
+                               if ( !this._endEffect ) {
+                                       return;
+                               }
 
-                       if ( y >= 0 ) {
-                               this._sy = 0;
-                       } else if ( y < -scroll_height ) {
-                               this._sy = -scroll_height;
+                               this._endEffect = false;
                        } else {
+                               this._endEffect = false;
+                               this._setOverflowIndicator();
+                               this._showOverflowIndicator();
+                       }
+               },
+
+               _setCalibration: function ( x, y ) {
+                       if ( this.options.overshootEnable ) {
+                               this._sx = x;
                                this._sy = y;
+                               return;
                        }
 
-                       if ( scroll_height < 0 ) {
-                               this._sy = 0;
+                       var $v = this._$view,
+                               $c = this._$clip,
+                               dirLock = this._directionLock,
+                               scroll_height = 0,
+                               scroll_width = 0;
+
+                       if ( dirLock !== "y" && this._hTracker ) {
+                               scroll_width = $v.width() - $c.width();
+
+                               if ( x >= 0 ) {
+                                       this._sx = 0;
+                               } else if ( x < -scroll_width ) {
+                                       this._sx = -scroll_width;
+                               } else {
+                                       this._sx = x;
+                               }
+
+                               if ( scroll_width < 0 ) {
+                                       this._sx = 0;
+                               }
                        }
-               }
-       },
 
-       _setScrollPosition: function ( x, y, duration ) {
-               this._setCalibration( x, y );
+                       if ( dirLock !== "x" && this._vTracker ) {
+                               scroll_height = this._getViewHeight() - $c.height();
 
-               x = this._sx;
-               y = this._sy;
+                               if ( y > 0 ) {
+                                       this._sy = 0;
 
-               var $v = this._$view;
+                                       if ( this._didDrag && scroll_height > 0 ) {
+                                               this._effect_dir = 0;
+                                               this._setEndEffect( "in" );
+                                       }
+                               } else if ( y < -scroll_height ) {
+                                       this._sy = -scroll_height;
 
-               var sm = this.options.scrollMethod;
+                                       if ( this._didDrag && scroll_height > 0 ) {
+                                               this._effect_dir = 1;
+                                               this._setEndEffect( "in" );
+                                       }
+                               } else {
+                                       if ( this._endEffect && this._sy !== y ) {
+                                               this._setEndEffect();
+                                       }
 
-               switch ( sm ) {
-               case "translate":
-                       setElementTransform( $v, x + "px", y + "px", duration );
-                       break;
+                                       this._sy = y;
+                               }
 
-               case "position":
-                       $v.css({left: x + "px", top: y + "px"});
-                       break;
+                               if ( scroll_height < 0 ) {
+                                       this._sy = 0;
+                               }
+                       }
+               },
 
-               case "scroll":
-                       var c = this._$clip[0];
-                       c.scrollLeft = -x;
-                       c.scrollTop = -y;
-                       break;
-               }
+               _setScrollPosition: function ( x, y, duration ) {
+                       var $v = this._$view,
+                               sm = this.options.scrollMethod,
+                               $vsb = this._$vScrollBar,
+                               $hsb = this._$hScrollBar,
+                               $sbt;
 
-               var $vsb = this._$vScrollBar;
-               var $hsb = this._$hScrollBar;
+                       this._setCalibration( x, y );
 
-               if ( $vsb ) {
-                       var $sbt = $vsb.find(".ui-scrollbar-thumb");
+                       x = this._sx;
+                       y = this._sy;
 
                        if ( sm === "translate" ) {
-                               setElementTransform( $sbt, "0px",
-                                       -y / $v.height() * $sbt.parent().height() + "px",
-                                       duration );
+                               this._setElementTransform( $v, x + "px", y + "px", duration );
                        } else {
-                               $sbt.css( "top", -y / $v.height() * 100 + "%" );
+                               $v.css( {left: x + "px", top: y + "px"} );
                        }
-               }
 
-               if ( $hsb ) {
-                       var $sbt = $hsb.find(".ui-scrollbar-thumb");
+                       if ( $vsb ) {
+                               $sbt = $vsb.find(".ui-scrollbar-thumb");
 
-                       if ( sm === "translate" ) {
-                               setElementTransform( $sbt,
-                                       -x / $v.width() * $sbt.parent().width() + "px", "0px",
-                                       duration);
+                               if ( sm === "translate" ) {
+                                       this._setElementTransform( $sbt, "0px",
+                                               -y / this._getViewHeight() * $sbt.parent().height() + "px",
+                                               duration );
+                               } else {
+                                       $sbt.css( "top", -y / this._getViewHeight() * 100 + "%" );
+                               }
+                       }
+
+                       if ( $hsb ) {
+                               $sbt = $hsb.find(".ui-scrollbar-thumb");
+
+                               if ( sm === "translate" ) {
+                                       this._setElementTransform( $sbt,
+                                               -x / $v.width() * $sbt.parent().width() + "px", "0px",
+                                               duration);
+                               } else {
+                                       $sbt.css("left", -x / $v.width() * 100 + "%");
+                               }
+                       }
+               },
+
+               _outerScroll: function ( y, scroll_height ) {
+                       var self = this,
+                               top = $( window ).scrollTop() - window.screenTop,
+                               sy = 0,
+                               duration = this.options.snapbackDuration,
+                               start = getCurrentTime(),
+                               tfunc;
+
+                       if ( !this.options.outerScrollEnable ) {
+                               return;
+                       }
+
+                       if ( this._$clip.jqmData("scroll") !== "y" ) {
+                               return;
+                       }
+
+                       if ( this._outerScrolling ) {
+                               return;
+                       }
+
+                       if ( y > 0 ) {
+                               sy = ( window.screenTop ? window.screenTop : -y );
+                       } else if ( y < -scroll_height ) {
+                               sy = -y - scroll_height;
                        } else {
-                               $sbt.css("left", -x/$v.width() * 100 + "%");
+                               return;
                        }
-               }
-       },
-
-       scrollTo: function ( x, y, duration ) {
-               this._stopMScroll();
-               var sm = this.options.scrollMethod;
-
-               /*
-                * currently support only animation for translate
-                * Don't want to use setTimeout algorithm for animation.
-                */
-               if ( !duration || (duration && sm === "translate") ) {
-                       return this._setScrollPosition( x, y, duration );
-               }
 
-               // follow jqm default animation when the scrollmethod is not translate. 
+                       tfunc = function () {
+                               var elapsed = getCurrentTime() - start;
 
-               x = -x;
-               y = -y;
+                               if ( elapsed >= duration ) {
+                                       window.scrollTo( 0, top + sy );
+                                       self._outerScrolling = undefined;
 
-               var self = this;
-               var start = getCurrentTime();
-               var efunc = $.easing["easeOutQuad"];
-               var sx = this._sx;
-               var sy = this._sy;
-               var dx = x - sx;
-               var dy = y - sy;
+                                       self._stopMScroll();
+                               } else {
+                                       ec = $.easing.easeOutQuad( elapsed / duration,
+                                                       elapsed, 0, 1, duration );
 
-               var tfunc = function() {
-                       var elapsed = getCurrentTime() - start;
+                                       window.scrollTo( 0, top + ( sy * ec ) );
+                                       self._outerScrolling = setTimeout( tfunc, self._timerInterval );
+                               }
+                       };
+                       this._outerScrolling = setTimeout( tfunc, self._timerInterval );
+               },
+
+               _scrollTo: function ( x, y, duration ) {
+                       var self = this,
+                               start = getCurrentTime(),
+                               efunc = $.easing.easeOutQuad,
+                               sx = this._sx,
+                               sy = this._sy,
+                               dx = x - sx,
+                               dy = y - sy,
+                               tfunc;
+
+                       x = -x;
+                       y = -y;
+
+                       tfunc = function () {
+                               var elapsed = getCurrentTime() - start,
+                                   ec;
+
+                               if ( elapsed >= duration ) {
+                                       self._timerID = 0;
+                                       self._setScrollPosition( x, y );
+                               } else {
+                                       ec = efunc( elapsed / duration, elapsed, 0, 1, duration );
 
-                       if ( elapsed >= duration ) {
-                               self._timerID = 0;
-                               self._setScrollPosition( x, y );
+                                       self._setScrollPosition( sx + ( dx * ec ), sy + ( dy * ec ) );
+                                       self._timerID = setTimeout( tfunc, self._timerInterval );
+                               }
+                       };
+
+                       this._timerID = setTimeout( tfunc, this._timerInterval );
+               },
+
+               scrollTo: function ( x, y, duration ) {
+                       this._stopMScroll();
+                       this._didDrag = false;
+
+                       if ( !duration || this.options.scrollMethod === "translate" ) {
+                               this._setScrollPosition( x, y, duration );
                        } else {
-                               var ec = efunc( elapsed / duration, elapsed, 0, 1, duration );
+                               this._scrollTo( x, y, duration );
+                       }
+               },
+
+               getScrollPosition: function () {
+                       return { x: -this._sx, y: -this._sy };
+               },
+
+               skipDragging: function ( value ) {
+                       this._skip_dragging = value;
+               },
+
+               _getScrollHierarchy: function () {
+                       var svh = [],
+                               d;
 
-                               self._setScrollPosition( sx + (dx * ec), sy + (dy * ec) );
-                               self._timerID = setTimeout( tfunc, self._timerInterval );
+                       this._$clip.parents( ".ui-scrollview-clip").each( function () {
+                               d = $( this ).jqmData("scrollview");
+                               if ( d ) {
+                                       svh.unshift( d );
+                               }
+                       } );
+                       return svh;
+               },
+
+               _getAncestorByDirection: function ( dir ) {
+                       var svh = this._getScrollHierarchy(),
+                               n = svh.length,
+                               sv,
+                               svdir;
+
+                       while ( 0 < n-- ) {
+                               sv = svh[n];
+                               svdir = sv.options.direction;
+
+                               if (!svdir || svdir === dir) {
+                                       return sv;
+                               }
                        }
-               };
+                       return null;
+               },
 
-               this._timerID = setTimeout( tfunc, this._timerInterval );
-       },
+               _handleDragStart: function ( e, ex, ey ) {
+                       this._stopMScroll();
+
+                       this._didDrag = false;
+                       this._skip_dragging = false;
+
+                       var target = $( e.target ),
+                               self = this,
+                               $c = this._$clip,
+                               svdir = this.options.direction;
 
-       getScrollPosition: function () {
-               return { x: -this._sx, y: -this._sy };
-       },
+                       /* should prevent the default behavior when click the button */
+                       this._is_button = target.is( '.ui-btn' ) ||
+                                       target.is( '.ui-btn-text' ) ||
+                                       target.is( '.ui-btn-inner' ) ||
+                                       target.is( '.ui-btn-inner .ui-icon' );
 
-       _getScrollHierarchy: function () {
-               var svh = [];
-               this._$clip.parents(".ui-scrollview-clip").each( function () {
-                       var d = $(this).jqmData("scrollview");
-                       if ( d ) {
-                               svh.unshift( d );
+                       /* should prevent the default behavior when click the slider */
+                       if ( target.parents('.ui-slider').length || target.is('.ui-slider') ) {
+                               this._skip_dragging = true;
+                               return;
                        }
-               } );
-               return svh;
-       },
 
-       _getAncestorByDirection: function ( dir ) {
-               var svh = this._getScrollHierarchy();
-               var n = svh.length;
+                       if ( target.is('textarea') ) {
+                               target.bind( "scroll", function () {
+                                       self._skip_dragging = true;
+                                       target.unbind("scroll");
+                               });
+                       }
 
-               while ( 0 < n-- ) {
-                       var sv = svh[n];
-                       var svdir = sv.options.direction;
+                       /*
+                        * We need to prevent the default behavior to
+                        * suppress accidental selection of text, etc.
+                        */
+                       this._is_inputbox = target.is(':input') ||
+                                       target.parents(':input').length > 0;
+
+                       if ( this._is_inputbox ) {
+                               target.one( "resize.scrollview", function () {
+                                       if ( ey > $c.height() ) {
+                                               self.scrollTo( -ex, self._sy - ey + $c.height(),
+                                                       self.options.snapbackDuration );
+                                       }
+                               });
+                       }
 
-                       if (!svdir || svdir === dir) {
-                               return sv;
+                       if ( this.options.eventType === "mouse" && !this._is_inputbox && !this._is_button ) {
+                               e.preventDefault();
                        }
-               }
-               return null;
-       },
-
-       _handleDragStart: function ( e, ex, ey ) {
-               // Stop any scrolling of elements in our parent hierarcy.
-               $.each( this._getScrollHierarchy(),
-                       function (i, sv) {
-                               sv._stopMScroll();
-                       } );
-               this._stopMScroll();
 
-               this._didDrag = false;
-               var target = $( e.target );
+                       this._lastX = ex;
+                       this._lastY = ey;
+                       this._startY = ey;
+                       this._doSnapBackX = false;
+                       this._doSnapBackY = false;
+                       this._speedX = 0;
+                       this._speedY = 0;
+                       this._directionLock = "";
 
-               // should skip the dragging when click the button
-               this._skip_dragging = target.is('.ui-btn-text') ||
-                               target.is('.ui-btn-inner');
+                       this._lastMove = 0;
+                       this._enableTracking();
 
-               if ( this._skip_dragging ) {
-                       return;
-               }
+                       this._set_scrollbar_size();
+               },
 
-               /*
-                * If we're using mouse events, we need to prevent the default
-                * behavior to suppress accidental selection of text, etc. We
-                * can't do this on touch devices because it will disable the
-                * generation of "click" events.
-                */
+               _propagateDragMove: function ( sv, e, ex, ey, dir ) {
+                       this._hideScrollBars();
+                       this._hideOverflowIndicator();
+                       this._disableTracking();
+                       sv._handleDragStart( e, ex, ey );
+                       sv._directionLock = dir;
+                       sv._didDrag = this._didDrag;
+               },
+
+               _handleDragMove: function ( e, ex, ey ) {
+                       if ( this._skip_dragging ) {
+                               return;
+                       }
 
-               var shouldBlockEvent = 1;
+                       if ( !this._dragging ) {
+                               return;
+                       }
 
-               if ( this.options.eventType === "mouse" ) {
-                       shouldBlockEvent = !( target.is(':input') ||
-                               target.parents(':input').length > 0 );
-               } else if ( this.options.eventType === "touch" ) {
-                       shouldBlockEvent = !( target.is(':input') ||
-                               target.parents(':input').length > 0 );
+                       if ( !this._is_inputbox && !this._is_button ) {
+                               e.preventDefault();
+                       }
 
-               }
+                       var mt = this.options.moveThreshold,
+                               dx = ex - this._lastX,
+                               dy = ey - this._lastY,
+                               svdir = this.options.direction,
+                               dir = null,
+                               x,
+                               y,
+                               sv,
+                               scope,
+                               newX,
+                               newY,
+                               dirLock;
+
+                       this._lastMove = getCurrentTime();
+
+                       if ( !this._directionLock ) {
+                               x = Math.abs( dx );
+                               y = Math.abs( dy );
+
+                               if ( x < mt && y < mt ) {
+                                       return false;
+                               }
 
-               if ( shouldBlockEvent ) {
-                       e.preventDefault();
-               }
+                               if ( x < y && (x / y) < 0.5 ) {
+                                       dir = "y";
+                               } else if ( x > y && (y / x) < 0.5 ) {
+                                       dir = "x";
+                               }
 
-               var c = this._$clip;
-               var v = this._$view;
+                               if ( svdir && dir && svdir !== dir ) {
+                                       /*
+                                        * This scrollview can't handle the direction the user
+                                        * is attempting to scroll. Find an ancestor scrollview
+                                        * that can handle the request.
+                                        */
+
+                                       sv = this._getAncestorByDirection( dir );
+                                       if ( sv ) {
+                                               this._propagateDragMove( sv, e, ex, ey, dir );
+                                               return false;
+                                       }
+                               }
 
-               if ( this.options.delayedClickEnabled ) {
-                       this._$clickEle =
-                               target.closest( this.options.delayedClickSelector );
-               }
+                               this._directionLock = svdir || (dir || "none");
+                       }
 
-               this._lastX = ex;
-               this._lastY = ey;
-               this._startY = ey;
-               this._doSnapBackX = false;
-               this._doSnapBackY = false;
-               this._speedX = 0;
-               this._speedY = 0;
+                       newX = this._sx;
+                       newY = this._sy;
+                       dirLock = this._directionLock;
 
-               this._directionLock = "";
+                       if ( dirLock !== "y" && this._hTracker ) {
+                               x = this._sx;
+                               this._speedX = dx;
+                               newX = x + dx;
 
-               var cw = 0,
-                   vw = 0,
-                   ch = 0,
-                   vh = 0;
+                               this._doSnapBackX = false;
 
-               if ( this._hTracker ) {
-                       cw = parseInt( c.css("width"), 10 );
-                       vw = parseInt( v.css("width"), 10 );
-                       this._maxX = cw - vw;
+                               scope = ( newX > 0 || newX < this._maxX );
 
-                       if ( this._maxX > 0 ) {
-                               this._maxX = 0;
-                       }
-                       if ( this._$hScrollBar ) {
-                               var thumb = this._$hScrollBar.find(".ui-scrollbar-thumb");
-                               thumb.css( "width", (cw >= vw ?
-                                       "100%" : Math.floor(cw / vw * 100) + "%") );
+                               if ( scope && dirLock === "x" ) {
+                                       sv = this._getAncestorByDirection("x");
+                                       if ( sv ) {
+                                               this._setScrollPosition( newX > 0 ?
+                                                               0 : this._maxX, newY );
+                                               this._propagateDragMove( sv, e, ex, ey, dir );
+                                               return false;
+                                       }
+
+                                       newX = x + ( dx / 2 );
+                                       this._doSnapBackX = true;
+                               }
                        }
-               }
 
-               if ( this._vTracker ) {
-                       ch = parseInt( c.css("height"), 10 );
-                       vh = parseInt( v.css("height"), 10 ) +
-                               parseFloat( v.css("padding-top") );
-                       this._maxY = ch - vh;
+                       if ( dirLock !== "x" && this._vTracker ) {
+                               if ( Math.abs( this._startY - ey ) < mt && dirLock !== "xy" ) {
+                                       return;
+                               }
+
+                               y = this._sy;
+                               this._speedY = dy;
+                               newY = y + dy;
+
+                               this._doSnapBackY = false;
+
+                               scope = ( newY > 0 || newY < this._maxY );
 
-                       if ( this._maxY > 0 ) {
-                               this._maxY = 0;
+                               if ( scope && dirLock === "y" ) {
+                                       sv = this._getAncestorByDirection("y");
+                                       if ( sv ) {
+                                               this._setScrollPosition( newX,
+                                                               newY > 0 ? 0 : this._maxY );
+                                               this._propagateDragMove( sv, e, ex, ey, dir );
+                                               return false;
+                                       }
+
+                                       newY = y + ( dy / 2 );
+                                       this._doSnapBackY = true;
+                               }
                        }
-                       if ( this._$vScrollBar ) {
-                               var thumb = this._$vScrollBar.find(".ui-scrollbar-thumb");
-                               thumb.css( "height", (ch >= vh ?
-                                       "100%" : Math.floor(ch / vh * 100) + "%") );
+
+                       if ( this.options.overshootEnable === false ) {
+                               this._doSnapBackX = false;
+                               this._doSnapBackY = false;
                        }
-               }
 
-               var svdir = this.options.direction;
+                       this._lastX = ex;
+                       this._lastY = ey;
 
-               this._pageDelta = 0;
-               this._pageSize = 0;
-               this._pagePos = 0;
+                       this._setScrollPosition( newX, newY );
 
-               if ( this.options.pagingEnabled && (svdir === "x" || svdir === "y") ) {
-                       this._pageSize = (svdir === "x") ? cw : ch;
-                       this._pagePos = (svdir === "x") ? this._sx : this._sy;
-                       this._pagePos -= this._pagePos % this._pageSize;
-               }
+                       if ( this._didDrag === false ) {
+                               this._didDrag = true;
+                               this._showScrollBars();
+                               this._showOverflowIndicator();
 
-               this._lastMove = 0;
-               this._enableTracking();
-       },
-
-       _propagateDragMove: function ( sv, e, ex, ey, dir ) {
-               this._hideScrollBars();
-               this._disableTracking();
-               sv._handleDragStart( e,ex,ey );
-               sv._directionLock = dir;
-               sv._didDrag = this._didDrag;
-       },
-
-       _handleDragMove: function ( e, ex, ey ) {
-               if ( this._skip_dragging ) {
-                       return;
-               }
+                               this._$clip.parents(".ui-scrollview-clip").each( function () {
+                                       $( this ).scrollview( "skipDragging", true );
+                               } );
+                       }
+               },
 
-               if ( !this._dragging ) {
-                       return;
-               }
+               _handleDragStop: function ( e ) {
+                       var self = this;
+
+                       if ( this._skip_dragging ) {
+                               return;
+                       }
 
-               var mt = this.options.moveThreshold;
+                       var l = this._lastMove,
+                               t = getCurrentTime(),
+                               doScroll = (l && (t - l) <= this.options.moveIntervalThreshold),
+                               sx = ( this._hTracker && this._speedX && doScroll ) ?
+                                               this._speedX : ( this._doSnapBackX ? 1 : 0 ),
+                               sy = ( this._vTracker && this._speedY && doScroll ) ?
+                                               this._speedY : ( this._doSnapBackY ? 1 : 0 ),
+                               svdir = this.options.direction,
+                               x,
+                               y;
+
+                       if ( sx || sy ) {
+                               if ( !this._setGestureScroll( sx, sy ) ) {
+                                       this._startMScroll( sx, sy );
+                               }
+                       } else {
+                               this._hideScrollBars();
+                               this._hideOverflowIndicator();
+                       }
 
-               if ( Math.abs( this._startY - ey ) < mt && !this._didDrag ) {
-                       return;
-               }
+                       this._disableTracking();
 
-               this._lastMove = getCurrentTime();
+                       if ( this._endEffect ) {
+                               setTimeout( function () {
+                                       self._setEndEffect( "out" );
+                                       self._hideScrollBars();
+                                       self._hideOverflowIndicator();
+                               }, 300 );
+                       }
+
+                       return !this._didDrag;
+               },
+
+               _setGestureScroll: function ( sx, sy ) {
+                       var self = this,
+                               reset = function () {
+                                       clearTimeout( self._gesture_timer );
+                                       self._gesture_dir = 0;
+                                       self._gesture_count = 0;
+                                       self._gesture_timer = undefined;
+                               };
+
+                       if ( !sy ) {
+                               return false;
+                       }
 
-               var v = this._$view;
+                       dir = sy > 0 ? 1 : -1;
 
-               var dx = ex - this._lastX;
-               var dy = ey - this._lastY;
-               var svdir = this.options.direction;
-               var dir = null;
+                       if ( !this._gesture_timer ) {
+                               this._gesture_count = 1;
+                               this._gesture_dir = dir;
 
-               if ( !this._directionLock ) {
-                       var x = Math.abs( dx );
-                       var y = Math.abs( dy );
+                               this._gesture_timer = setTimeout( function () {
+                                       reset();
+                               }, 1000 );
 
-                       if ( x < mt && y < mt ) {
                                return false;
                        }
 
-                       if ( x < y && (x / y) < 0.5 ) {
-                               dir = "y";
-                       } else if ( x > y && (y / x) < 0.5 ) {
-                               dir = "x";
+                       if ( this._gesture_dir !== dir ) {
+                               reset();
+                               return false;
                        }
 
-                       if ( svdir && dir && svdir !== dir ) {
-                               /*
-                                * This scrollview can't handle the direction the user
-                                * is attempting to scroll. Find an ancestor scrollview
-                                * that can handle the request.
-                                */
+                       this._gesture_count++;
 
-                               var sv = this._getAncestorByDirection( dir );
-                               if ( sv ) {
-                                       this._propagateDragMove( sv, e, ex, ey, dir );
-                                       return false;
+                       if ( this._gesture_count === 3 ) {
+                               if ( dir > 0 ) {
+                                       this.scrollTo( 0, 0, this.options.overshootDuration );
+                               } else {
+                                       this.scrollTo( 0, -( this._getViewHeight() - this._$clip.height() ),
+                                                       this.options.overshootDuration );
                                }
-                       }
+                               reset();
 
-                       this._directionLock = svdir ? svdir : (dir ? dir : "none");
-               }
+                               return true;
+                       }
 
-               var newX = this._sx;
-               var newY = this._sy;
-               var dirLock = this._directionLock;
+                       return false;
+               },
 
-               if ( dirLock !== "y" && this._hTracker ) {
-                       var x = this._sx;
-                       this._speedX = dx;
-                       newX = x + dx;
+               _enableTracking: function () {
+                       this._dragging = true;
+               },
 
-                       // Simulate resistance.
+               _disableTracking: function () {
+                       this._dragging = false;
+               },
 
-                       this._doSnapBackX = false;
+               _showScrollBars: function ( interval ) {
+                       var vclass = "ui-scrollbar-visible",
+                               self = this;
 
-                       var scope = (newX > 0 || newX < this._maxX);
-                       if ( scope && dirLock === "x" ) {
-                               var sv = this._getAncestorByDirection("x");
-                               if ( sv ) {
-                                       this._setScrollPosition( newX > 0 ?
-                                                       0 : this._maxX, newY );
-                                       this._propagateDragMove( sv, e, ex, ey, dir );
-                                       return false;
-                               }
+                       if ( !this.options.showScrollBars ) {
+                               return;
+                       }
+                       if ( this._scrollbar_showed ) {
+                               return;
+                       }
 
-                               newX = x + (dx / 2);
-                               this._doSnapBackX = true;
+                       if ( this._$vScrollBar ) {
+                               this._$vScrollBar.addClass( vclass );
+                       }
+                       if ( this._$hScrollBar ) {
+                               this._$hScrollBar.addClass( vclass );
                        }
-               }
 
-               if ( dirLock !== "x" && this._vTracker ) {
-                       var y = this._sy;
-                       this._speedY = dy;
-                       newY = y + dy;
+                       this._scrollbar_showed = true;
 
-                       // Simulate resistance.
+                       if ( interval ) {
+                               setTimeout( function () {
+                                       self._hideScrollBars();
+                               }, interval );
+                       }
+               },
 
-                       this._doSnapBackY = false;
+               _hideScrollBars: function () {
+                       var vclass = "ui-scrollbar-visible";
 
-                       var scope = (newY > 0 || newY < this._maxY);
-                       if ( scope && dirLock === "y" ) {
-                               var sv = this._getAncestorByDirection("y");
-                               if ( sv ) {
-                                       this._setScrollPosition( newX,
-                                                       newY > 0 ? 0 : this._maxY );
-                                       this._propagateDragMove( sv, e, ex, ey, dir );
-                                       return false;
-                               }
+                       if ( !this.options.showScrollBars ) {
+                               return;
+                       }
+                       if ( !this._scrollbar_showed ) {
+                               return;
+                       }
 
-                               newY = y + (dy / 2);
-                               this._doSnapBackY = true;
+                       if ( this._$vScrollBar ) {
+                               this._$vScrollBar.removeClass( vclass );
+                       }
+                       if ( this._$hScrollBar ) {
+                               this._$hScrollBar.removeClass( vclass );
                        }
-               }
 
-               if ( this.options.overshootEnable === false ) {
-                       this._doSnapBackX = false;
-                       this._doSnapBackY = false;
-               }
+                       this._scrollbar_showed = false;
+               },
 
-               if ( this.options.pagingEnabled && (svdir === "x" || svdir === "y") ) {
-                       if ( this._doSnapBackX || this._doSnapBackY ) {
-                               this._pageDelta = 0;
+               _setOverflowIndicator: function ( dir ) {
+                       if ( dir === 1 ) {
+                               this._opacity_top = "0";
+                               this._opacity_bottom = "0.8";
+                       } else if ( dir === 0 ) {
+                               this._opacity_top = "0.8";
+                               this._opacity_bottom = "0";
                        } else {
-                               var opos = this._pagePos;
-                               var cpos = svdir === "x" ? newX : newY;
-                               var delta = svdir === "x" ? dx : dy;
-
-                               if ( opos > cpos && delta < 0 ) {
-                                       this._pageDelta = this._pageSize;
-                               } else if ( opos < cpos && delta > 0 ) {
-                                       this._pageDelta = -this._pageSize;
-                               } else {
-                                       this._pageDelta = 0;
-                               }
+                               this._opacity_top = "0.5";
+                               this._opacity_bottom = "0.5";
                        }
-               }
+               },
 
-               this._didDrag = true;
-               this._lastX = ex;
-               this._lastY = ey;
+               _showOverflowIndicator: function () {
+                       if ( !this.options.overflowEnable || !this._overflowAvail || this._softkeyboard ) {
+                               return;
+                       }
 
-               this._setScrollPosition( newX, newY );
+                       this._overflow_top.animate( { opacity: this._opacity_top }, 300 );
+                       this._overflow_bottom.animate( { opacity: this._opacity_bottom }, 300 );
 
-               this._showScrollBars();
+                       this._overflow_showed = true;
+               },
 
-               return;
-       },
+               _hideOverflowIndicator: function () {
+                       if ( !this.options.overflowEnable || !this._overflowAvail || this._softkeyboard ) {
+                               return;
+                       }
 
-       _handleDragStop: function ( e ) {
-               if ( this._skip_dragging ) {
-                       return;
-               }
+                       if ( this._overflow_showed === false ) {
+                               return;
+                       }
+
+                       this._overflow_top.animate( { opacity: 0 }, 300 );
+                       this._overflow_bottom.animate( { opacity: 0 }, 300 );
 
-               var l = this._lastMove;
-               var t = getCurrentTime();
-               var doScroll = (l && (t - l) <= this.options.moveIntervalThreshold);
+                       this._overflow_showed = false;
+                       this._setOverflowIndicator();
+               },
 
-               var sx = ( this._hTracker && this._speedX && doScroll ) ?
-                               this._speedX : ( this._doSnapBackX ? 1 : 0 );
-               var sy = ( this._vTracker && this._speedY && doScroll ) ?
-                       this._speedY : ( this._doSnapBackY ? 1 : 0 );
+               _add_event: function () {
+                       var self = this,
+                               $c = this._$clip,
+                               $v = this._$view;
 
-               var svdir = this.options.direction;
-               if ( this.options.pagingEnabled && (svdir === "x" || svdir === "y") &&
-                               !this._doSnapBackX && !this._doSnapBackY ) {
-                       var x = this._sx;
-                       var y = this._sy;
+                       if ( this.options.eventType === "mouse" ) {
+                               this._dragEvt = "mousedown mousemove mouseup click mousewheel";
 
-                       if ( svdir === "x" ) {
-                               x = -this._pagePos + this._pageDelta;
+                               this._dragCB = function ( e ) {
+                                       switch ( e.type ) {
+                                       case "mousedown":
+                                               return self._handleDragStart( e,
+                                                               e.clientX, e.clientY );
+
+                                       case "mousemove":
+                                               return self._handleDragMove( e,
+                                                               e.clientX, e.clientY );
+
+                                       case "mouseup":
+                                               return self._handleDragStop( e );
+
+                                       case "click":
+                                               return !self._didDrag;
+
+                                       case "mousewheel":
+                                               var old = self.getScrollPosition();
+                                               self.scrollTo( -old.x,
+                                                       -(old.y - e.originalEvent.wheelDelta) );
+                                               break;
+                                       }
+                               };
                        } else {
-                               y = -this._pagePos + this._pageDelta;
+                               this._dragEvt = "touchstart touchmove touchend click";
+
+                               this._dragCB = function ( e ) {
+                                       var touches = e.originalEvent.touches;
+
+                                       switch ( e.type ) {
+                                       case "touchstart":
+                                               if ( touches.length != 1) {
+                                                       return;
+                                               }
+
+                                               return self._handleDragStart( e,
+                                                               touches[0].pageX, touches[0].pageY );
+
+                                       case "touchmove":
+                                               if ( touches.length != 1) {
+                                                       return;
+                                               }
+
+                                               return self._handleDragMove( e,
+                                                               touches[0].pageX, touches[0].pageY );
+
+                                       case "touchend":
+                                               if ( touches.length != 0) {
+                                                       return;
+                                               }
+
+                                               return self._handleDragStop( e );
+
+                                       case "click":
+                                               return !self._didDrag;
+                                       }
+                               };
                        }
 
-                       this.scrollTo( x, y, this.options.snapbackDuration );
-               } else if ( sx || sy ) {
-                       this._startMScroll( sx, sy );
-               } else {
-                       this._hideScrollBars();
-               }
+                       $v.bind( this._dragEvt, this._dragCB );
 
-               this._disableTracking();
+                       $v.bind( "keydown", function ( e ) {
+                               var elem,
+                                       elem_top,
+                                       scroll_top = $( window ).scrollTop() - window.screenTop,
+                                       screen_h;
 
-               if ( !this._didDrag &&
-                               this.options.delayedClickEnabled &&
-                               this._$clickEle.length ) {
-                       this._$clickEle
-                               .trigger("mousedown")
-                               .trigger("mouseup")
-                               .trigger("click");
-               }
+                               if ( e.keyCode == 9 ) {
+                                       return false;
+                               }
 
-               /*
-                * If a view scrolled, then we need to absorb
-                * the event so that links etc, underneath our
-                * cursor/finger don't fire.
-                */
+                               elem = $c.find(".ui-focus");
 
-               return !this._didDrag;
-       },
+                               if ( elem === undefined ) {
+                                       return;
+                               }
 
-       _enableTracking: function () {
-               this._dragging = true;
-       },
+                               elem_top = elem.offset().top - scroll_top;
+                               screen_h = $c.offset().top + $c.height() - elem.height();
 
-       _disableTracking: function () {
-               this._dragging = false;
-       },
+                               if ( self._softkeyboard ) {
+                                       screen_h -= self._softkeyboardHeight;
+                               }
 
-       _showScrollBars: function () {
-               var vclass = "ui-scrollbar-visible";
-               if ( this._$vScrollBar ) {
-                       this._$vScrollBar.addClass( vclass );
-               }
-               if ( this._$hScrollBar ) {
-                       this._$hScrollBar.addClass( vclass );
-               }
-       },
+                               if ( ( elem_top < $c.offset().top ) || ( elem_top > screen_h ) ) {
+                                       self.scrollTo( 0, self._sy -
+                                                       ( elem_top - $c.offset().top - elem.height() ) );
+                               }
 
-       _hideScrollBars: function () {
-               var vclass = "ui-scrollbar-visible";
-               if ( this._$vScrollBar ) {
-                       this._$vScrollBar.removeClass( vclass );
-               }
-               if ( this._$hScrollBar ) {
-                       this._$hScrollBar.removeClass( vclass );
-               }
-       },
+                               return;
+                       });
+
+                       $v.bind( "keyup", function ( e ) {
+                               var input,
+                                       elem,
+                                       elem_top,
+                                       scroll_top = $( window ).scrollTop() - window.screenTop,
+                                       screen_h;
+
+                               if ( e.keyCode != 9 ) {
+                                       return;
+                               }
+
+                               /* Tab Key */
+
+                               input = $( this ).find(":input");
+
+                               for ( i = 0; i < input.length; i++ ) {
+                                       if ( !$( input[i] ).hasClass("ui-focus") ) {
+                                               continue;
+                                       }
+
+                                       if ( i + 1 == input.length ) {
+                                               elem = $( input[0] );
+                                       } else {
+                                               elem = $( input[i + 1] );
+                                       }
 
-       _addBehaviors: function () {
-               var self = this;
+                                       elem_top = elem.offset().top - scroll_top;
+                                       screen_h = $c.offset().top + $c.height() - elem.height();
 
-               if ( this.options.eventType === "mouse" ) {
-                       this._dragEvt = "mousedown mousemove mouseup click";
-                       this._dragCB = function ( e ) {
-                               switch ( e.type ) {
-                               case "mousedown":
-                                       return self._handleDragStart( e,
-                                                       e.clientX, e.clientY );
+                                       if ( self._softkeyboard ) {
+                                               screen_h -= self._softkeyboardHeight;
+                                       }
 
-                               case "mousemove":
-                                       return self._handleDragMove( e,
-                                                       e.clientX, e.clientY );
+                                       if ( ( elem_top < 0 ) || ( elem_top > screen_h ) ) {
+                                               self.scrollTo( 0, self._sy - elem_top +
+                                                       elem.height() + $c.offset().top, 0);
+                                       }
 
-                               case "mouseup":
-                                       return self._handleDragStop( e );
+                                       elem.focus();
 
-                               case "click":
-                                       return !self._didDrag;
+                                       break;
                                }
-                       };
-               } else {
-                       this._dragEvt = "touchstart touchmove touchend vclick";
-                       this._dragCB = function ( e ) {
-                               var t;
 
-                               switch ( e.type ) {
-                               case "touchstart":
-                                       t = e.originalEvent.targetTouches[0];
-                                       return self._handleDragStart( e,
-                                                       t.pageX, t.pageY );
+                               return false;
+                       });
+
+                       $c.bind( "updatelayout", function ( e ) {
+                               var sy,
+                                       vh,
+                                       view_h = self._getViewHeight();
 
-                               case "touchmove":
-                                       t = e.originalEvent.targetTouches[0];
-                                       return self._handleDragMove( e,
-                                                       t.pageX, t.pageY );
+                               if ( !$c.height() || !view_h ) {
+                                       self.scrollTo( 0, 0, 0 );
+                                       return;
+                               }
+
+                               sy = $c.height() - view_h;
+                               vh = view_h - self._view_height;
 
-                               case "touchend":
-                                       return self._handleDragStop( e );
+                               self._view_height = view_h;
 
-                               case "vclick":
-                                       return !self._didDrag;
+                               if ( vh == 0 || vh > $c.height() / 2 ) {
+                                       return;
                                }
-                       };
-               }
 
-               this._$view.bind( this._dragEvt, this._dragCB );
+                               if ( sy > 0 ) {
+                                       self.scrollTo( 0, 0, 0 );
+                               } else if ( self._sy - sy <= -vh ) {
+                                       self.scrollTo( 0, self._sy,
+                                               self.options.snapbackDuration );
+                               } else if ( self._sy - sy <= vh + self.options.moveThreshold ) {
+                                       self.scrollTo( 0, sy,
+                                               self.options.snapbackDuration );
+                               }
+                       });
+
+                       $( window ).bind( "resize", function ( e ) {
+                               var focused,
+                                       view_h = self._getViewHeight();
+
+                               if ( $(".ui-page-active").get(0) !== $c.closest(".ui-page").get(0) ) {
+                                       return;
+                               }
+
+                               if ( !$c.height() || !view_h ) {
+                                       return;
+                               }
+
+                               focused = $c.find(".ui-focus");
+
+                               if ( focused ) {
+                                       focused.trigger("resize.scrollview");
+                               }
+
+                               /* calibration - after triggered throttledresize */
+                               setTimeout( function () {
+                                       if ( self._sy < $c.height() - self._getViewHeight() ) {
+                                               self.scrollTo( 0, $c.height() - self._getViewHeight(),
+                                                       self.options.overshootDuration );
+                                       }
+                               }, 260 );
+
+                               self._view_height = view_h;
+                       });
+
+                       $( window ).bind( "vmouseout", function ( e ) {
+                               var drag_stop = false;
+
+                               if ( $(".ui-page-active").get(0) !== $c.closest(".ui-page").get(0) ) {
+                                       return;
+                               }
+
+                               if ( !self._dragging ) {
+                                       return;
+                               }
+
+                               if ( e.pageX < 0 || e.pageX > $( window ).width() ) {
+                                       drag_stop = true;
+                               }
+
+                               if ( e.pageY < 0 || e.pageY > $( window ).height() ) {
+                                       drag_stop = true;
+                               }
+
+                               if ( drag_stop ) {
+                                       self._hideScrollBars();
+                                       self._hideOverflowIndicator();
+                                       self._disableTracking();
+                               }
+                       });
+
+                       this._softkeyboard = false;
+                       this._softkeyboardHeight = 0;
+
+                       window.addEventListener( "softkeyboardchange", function ( e ) {
+                               if ( $(".ui-page-active").get(0) !== $c.closest(".ui-page").get(0) ) {
+                                       return;
+                               }
+
+                               self._softkeyboard = ( e.state === "on" ? true : false );
+                               self._softkeyboardHeight = parseInt( e.height ) *
+                                               ( $( window ).width() / window.screen.availWidth );
+                       });
+
+                       $c.closest(".ui-page")
+                               .bind( "pageshow", function ( e ) {
+                                       /* should be called after pagelayout */
+                                       setTimeout( function () {
+                                               self._view_height = self._getViewHeight();
+                                               self._set_scrollbar_size();
+                                               self._setScrollPosition( self._sx, self._sy );
+                                               self._showScrollBars( 2000 );
+                                       }, 0 );
+                               });
+               },
+
+               _add_scrollbar: function () {
+                       var $c = this._$clip,
+                               prefix = "<div class=\"ui-scrollbar ui-scrollbar-",
+                               suffix = "\"><div class=\"ui-scrollbar-track\"><div class=\"ui-scrollbar-thumb\"></div></div></div>";
+
+                       if ( !this.options.showScrollBars ) {
+                               return;
+                       }
 
-               if ( this.options.showScrollBars ) {
-                       var $c = this._$clip;
-                       var prefix = "<div class=\"ui-scrollbar ui-scrollbar-";
-                       var suffix = "\"><div class=\"ui-scrollbar-track\"><div class=\"ui-scrollbar-thumb\"></div></div></div>";
                        if ( this._vTracker ) {
                                $c.append( prefix + "y" + suffix );
                                this._$vScrollBar = $c.children(".ui-scrollbar-y");
@@ -747,203 +1214,281 @@ jQuery.widget( "tizen.scrollview", jQuery.mobile.widget, {
                                $c.append( prefix + "x" + suffix );
                                this._$hScrollBar = $c.children(".ui-scrollbar-x");
                        }
-               }
-       }
-});
 
-function setElementTransform( $ele, x, y, duration ) {
-       var v = "translate3d(" + x + "," + y + ", 0px)";
-       var transition;
+                       this._scrollbar_showed = false;
+               },
 
-       if ( !duration || duration === undefined ) {
-               transition = "none";
-       } else {
-               transition =  "-webkit-transform " + duration / 1000 + "s";
-       }
+               _add_scroll_jump: function () {
+                       var $c = this._$clip,
+                               self = this,
+                               top_btn,
+                               left_btn;
 
-       $ele.css({
-               "-moz-transform": v,
-               "-webkit-transform": v,
-               "transform": v,
-               "-webkit-transition": transition
-       });
-}
-
-function MomentumTracker( options ) {
-       this.options = $.extend( {}, options );
-       this.easing = "easeOutQuad";
-       this.reset();
-}
-
-var tstates = {
-       scrolling: 0,
-       overshot:  1,
-       snapback:  2,
-       done:      3
-};
-
-function getCurrentTime() {
-       return ( new Date() ).getTime();
-}
-
-$.extend( MomentumTracker.prototype, {
-       start: function ( pos, speed, duration, minPos, maxPos ) {
-               var tstate = (pos < minPos || pos > maxPos) ?
-                               tstates.snapback : tstates.scrolling;
-               this.state = (speed !== 0) ? tstate : tstates.done;
-               this.pos = pos;
-               this.speed = speed;
-               this.duration = (this.state === tstates.snapback) ?
-                               this.options.snapbackDuration : duration;
-               this.minPos = minPos;
-               this.maxPos = maxPos;
-
-               this.fromPos = (this.state === tstates.snapback) ? this.pos : 0;
-               var pos_temp = (this.pos < this.minPos) ? this.minPos : this.maxPos;
-               this.toPos = (this.state === tstates.snapback) ? pos_temp : 0;
-
-               this.startTime = getCurrentTime();
-       },
-
-       reset: function () {
-               this.state = tstates.done;
-               this.pos = 0;
-               this.speed = 0;
-               this.minPos = 0;
-               this.maxPos = 0;
-               this.duration = 0;
-       },
-
-       update: function ( overshootEnable ) {
-               var state = this.state;
-
-               if ( state === tstates.done ) {
-                       return this.pos;
-               }
+                       if ( !this.options.scrollJump ) {
+                               return;
+                       }
+
+                       if ( this._vTracker ) {
+                               top_btn = $( '<div class="ui-scroll-jump-top-bg">' +
+                                               '<div data-role="button" data-inline="true" data-icon="scrolltop" data-style="box"></div></div>' );
+                               $c.append( top_btn ).trigger("create");
 
-               var cur_time = getCurrentTime();
-               var duration = this.duration;
-               var elapsed =  cur_time - this.startTime;
-               elapsed = elapsed > duration ? duration : elapsed;
+                               top_btn.bind( "vclick", function () {
+                                       self.scrollTo( 0, 0, self.options.overshootDuration );
+                               } );
+                       }
 
-               if ( state === tstates.scrolling || state === tstates.overshot ) {
-                       var dx = this.speed *
-                               (1 - $.easing[this.easing]( elapsed / duration,
-                                                       elapsed, 0, 1, duration ));
+                       if ( this._hTracker ) {
+                               left_btn = $( '<div class="ui-scroll-jump-left-bg">' +
+                                               '<div data-role="button" data-inline="true" data-icon="scrollleft" data-style="box"></div></div>' );
+                               $c.append( left_btn ).trigger("create");
 
-                       var x = this.pos + dx;
+                               left_btn.bind( "vclick", function () {
+                                       self.scrollTo( 0, 0, self.options.overshootDuration );
+                               } );
+                       }
+               },
 
-                       var didOverShoot = (state === tstates.scrolling) &&
-                               (x < this.minPos || x > this.maxPos);
+               _add_overflow_indicator: function () {
+                       if ( !this.options.overflowEnable ) {
+                               return;
+                       }
 
-                       if ( didOverShoot ) {
-                               x = (x < this.minPos) ? this.minPos : this.maxPos;
+                       this._overflow_top = $( '<div class="ui-overflow-indicator-top"></div>' );
+                       this._overflow_bottom = $( '<div class="ui-overflow-indicator-bottom"></div>' );
+
+                       this._$clip.append( this._overflow_top );
+                       this._$clip.append( this._overflow_bottom );
+
+                       this._opacity_top = "0.5";
+                       this._opacity_bottom = "0.5";
+                       this._overflow_showed = false;
+               },
+
+               _set_scrollbar_size: function () {
+                       var $c = this._$clip,
+                               $v = this._$view,
+                               cw = 0,
+                               vw = 0,
+                               ch = 0,
+                               vh = 0,
+                               thumb;
+
+                       if ( !this.options.showScrollBars ) {
+                               return;
                        }
 
-                       this.pos = x;
+                       if ( this._hTracker ) {
+                               cw = $c.width();
+                               vw = $v.width();
+                               this._maxX = cw - vw;
 
-                       if ( state === tstates.overshot ) {
-                               if ( elapsed >= duration ) {
-                                       this.state = tstates.snapback;
-                                       this.fromPos = this.pos;
-                                       this.toPos = (x < this.minPos) ?
-                                               this.minPos : this.maxPos;
-                                       this.duration = this.options.snapbackDuration;
-                                       this.startTime = cur_time;
-                                       elapsed = 0;
-                               }
-                       } else if ( state === tstates.scrolling ) {
-                               if ( didOverShoot && overshootEnable ) {
-                                       this.state = tstates.overshot;
-                                       this.speed = dx / 2;
-                                       this.duration = this.options.overshootDuration;
-                                       this.startTime = cur_time;
-                               } else if ( elapsed >= duration ) {
-                                       this.state = tstates.done;
+                               if ( this._maxX > 0 ) {
+                                       this._maxX = 0;
+                               }
+                               if ( this._$hScrollBar && vw ) {
+                                       thumb = this._$hScrollBar.find(".ui-scrollbar-thumb");
+                                       thumb.css( "width", (cw >= vw ? "0" :
+                                                       (Math.floor(cw / vw * 100) || 1) + "%") );
                                }
                        }
-               } else if ( state === tstates.snapback ) {
-                       if ( elapsed >= duration ) {
-                               this.pos = this.toPos;
-                               this.state = tstates.done;
-                       } else {
-                               this.pos = this.fromPos + ((this.toPos - this.fromPos) *
-                                       $.easing[this.easing]( elapsed/duration,
-                                               elapsed, 0, 1, duration ));
+
+                       if ( this._vTracker ) {
+                               ch = $c.height();
+                               vh = this._getViewHeight();
+                               this._maxY = ch - vh;
+
+                               if ( this._maxY > 0 ) {
+                                       this._maxY = 0;
+                               }
+                               if ( this._$vScrollBar && vh ) {
+                                       thumb = this._$vScrollBar.find(".ui-scrollbar-thumb");
+                                       thumb.css( "height", (ch >= vh ? "0" :
+                                                       (Math.floor(ch / vh * 100) || 1) + "%") );
+
+                                       this._overflowAvail = !!thumb.height();
+                               }
                        }
                }
+       });
 
-               return this.pos;
-       },
+       $.extend( MomentumTracker.prototype, {
+               start: function ( pos, speed, duration, minPos, maxPos ) {
+                       var tstate = ( pos < minPos || pos > maxPos ) ?
+                                       tstates.snapback : tstates.scrolling,
+                               pos_temp;
+
+                       this.state = ( speed !== 0 ) ? tstate : tstates.done;
+                       this.pos = pos;
+                       this.speed = speed;
+                       this.duration = ( this.state === tstates.snapback ) ?
+                                       this.options.snapbackDuration : duration;
+                       this.minPos = minPos;
+                       this.maxPos = maxPos;
+
+                       this.fromPos = ( this.state === tstates.snapback ) ? this.pos : 0;
+                       pos_temp = ( this.pos < this.minPos ) ? this.minPos : this.maxPos;
+                       this.toPos = ( this.state === tstates.snapback ) ? pos_temp : 0;
+
+                       this.startTime = getCurrentTime();
+               },
+
+               reset: function () {
+                       this.state = tstates.done;
+                       this.pos = 0;
+                       this.speed = 0;
+                       this.minPos = 0;
+                       this.maxPos = 0;
+                       this.duration = 0;
+                       this.remained = 0;
+               },
+
+               update: function ( overshootEnable ) {
+                       var state = this.state,
+                               cur_time = getCurrentTime(),
+                               duration = this.duration,
+                               elapsed =  cur_time - this.startTime,
+                               dx,
+                               x,
+                               didOverShoot;
+
+                       if ( state === tstates.done ) {
+                               return this.pos;
+                       }
 
-       done: function () {
-               return this.state === tstates.done;
-       },
+                       elapsed = elapsed > duration ? duration : elapsed;
 
-       getPosition: function () {
-               return this.pos;
-       }
-});
-
-function ResizePageContentHeight( page ) {
-       var $page = $( page ),
-               $content = $page.children(".ui-content"),
-               hh = $page.children(".ui-header").outerHeight() || 0,
-               fh = $page.children(".ui-footer").outerHeight() || 0,
-               pt = parseFloat( $content.css("padding-top") ),
-               pb = parseFloat( $content.css("padding-bottom") ),
-               wh = window.innerHeight;
-
-       $content.height( wh - (hh + fh) - (pt + pb) );
-}
-
-// auto-init scrollview and scrolllistview widgets
-$( document ).bind( 'pagecreate create', function ( e ) {
-       var $page = $( e.target );
-       var scroll = $page.find(".ui-content").attr("data-scroll");
-
-       if ( scroll === "none" ) {
-               return;
-       }
+                       this.remained = duration - elapsed;
 
-       if ( $.support.scrollview === true && scroll === undefined ) {
-               $page.find(".ui-content").attr( "data-scroll", "y" );
-       }
+                       if ( state === tstates.scrolling || state === tstates.overshot ) {
+                               dx = this.speed *
+                                       ( 1 - $.easing[this.easing]( elapsed / duration,
+                                                               elapsed, 0, 1, duration ) );
 
-       $page.find(":jqmData(scroll):not(.ui-scrollview-clip)").each( function () {
-               var $this = $( this );
+                               x = this.pos + dx;
 
-               if ( $this.hasClass("ui-scrolllistview") ) {
-                       $this.scrolllistview();
-               } else {
-                       var st = $this.jqmData("scroll") + "",
-                               paging = st && (st.search(/^[xy]p$/) !== -1),
-                               dir = st && (st.search(/^[xy]/) !== -1) ? st.charAt(0) : null;
+                               didOverShoot = ( state === tstates.scrolling ) &&
+                                       ( x < this.minPos || x > this.maxPos );
 
-                       var opts = {
-                               direction: dir || undefined,
-                               paging: paging || undefined,
-                               scrollMethod: $this.jqmData("scroll-method") || undefined
-                       };
+                               if ( didOverShoot ) {
+                                       x = ( x < this.minPos ) ? this.minPos : this.maxPos;
+                               }
+
+                               this.pos = x;
+
+                               if ( state === tstates.overshot ) {
+                                       if ( !overshootEnable ) {
+                                               this.state = tstates.done;
+                                       }
+                                       if ( elapsed >= duration ) {
+                                               this.state = tstates.snapback;
+                                               this.fromPos = this.pos;
+                                               this.toPos = ( x < this.minPos ) ?
+                                                               this.minPos : this.maxPos;
+                                               this.duration = this.options.snapbackDuration;
+                                               this.startTime = cur_time;
+                                               elapsed = 0;
+                                       }
+                               } else if ( state === tstates.scrolling ) {
+                                       if ( didOverShoot && overshootEnable ) {
+                                               this.state = tstates.overshot;
+                                               this.speed = dx / 2;
+                                               this.duration = this.options.overshootDuration;
+                                               this.startTime = cur_time;
+                                       } else if ( elapsed >= duration ) {
+                                               this.state = tstates.done;
+                                       }
+                               }
+                       } else if ( state === tstates.snapback ) {
+                               if ( elapsed >= duration ) {
+                                       this.pos = this.toPos;
+                                       this.state = tstates.done;
+                               } else {
+                                       this.pos = this.fromPos + (( this.toPos - this.fromPos ) *
+                                               $.easing[this.easing]( elapsed / duration,
+                                                       elapsed, 0, 1, duration ));
+                               }
+                       }
+
+                       return this.pos;
+               },
+
+               done: function () {
+                       return this.state === tstates.done;
+               },
+
+               isMin: function () {
+                       return this.pos === this.minPos;
+               },
+
+               isMax: function () {
+                       return this.pos === this.maxPos;
+               },
+
+               isAvail: function () {
+                       return !( this.minPos === this.maxPos );
+               },
 
-                       $this.scrollview( opts );
+               getRemained: function () {
+                       return this.remained;
+               },
+
+               getPosition: function () {
+                       return this.pos;
                }
        });
-});
 
-$( document ).bind( 'pageshow', function ( e ) {
-       var $page = $( e.target );
-       var scroll = $page.find(".ui-content").attr("data-scroll");
+       $( document ).bind( 'pagecreate create', function ( e ) {
+               var $page = $( e.target ),
+                       content_scroll = $page.find(".ui-content").jqmData("scroll");
 
-       if ( scroll === "y" ) {
-               setTimeout( function () {
-                       ResizePageContentHeight( e.target );
-               }, 100);
-       }
-});
+               /* content scroll */
+               if ( $.support.scrollview === undefined ) {
+                       $.support.scrollview = true;
+               }
+
+               if ( $.support.scrollview === true && content_scroll === undefined ) {
+                       content_scroll = "y";
+               }
+
+               if ( content_scroll !== "y" ) {
+                       content_scroll = "none";
+               }
+
+               $page.find(".ui-content").attr( "data-scroll", content_scroll );
+
+               $page.find(":jqmData(scroll)").not(".ui-scrollview-clip").each( function () {
+                       if ( $( this ).hasClass("ui-scrolllistview") ) {
+                               $( this ).scrolllistview();
+                       } else {
+                               var st = $( this ).jqmData("scroll"),
+                                       dir = st && ( st.search(/^[xy]/) !== -1 ) ? st : null,
+                                       content = $(this).hasClass("ui-content"),
+                                       opts;
+
+                               if ( st === "none" ) {
+                                       return;
+                               }
+
+                               opts = {
+                                       direction: dir || undefined,
+                                       overflowEnable: content,
+                                       scrollMethod: $( this ).jqmData("scroll-method") || undefined,
+                                       scrollJump: $( this ).jqmData("scroll-jump") || undefined
+                               };
+
+                               $( this ).scrollview( opts );
+                       }
+               });
+       });
+
+       $( document ).bind( 'pageshow', function ( e ) {
+               var $page = $( e.target ),
+                       scroll = $page.find(".ui-content").jqmData("scroll");
 
-$( window ).bind( "orientationchange", function( e ) {
-       ResizePageContentHeight( $(".ui-page") );
-});
+               if ( scroll === "y" ) {
+                       resizePageContentHeight( e.target );
+               }
+       });
 
 }( jQuery, window, document ) );