]>
git.proxmox.com Git - extjs.git/blob - extjs/packages/core/src/scroll/TouchScroller.js
2 * @class Ext.scroll.TouchScroller
4 * Momentum scrolling is one of the most important parts of the user experience on touch-screen
5 * devices. Depending on the device and browser, Ext JS will select one of several different
6 * scrolling implementations for best performance.
8 * Scroller settings can be changed using the {@link Ext.Container#scrollable scrollable}
9 * configuration on {@link Ext.Component}. Here is a simple example of how to adjust the
10 * scroller settings when using a Component (or anything that extends it).
13 * Ext.create('Ext.Component', {
14 * renderTo: Ext.getBody(),
17 * // this component is scrollable vertically but not horizontally
19 * html: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque convallis lorem et magna tempus fermentum.'
22 Ext
.define('Ext.scroll.TouchScroller', {
23 extend
: 'Ext.scroll.Scroller',
24 alias
: 'scroller.touch',
27 'Ext.fx.easing.BoundMomentum',
28 'Ext.fx.easing.EaseOut',
29 'Ext.util.Translatable',
30 'Ext.scroll.Indicator',
34 isTouchScroller
: true,
41 * - `true`: monitors both element and innerElement for resize
42 * - `null`: only monitors element for resize
43 * - `false`: does not monitor resize of either element or innerElement
60 elementSize
: undefined,
71 * @cfg maxAbsoluteVelocity
74 maxAbsoluteVelocity
: 6,
77 * @cfg {Object} momentumEasing
79 * The default value is:
92 * Note that supplied object will be recursively merged with the default object. For example, you can simply
93 * pass this to change the momentum acceleration only:
116 * @cfg outOfBoundRestrictFactor
119 outOfBoundRestrictFactor
: 0.5,
122 * @cfg {Ext.dom.Element}
124 * The element that wraps the content of {@link #element} and is translated in
125 * response to user interaction. If not configured, one will be automatically
141 * @cfg slotSnapOffset
150 * @cfg startMomentumResetTime
153 startMomentumResetTime
: 300,
160 translationMethod
: 'auto',
165 cls
: Ext
.baseCSSPrefix
+ 'scroll-container',
166 scrollerCls
: Ext
.baseCSSPrefix
+ 'scroll-scroller',
183 touchstart
: 'onTouchStart',
184 touchmove
: 'onTouchMove',
185 dragstart
: 'onDragStart',
192 constructor: function(config
) {
196 me
.elementListeners
= {
205 me
.minPosition
= { x
: 0, y
: 0 };
207 me
.startPosition
= { x
: 0, y
: 0 };
209 me
.velocity
= { x
: 0, y
: 0 };
211 me
.isAxisEnabledFlags
= { x
: false, y
: false };
213 me
.flickStartPosition
= { x
: 0, y
: 0 };
215 me
.flickStartTime
= { x
: 0, y
: 0 };
217 me
.lastDragPosition
= { x
: 0, y
: 0 };
219 me
.dragDirection
= { x
: 0, y
: 0};
221 me
.callParent([config
]);
225 me
.scheduleRefresh
= {
233 applyBounceEasing: function(easing
) {
234 var defaultClass
= Ext
.fx
.easing
.EaseOut
;
237 x
: Ext
.factory(easing
, defaultClass
),
238 y
: Ext
.factory(easing
, defaultClass
)
242 applyElementSize: function(size
) {
243 var el
= this.getElement(),
256 if (size
== null) { // null or undefined
258 y
= dom
.clientHeight
;
270 applyIndicators: function(indicators
, oldIndicators
) {
272 xIndicator
, yIndicator
, x
, y
;
275 if (indicators
=== true) {
276 xIndicator
= yIndicator
= {};
281 // handle an object with x/y keys for configuring the indicators
282 // individually. undfined/null/true are all the same, only false
283 // can prevent the indicator from being created
284 xIndicator
= (x
== null || x
=== true) ? {} : x
;
285 yIndicator
= (x
== null || y
=== true) ? {} : y
;
287 // not an object with x/y keys, handle as a single indicators config
288 // that applies to both axes
289 xIndicator
= yIndicator
= indicators
;
295 oldIndicators
.x
.setConfig(xIndicator
);
297 oldIndicators
.x
.destroy();
298 oldIndicators
.x
= null;
301 oldIndicators
.y
.setConfig(yIndicator
);
303 oldIndicators
.y
.destroy();
304 oldIndicators
.y
= null;
306 indicators
= oldIndicators
;
308 indicators
= { x
: null, y
: null };
310 indicators
.x
= new Ext
.scroll
.Indicator(Ext
.applyIf({
316 indicators
.y
= new Ext
.scroll
.Indicator(Ext
.applyIf({
322 } else if (oldIndicators
) {
323 if (oldIndicators
.x
) {
324 oldIndicators
.x
.destroy();
326 if (oldIndicators
.y
) {
327 oldIndicators
.y
.destroy();
329 oldIndicators
.x
= oldIndicators
.y
= null;
335 applyMomentumEasing: function(easing
) {
336 var defaultClass
= Ext
.fx
.easing
.BoundMomentum
;
339 x
: Ext
.factory(easing
, defaultClass
),
340 y
: Ext
.factory(easing
, defaultClass
)
344 applyInnerElement: function(innerElement
) {
345 if (innerElement
&& !innerElement
.isElement
) {
346 innerElement
= Ext
.get(innerElement
);
350 if (this.isConfiguring
&& !innerElement
) {
351 Ext
.raise("Cannot create Ext.scroll.TouchScroller instance with null innerElement");
358 applyMaxPosition: function(maxPosition
, oldMaxPosition
) {
359 // If a no-op (generated setter tests object identity), return undefined to abort set.
360 if (oldMaxPosition
&& maxPosition
.x
=== oldMaxPosition
.x
&& maxPosition
.y
=== oldMaxPosition
.y
) {
363 var translatable
= this.getTranslatable(),
366 // If an animation is in flight...
367 if (translatable
.isAnimating
) {
369 // Find its Y dimension easing object
370 yEasing
= translatable
.activeEasingY
;
372 // If it's animating in the -ve direction (scrolling up), and we are
373 // shortening the scroll range, ensure the easing's min point complies
374 // with the new end position.
375 if (yEasing
&& yEasing
.getStartVelocity
&&
376 yEasing
.getStartVelocity() < 0 && maxPosition
.y
< oldMaxPosition
.y
) {
377 yEasing
.setMinMomentumValue(-maxPosition
.y
);
384 applyMaxUserPosition: function(maxUserPosition
, oldMaxUserPosition
) {
385 // If a no-op (generated setter tests object identity), return undefined to abort set.
386 if (oldMaxUserPosition
&& maxUserPosition
.x
=== oldMaxUserPosition
.x
&& maxUserPosition
.y
=== oldMaxUserPosition
.y
) {
389 return maxUserPosition
;
392 applySize: function(size
) {
393 var el
= this.getElement(),
394 dom
, scrollerDom
, x
, y
;
396 if (typeof size
=== 'number') {
404 if (el
&& (x
== null || y
== null)) {
406 scrollerDom
= this.getInnerElement().dom
;
408 // using scrollWidth/scrollHeight instead of offsetWidth/offsetHeight ensures
409 // that the size includes any contained absolutely positioned items
411 x
= Math
.max(scrollerDom
.scrollWidth
, dom
.clientWidth
);
415 y
= Math
.max(scrollerDom
.scrollHeight
, dom
.clientHeight
);
425 applySlotSnapOffset: function(snapOffset
) {
426 if (typeof snapOffset
=== 'number') {
436 applySlotSnapSize: function(snapSize
) {
437 if (typeof snapSize
=== 'number') {
447 applySlotSnapEasing: function(easing
) {
448 var defaultClass
= Ext
.fx
.easing
.EaseOut
;
451 x
: Ext
.factory(easing
, defaultClass
),
452 y
: Ext
.factory(easing
, defaultClass
)
456 applyTranslatable: function(config
, translatable
) {
457 return Ext
.factory(config
, Ext
.util
.Translatable
, translatable
);
460 destroy: function() {
462 element
= me
.getElement(),
463 innerElement
= me
.getInnerElement(),
464 sizeMonitors
= me
.sizeMonitors
;
467 sizeMonitors
.element
.destroy();
468 sizeMonitors
.container
.destroy();
471 if (element
&& !element
.destroyed
) {
472 element
.removeCls(me
.cls
);
475 if (innerElement
&& !innerElement
.destroyed
) {
476 innerElement
.removeCls(me
.scrollerCls
);
480 if (!element
.destroyed
) {
484 innerElement
.destroy();
488 me
.setInnerElement(null);
489 me
.setIndicators(null);
491 Ext
.destroy(me
.getTranslatable());
496 refresh: function(immediate
, /* private */ options
) {
501 me
.doRefresh(options
);
503 // Schedule a refresh at the next transition to idle.
504 else if (!me
.refreshScheduled
) {
505 me
.scheduleRefresh
.args
= [options
];
506 me
.refreshScheduled
= Ext
.on(me
.scheduleRefresh
);
510 updateAutoRefresh: function(autoRefresh
) {
511 this.toggleResizeListeners(autoRefresh
);
514 updateBounceEasing: function(easing
) {
515 this.getTranslatable().setEasingX(easing
.x
).setEasingY(easing
.y
);
518 updateElementSize: function() {
519 if (!this.isConfiguring
) {
520 // to avoid multiple calls to refreshAxes() during initialization we will
521 // call it once after initConfig has finished.
526 updateDisabled: function(disabled
) {
527 // attachment of listeners is handled by updateElement during initial config
528 if (!this.isConfiguring
) {
530 this.detachListeners();
532 this.attachListeners();
537 updateElement: function(element
, oldElement
) {
539 // first check if the user configured a innerElement
540 innerElement
= me
.getInnerElement(),
541 listeners
, autoRefresh
;
544 // if no configured scroller element, check if the first child has the
545 // scrollerCls, if so we can assume that the user already wrapped the content
546 // in a scrollerEl (this is true of both Ext and Touch Components).
547 innerElement
= element
.dom
.firstChild
;
549 if (!innerElement
|| innerElement
.nodeType
!== 1 ||
550 !Ext
.fly(innerElement
).hasCls(me
.scrollerCls
)) {
551 // no scrollerEl found, generate one now
552 innerElement
= me
.wrapContent(element
);
555 me
.setInnerElement(innerElement
);
558 element
.addCls(me
.cls
);
560 if (me
.isConfiguring
) {
561 if (!me
.getTranslatable().isScrollParent
) {
562 // Not using DOM scrolling, clear overflow styles.
563 element
.dom
.style
.overflowX
= element
.dom
.style
.overflowY
= '';
565 // If using full virtual scrolling attach a mousewheel listener for moving
566 // the scroll position. Otherwise we use native scrolling when interacting
567 // using the mouse and so do not want to override the native behavior
568 listeners
= me
.elementListeners
;
569 listeners
.mousewheel
= 'onMouseWheel';
571 fn
: 'onElementScroll',
578 if (!me
.getDisabled()) {
579 me
.attachListeners();
582 if (!me
.isConfiguring
) {
583 // setting element after initial construction of Scroller
584 // sync up configs that depend on element
585 autoRefresh
= me
.getAutoRefresh();
587 if (autoRefresh
!== false) {
588 me
.toggleResizeListeners(autoRefresh
);
592 } else if (autoRefresh
=== null) {
593 // setting elementSize to null will cause it to be updated from the dom
594 me
.setElementSize(null);
600 updateFps: function(fps
) {
601 if (fps
!== 'auto') {
602 this.getTranslatable().setFps(fps
);
606 updateMaxUserPosition: function() {
607 this.snapToBoundary();
610 updateMinUserPosition: function() {
611 this.snapToBoundary();
614 updateInnerElement: function(innerElement
) {
616 innerElement
.addCls(this.scrollerCls
);
619 this.getTranslatable().setElement(innerElement
);
622 updateSize: function(size
) {
623 if (!this.isConfiguring
) {
625 // Base class keeps the spacer el sized to "stretch" a DOM scroll range.
626 if (Ext
.supports
.touchScroll
=== 1) {
627 this.callParent([size
]);
629 // to avoid multiple calls to refreshAxes() during initialization we will
630 // call it once after initConfig has finished.
635 updateTranslatable: function(translatable
) {
636 translatable
.setElement(this.getInnerElement());
638 // We only need to process scroll in each frame, and scroll end on animation end
639 // if we are using CSS transform.
641 // If we are using DOM scrollTop/scrollLeft, then
642 // Scroller#onDomScroll will perform these duties.
643 if (!translatable
.isScrollParent
) {
645 animationframe
: 'onAnimationFrame',
646 animationend
: 'onAnimationEnd',
652 updateX: function() {
653 if (!this.isConfiguring
) {
654 // to avoid multiple calls to refreshAxes() during initialization we will
655 // call it once after initConfig has finished.
660 updateY: function() {
661 if (!this.isConfiguring
) {
662 // to avoid multiple calls to refreshAxes() during initialization we will
663 // call it once after initConfig has finished.
669 attachListeners: function() {
670 this.getElement().on(this.elementListeners
);
673 constrainX: function(x
) {
674 return Math
.min(this.getMaxPosition().x
, Math
.max(x
, 0));
677 constrainY: function(y
) {
678 return Math
.min(this.getMaxPosition().y
, Math
.max(y
, 0));
681 // overridden in RTL mode to swap min/max momentum values
682 convertEasingConfig: function(config
) {
686 detachListeners: function() {
687 this.getElement().un(this.elementListeners
);
693 doRefresh: function(options
) {
697 if (me
.refreshScheduled
) {
698 me
.refreshScheduled
= me
.refreshScheduled
.destroy();
701 if (me
.refreshCounter
&& me
.getElement()) {
704 me
.getTranslatable().refresh();
707 // If either size or elementSize were provided in options, do not bother
708 // to read the DOM to determine sizing info, just set the size given.
710 elementSize
= options
.elementSize
;
713 // If size or elementSize are null, they will be read from the DOM
715 me
.setElementSize(elementSize
);
717 me
.fireEvent('refresh', me
);
718 me
.refreshCounter
= 0;
722 doScrollTo: function(x
, y
, animation
, /* private */ allowOverscroll
) {
724 isDragging
= me
.isDragging
,
725 // We only call onScroll if we are programatically CSS translating the scrollable.
726 // If we are using DOM scrollTop/scrollLeft, then
727 // Scroller#onDomScroll will perform this duty.
728 DOMScrolling
= me
.getTranslatable().isScrollParent
,
729 // We only fire scrollstart, and scrollend if we are not reflecting,
730 // (and not having this handled by Scroller#onDomScroll)
731 // If we are reflecting, then onPartnerScrollStart and onPartnerScrollEnd
732 // will perform these duties.
733 fireStartEnd
= !me
.isReflecting
&& !DOMScrolling
;
735 if (me
.destroyed
|| !me
.getElement()) {
739 // Normally the scroll position is constrained to the max scroll position, but
740 // during a drag operation or during reflection the scroller is allowed to overscroll.
741 allowOverscroll
= allowOverscroll
|| me
.isDragging
;
743 var translatable
= me
.getTranslatable(),
744 position
= me
.position
,
745 positionChanged
= false,
746 translationX
, translationY
;
748 if (!isDragging
|| me
.isAxisEnabled('x')) {
749 if (isNaN(x
) || typeof x
!== 'number') {
753 if (!allowOverscroll
) {
754 x
= me
.constrainX(x
);
757 if (position
.x
!== x
) {
759 positionChanged
= true;
763 translationX
= me
.convertX(-x
);
766 if (!isDragging
|| me
.isAxisEnabled('y')) {
767 if (isNaN(y
) || typeof y
!== 'number') {
770 if (!allowOverscroll
) {
771 y
= me
.constrainY(y
);
774 if (position
.y
!== y
) {
776 positionChanged
= true;
783 if (positionChanged
) {
784 // If we are using scrollTop/scrollLeft, then DOM events will fire
785 // and Scroller#onDomScroll will perform scroll start processing
787 // Invoke scroll start handlers.
788 // If we are already scrolling, and this movement
789 // is in response to a subsequent touchmove, the this.isScrolling
790 // flag will already be set, and this will do nothing.
794 // onAnimationEnd calls onScrollEnd
795 translatable
.translateAnimated(translationX
, translationY
, animation
);
797 // If we are using scrollTop/scrollLeft, then DOM events will fire
798 // and Scroller#onDomScroll will perform scroll processing
802 translatable
.translate(translationX
, translationY
);
803 // If we are using scrollTop/scrollLeft, then DOM events will fire
804 // and Scroller#onDomScroll will schedule scroll end processing
809 } else if (animation
&& animation
.callback
) {
810 animation
.callback();
819 getAnimationEasing: function(axis
, e
) {
820 if (!this.isAxisEnabled(axis
)) {
825 currentPosition
= me
.position
[axis
],
826 minPosition
= me
.getMinUserPosition()[axis
],
827 maxPosition
= me
.getMaxUserPosition()[axis
],
828 maxAbsVelocity
= me
.getMaxAbsoluteVelocity(),
830 dragEndTime
= me
.dragEndTime
,
831 velocity
= e
.flick
.velocity
[axis
],
833 easingConfig
, easing
;
835 if (currentPosition
< minPosition
) {
836 boundValue
= minPosition
;
838 else if (currentPosition
> maxPosition
) {
839 boundValue
= maxPosition
;
843 currentPosition
= me
.convertX(currentPosition
);
844 boundValue
= me
.convertX(boundValue
);
847 // Out of bound, to be pulled back
848 if (boundValue
!== null) {
849 easing
= me
.getBounceEasing()[axis
];
851 startTime
: dragEndTime
,
852 startValue
: -currentPosition
,
853 endValue
: -boundValue
859 if (velocity
=== 0) {
863 if (velocity
< -maxAbsVelocity
) {
864 velocity
= -maxAbsVelocity
;
866 else if (velocity
> maxAbsVelocity
) {
867 velocity
= maxAbsVelocity
;
870 easing
= me
.getMomentumEasing()[axis
];
872 startTime
: dragEndTime
,
873 startValue
: -currentPosition
,
874 startVelocity
: velocity
* 1.5,
875 minMomentumValue
: -maxPosition
,
880 me
.convertEasingConfig(easingConfig
);
883 easing
.setConfig(easingConfig
);
890 * @return {Number/null}
892 getSnapPosition: function(axis
) {
894 snapSize
= me
.getSlotSnapSize()[axis
],
896 position
, snapOffset
, maxPosition
, mod
;
898 if (snapSize
!== 0 && me
.isAxisEnabled(axis
)) {
899 position
= me
.position
[axis
];
900 snapOffset
= me
.getSlotSnapOffset()[axis
];
901 maxPosition
= me
.getMaxUserPosition()[axis
];
903 mod
= Math
.floor((position
- snapOffset
) % snapSize
);
906 if (position
!== maxPosition
) {
907 if (Math
.abs(mod
) > snapSize
/ 2) {
908 snapPosition
= Math
.min(maxPosition
, position
+ ((mod
> 0) ? snapSize
- mod
: mod
- snapSize
));
911 snapPosition
= position
- mod
;
915 snapPosition
= position
- mod
;
923 hideIndicators: function() {
925 indicators
= me
.getIndicators(),
926 xIndicator
, yIndicator
;
929 if (me
.isAxisEnabled('x')) {
930 xIndicator
= indicators
.x
;
936 if (me
.isAxisEnabled('y')) {
937 yIndicator
= indicators
.y
;
946 * Returns `true` if a specified axis is enabled.
948 * @param {String} axis The axis to check (`x` or `y`).
949 * @return {Boolean} `true` if the axis is enabled.
951 isAxisEnabled: function(axis
) {
955 return this.isAxisEnabledFlags
[axis
];
958 onAnimationEnd: function() {
959 this.snapToBoundary();
963 onAnimationFrame: function(translatable
, x
, y
) {
964 var position
= this.position
;
966 position
.x
= this.convertX(-x
);
972 onAxisDrag: function(axis
, delta
) {
973 // Nothing to do if no delta, or it's on a disabled axis
974 if (delta
&& this.isAxisEnabled(axis
)) {
976 flickStartPosition
= me
.flickStartPosition
,
977 flickStartTime
= me
.flickStartTime
,
978 lastDragPosition
= me
.lastDragPosition
,
979 dragDirection
= me
.dragDirection
,
980 old
= me
.position
[axis
],
981 min
= me
.getMinUserPosition()[axis
],
982 max
= me
.getMaxUserPosition()[axis
],
983 start
= me
.startPosition
[axis
],
984 last
= lastDragPosition
[axis
],
985 current
= start
- delta
,
986 lastDirection
= dragDirection
[axis
],
987 restrictFactor
= me
.getOutOfBoundRestrictFactor(),
988 startMomentumResetTime
= me
.getStartMomentumResetTime(),
989 now
= Ext
.Date
.now(),
993 current
*= restrictFactor
;
995 else if (current
> max
) {
996 distance
= current
- max
;
997 current
= max
+ distance
* restrictFactor
;
1000 if (current
> last
) {
1001 dragDirection
[axis
] = 1;
1003 else if (current
< last
) {
1004 dragDirection
[axis
] = -1;
1007 if ((lastDirection
!== 0 && (dragDirection
[axis
] !== lastDirection
)) ||
1008 (now
- flickStartTime
[axis
]) > startMomentumResetTime
) {
1009 flickStartPosition
[axis
] = old
;
1010 flickStartTime
[axis
] = now
;
1013 lastDragPosition
[axis
] = current
;
1018 // In "hybrid" touch scroll mode where the TouchScroller is used to control the
1019 // scroll position of a naturally overflowing element, we need to sync the scroll
1020 // position of the TouchScroller when the element is scrolled
1021 onDomScroll: function() {
1025 if (me
.getTranslatable().isScrollParent
) {
1026 dom
= me
.getElement().dom
;
1027 position
= me
.position
;
1029 position
.x
= dom
.scrollLeft
;
1030 position
.y
= dom
.scrollTop
;
1035 onDrag: function(e
) {
1037 lastDragPosition
= me
.lastDragPosition
;
1039 if (!me
.isDragging
) {
1043 // If there's any moving to do, then move the content.
1044 // Boolean or operator avoids shortcutting the second function call if
1045 // first returns true.
1046 if (me
.onAxisDrag('x', me
.convertX(e
.deltaX
)) | me
.onAxisDrag('y', e
.deltaY
)) {
1047 me
.doScrollTo(lastDragPosition
.x
, lastDragPosition
.y
);
1051 onDragEnd: function(e
) {
1055 if (!me
.isDragging
) {
1059 me
.dragEndTime
= Ext
.Date
.now();
1063 me
.isDragging
= false;
1065 easingX
= me
.getAnimationEasing('x', e
);
1066 easingY
= me
.getAnimationEasing('y', e
);
1068 if (easingX
|| easingY
) {
1069 me
.getTranslatable().animate(easingX
, easingY
);
1075 onDragStart: function(e
) {
1077 direction
= me
.getDirection(),
1078 absDeltaX
= e
.absDeltaX
,
1079 absDeltaY
= e
.absDeltaY
,
1080 directionLock
= me
.getDirectionLock(),
1081 startPosition
= me
.startPosition
,
1082 flickStartPosition
= me
.flickStartPosition
,
1083 flickStartTime
= me
.flickStartTime
,
1084 lastDragPosition
= me
.lastDragPosition
,
1085 currentPosition
= me
.position
,
1086 dragDirection
= me
.dragDirection
,
1087 x
= currentPosition
.x
,
1088 y
= currentPosition
.y
,
1089 now
= Ext
.Date
.now();
1091 if (directionLock
&& direction
!== 'both') {
1092 if ((direction
=== 'horizontal' && absDeltaX
> absDeltaY
) ||
1093 (direction
=== 'vertical' && absDeltaY
> absDeltaX
)) {
1094 e
.stopPropagation();
1101 lastDragPosition
.x
= x
;
1102 lastDragPosition
.y
= y
;
1104 flickStartPosition
.x
= x
;
1105 flickStartPosition
.y
= y
;
1107 startPosition
.x
= x
;
1108 startPosition
.y
= y
;
1110 flickStartTime
.x
= now
;
1111 flickStartTime
.y
= now
;
1113 dragDirection
.x
= 0;
1114 dragDirection
.y
= 0;
1116 me
.dragStartTime
= now
;
1118 me
.isDragging
= true;
1120 // Only signal a scroll start if we are not already scrolling.
1121 // If the drag is just the user giving another impulse, it is NOT
1122 // the start of a drag.
1123 if (!me
.isScrolling
) {
1128 onElementResize: function(element
, info
) {
1129 this.refresh(true, {
1131 x
: info
.contentWidth
,
1132 y
: info
.contentHeight
1134 size
: this.getAutoRefresh() ? null : this.getSize()
1138 onElementScroll: function(event
, targetEl
) {
1139 targetEl
.scrollTop
= targetEl
.scrollLeft
= 0;
1142 onEvent: function(e
) {
1143 // use browserEvent to get the "real" type of DOM event that was fired, not a
1144 // potentially translated (or recognized) type
1146 browserEvent
= e
.browserEvent
;
1148 if ((!me
.self
.isTouching
|| me
.isTouching
) && // prevents nested scrolling
1149 // prevents scrolling in response to mouse input on multi-input devices
1150 // such as windows 8 laptops with touch screens.
1151 // Don't bother checking the event type if we are on a device that uses
1152 // full virtual scrolling (!isScrollParent)
1153 // TODO: this should be handled by the event system once EXTJSIV-12840
1155 ((!me
.getTranslatable().isScrollParent
) || (!me
.isMouseEvent
[browserEvent
.type
] &&
1156 browserEvent
.pointerType
!== 'mouse')) &&
1157 (me
.getY() || me
.getX())) {
1158 me
[me
.listenerMap
[e
.type
]](e
);
1162 onInnerElementResize: function(element
, info
) {
1163 this.refresh(true, {
1171 onMouseWheel: function(e
) {
1173 delta
= e
.getWheelDeltas(),
1176 position
= me
.position
,
1177 maxPosition
= me
.getMaxUserPosition(),
1178 minPosition
= me
.getMinUserPosition(),
1181 positionX
= max(min(position
.x
+ deltaX
, maxPosition
.x
), minPosition
.x
),
1182 positionY
= max(min(position
.y
+ deltaY
, maxPosition
.y
), minPosition
.y
);
1184 deltaX
= positionX
- position
.x
;
1185 deltaY
= positionY
- position
.y
;
1187 if (!deltaX
&& !deltaY
) {
1193 me
.scrollBy(deltaX
, deltaY
);
1198 onPartnerScrollEnd: function(x
, y
) {
1201 // In "hybrid" touch scroll mode where the TouchScroller is used to control the
1202 // scroll position of a naturally overflowing element, we do NOT need to call
1203 // fireScrollEnd because we will have been recieving DOM scroll events and will
1204 // begin and end scrolling in response to those events.
1206 // If scrollers programatically animate the scroll, then we must take the correct
1207 // scroll end action when our partner ends its scroll.
1208 if (!me
.getTranslatable().isScrollParent
) {
1209 me
.fireScrollEnd(x
, y
);
1211 me
.callParent([x
, y
]);
1212 me
.isScrolling
= false;
1213 me
.hideIndicators();
1216 onPartnerScrollStart: function(x
, y
) {
1219 me
.isScrolling
= true;
1221 // In "hybrid" touch scroll mode where the TouchScroller is used to control the
1222 // scroll position of a naturally overflowing element, we do NOT need to call
1223 // fireScrollStart because we will soon start recieving DOM scroll events and will
1224 // begin and end scrolling in response to those events.
1226 // If scrollers programatically animate the scroll, then we must take the correct
1227 // scroll end action when our partner starts its scroll.
1228 if (!me
.getTranslatable().isScrollParent
) {
1229 me
.fireScrollStart(x
, y
);
1231 me
.showIndicators();
1234 onScroll: function() {
1236 position
= me
.position
,
1239 indicators
= me
.getIndicators(),
1240 xIndicator
, yIndicator
;
1243 if (me
.isAxisEnabled('x')) {
1244 xIndicator
= indicators
.x
;
1246 xIndicator
.setValue(x
);
1249 if (me
.isAxisEnabled('y')) {
1250 yIndicator
= indicators
.y
;
1252 yIndicator
.setValue(y
);
1257 me
.fireScroll(x
, y
);
1260 onScrollEnd: function() {
1262 position
= me
.position
;
1264 if (me
.isScrolling
&& !me
.isTouching
&& !me
.snapToSlot()) {
1265 me
.hideIndicators();
1266 me
.isScrolling
= Ext
.isScrolling
= false;
1267 me
.fireScrollEnd(position
.x
, position
.y
);
1271 onScrollStart: function() {
1273 position
= me
.position
;
1275 if (!me
.isScrolling
) {
1276 me
.showIndicators();
1277 me
.isScrolling
= Ext
.isScrolling
= true;
1278 me
.fireScrollStart(position
.x
, position
.y
);
1282 onTouchEnd: function() {
1285 me
.isTouching
= me
.self
.isTouching
= false;
1287 if (!me
.isDragging
&& me
.snapToSlot()) {
1292 onTouchMove: function(e
) {
1293 // Prevents the page from scrolling while an element is being scrolled using
1294 // the TouchScroller. Only needed when inside a page that does not use a
1295 // Viewport, since the Viewport already prevents default behavior of touchmove
1299 onTouchStart: function() {
1302 me
.isTouching
= me
.self
.isTouching
= true;
1305 touchend
: 'onTouchEnd',
1313 refreshAxes: function() {
1315 flags
= me
.isAxisEnabledFlags
,
1316 size
= me
.getSize(),
1317 elementSize
= me
.getElementSize(),
1318 indicators
= me
.getIndicators(),
1319 maxX
, maxY
, x
, y
, xIndicator
, yIndicator
;
1321 if (!size
|| !elementSize
) {
1325 maxX
= Math
.max(0, size
.x
- elementSize
.x
);
1326 maxY
= Math
.max(0, size
.y
- elementSize
.y
);
1335 if (x
=== true || x
=== 'auto') {
1336 // auto scroll - axis is only enabled if the content is overflowing in the
1339 } else if (x
=== false) {
1341 xIndicator
= indicators
&& indicators
.x
;
1343 // hide the x indicator if the x axis is disabled, just in case we
1344 // are refreshing during a scroll
1347 } else if (x
=== 'scroll') {
1351 if (y
=== true || y
=== 'auto') {
1352 // auto scroll - axis is only enabled if the content is overflowing in the
1355 } else if (y
=== false) {
1357 yIndicator
= indicators
&& indicators
.y
;
1359 // hide the y indicator if the y axis is disabled, just in case we
1360 // are refreshing during a scroll
1363 } else if (y
=== 'scroll') {
1367 me
.setMaxUserPosition({
1368 x
: flags
.x
? maxX
: 0,
1369 y
: flags
.y
? maxY
: 0
1372 // If we are using regular DOM overflow scrolling, sync the element styles.
1373 if (Ext
.supports
.touchScroll
=== 1) {
1379 showIndicators: function() {
1381 indicators
= me
.getIndicators(),
1382 xIndicator
, yIndicator
;
1385 if (me
.isAxisEnabled('x')) {
1386 xIndicator
= indicators
.x
;
1392 if (me
.isAxisEnabled('y')) {
1393 yIndicator
= indicators
.y
;
1401 snapToBoundary: function() {
1403 position
= me
.getPosition();
1405 // If we haven't scrolled anywhere, we're done.
1406 if (me
.isConfiguring
|| !(position
.x
|| position
.y
)) {
1410 var minPosition
= me
.getMinUserPosition(),
1411 maxPosition
= me
.getMaxUserPosition(),
1412 minX
= minPosition
.x
,
1413 minY
= minPosition
.y
,
1414 maxX
= maxPosition
.x
,
1415 maxY
= maxPosition
.y
,
1416 x
= Math
.round(position
.x
),
1417 y
= Math
.round(position
.y
);
1422 else if (x
> maxX
) {
1429 else if (y
> maxY
) {
1433 me
.doScrollTo(x
, y
);
1440 snapToSlot: function() {
1442 snapX
= me
.getSnapPosition('x'),
1443 snapY
= me
.getSnapPosition('y'),
1444 easing
= me
.getSlotSnapEasing();
1446 if (snapX
!== null || snapY
!== null) {
1447 me
.doScrollTo(snapX
, snapY
, {
1460 * Stops the animation of the scroller at any time.
1462 stopAnimation: function() {
1463 this.getTranslatable().stopAnimation();
1466 toggleResizeListeners: function(autoRefresh
) {
1468 element
= me
.getElement(),
1469 method
, innerMethod
,
1473 innerElement
= me
.getInnerElement();
1475 method
= innerMethod
= 'on';
1476 } else if (autoRefresh
=== null) {
1480 method
= innerMethod
= 'un';
1483 element
[method
]('resize', 'onElementResize', me
);
1484 innerElement
[innerMethod
]('resize', 'onInnerElementResize', me
);
1488 unwrapContent: function() {
1489 var innerDom
= this.getInnerElement().dom
,
1490 dom
= this.getElement().dom
,
1493 while ((child
= innerDom
.firstChild
)) {
1494 dom
.insertBefore(child
, innerDom
);
1499 * Wraps the element's content in a innerElement
1500 * @param {Ext.dom.Element} element
1501 * @return {Ext.dom.Element} the innerElement
1504 wrapContent: function(element
) {
1505 var wrap
= document
.createElement('div'),
1509 while (child
= dom
.lastChild
) { // jshint ignore:line
1510 wrap
.insertBefore(child
, wrap
.firstChild
);
1513 dom
.appendChild(wrap
);
1515 this.setInnerElement(wrap
);
1517 // Set a flag that indiacates the element's content was not already pre-wrapped
1518 // when the scroller was instanced. This means we had to wrap the content
1519 // and so must unwrap when we destroy the scroller.
1520 this._isWrapped
= true;
1522 return this.getInnerElement();