/** * @private * Base class for iOS and Android viewports. */Ext.define('Ext.viewport.Default', function() { var TOP = 1, RIGHT = 2, BOTTOM = 4, LEFT = 8, sideMap = { top: TOP, right: RIGHT, bottom: BOTTOM, left: LEFT }, oppositeSide = { "1": BOTTOM, "2": LEFT, "4": TOP, "8": RIGHT }; return { extend: 'Ext.Container', xtype: 'viewport', PORTRAIT: 'portrait', LANDSCAPE: 'landscape', requires: [ 'Ext.GlobalEvents', 'Ext.layout.Card', 'Ext.util.InputBlocker' ], nameHolder: true, /** * @event ready * Fires when the Viewport is in the DOM and ready. * @param {Ext.Viewport} this */ /** * @event maximize * Fires when the Viewport is maximized. * @param {Ext.Viewport} this */ /** * @event orientationchange * Fires when the Viewport orientation has changed. * @param {Ext.Viewport} this * @param {String} newOrientation The new orientation. * @param {Number} width The width of the Viewport. * @param {Number} height The height of the Viewport. */ config: { /** * @private */ autoMaximize: false, /** * @private * * Auto blur the focused element when touching on a non-input. This is used to work around Android bugs * where the virtual keyboard is not hidden when tapping outside an input. */ autoBlurInput: true, /** * @cfg {Boolean} preventZooming * `true` to attempt to stop zooming when you double tap on the screen on mobile devices, * typically HTC devices with HTC Sense UI. * @accessor */ preventZooming: false, /** * @cfg * @private */ autoRender: true, /** * @cfg {Object/String} layout Configuration for this Container's layout. Example: * * Ext.create('Ext.Container', { * layout: { * type: 'hbox', * align: 'middle' * }, * items: [ * { * xtype: 'panel', * flex: 1, * style: 'background-color: red;' * }, * { * xtype: 'panel', * flex: 2, * style: 'background-color: green' * } * ] * }); * * @accessor */ layout: 'card', /** * @cfg * @private */ width: '100%', /** * @cfg * @private */ height: '100%', /** * An object of all the menus on this viewport. * @private */ menus: {}, /** * @private */ orientation: null }, classCls: Ext.baseCSSPrefix + 'viewport', getTemplate: function() { var template = this.callParent(); // Used in legacy browser that do not support matchMedia. Hidden element is used for checking of orientation if (!Ext.feature.has.MatchMedia) { template.unshift({ reference: 'orientationElement', className: Ext.baseCSSPrefix + 'orientation-inspector', children: [{ className: Ext.baseCSSPrefix + 'orientation-inspector-landscape' }] }); } return template; }, /** * @property {Boolean} isReady * `true` if the DOM is ready. */ isReady: false, isViewport: true, isMaximizing: false, id: 'ext-viewport', isInputRegex: /^(input|textarea|select|a)$/i, isInteractiveWebComponentRegEx: /^(audio|video)$/i, focusable: false, focusEl: null, ariaEl: null, allSidesCls: [ Ext.baseCSSPrefix + 'top', Ext.baseCSSPrefix + 'right', Ext.baseCSSPrefix + 'bottom', Ext.baseCSSPrefix + 'left' ], sideClsMap: { top: Ext.baseCSSPrefix + 'top', right: Ext.baseCSSPrefix + 'right', bottom: Ext.baseCSSPrefix + 'bottom', left: Ext.baseCSSPrefix + 'left' }, /** * @private */ fullscreenItemCls: Ext.baseCSSPrefix + 'fullscreen', constructor: function(config) { var me = this; me.doPreventPanning = me.doPreventPanning.bind(me); me.doPreventZooming = me.doPreventZooming.bind(me); me.maximizeOnEvents = [ 'ready', 'orientationchange' ]; // set default devicePixelRatio if it is not explicitly defined window.devicePixelRatio = window.devicePixelRatio || 1; me.callParent([config]); me.updateSize(); me.windowOuterHeight = me.getWindowOuterHeight(); // The global scroller is our scroller. // We must provide a non-scrolling one if we are not configured to scroll, // otherwise the deferred ready listener in Scroller will create // one with scroll: true Ext.setViewportScroller(me.getScrollable() || { x: false, y: false, element: Ext.getBody() }); // The body has to be overflow:hidden Ext.getBody().setStyle('overflow', 'hidden'); me.stretchHeights = me.stretchHeights || {}; if (Ext.feature.has.OrientationChange) { me.addWindowListener('orientationchange', me.onOrientationChange.bind(me)); } // Tale over firing the resize event to sync the Viewport first, then fire the event. Ext.GlobalEvents.on('resize', 'onWindowResize', me, {priority: 1000}); Ext.onDocumentReady(me.onDomReady, me); return me; }, initialize: function() { var me = this; me.addMeta('apple-mobile-web-app-capable', 'yes'); me.addMeta('apple-touch-fullscreen', 'yes'); me.callParent(); }, initInheritedState: function (inheritedState, inheritedStateInner) { var me = this, root = Ext.rootInheritedState; if (inheritedState !== root) { // We need to go at this again but with the rootInheritedState object. Let // any derived class poke on the proper object! me.initInheritedState(me.inheritedState = root, me.inheritedStateInner = Ext.Object.chain(root)); } else { me.callParent([inheritedState, inheritedStateInner]); } }, onAppLaunch: function() { var me = this; if (!me.isReady) { me.onDomReady(); } }, onDomReady: function() { var me = this; if (me.isReady) { return; } me.isReady = true; me.updateSize(); me.onReady(); me.fireEvent('ready', me); Ext.GlobalEvents.fireEvent('viewportready', me); }, onReady: function() { if (this.getAutoRender()) { this.render(); } if (Ext.browser.name === 'ChromeiOS') { this.setHeight('-webkit-calc(100% - ' + ((window.outerHeight - window.innerHeight) / 2) + 'px)'); } }, render: function() { var me = this, body = Ext.getBody(); if (!me.rendered) { me.callParent([body]); me.setOrientation(me.determineOrientation()); Ext.getBody().addCls(Ext.baseCSSPrefix + me.getOrientation()); } }, applyAutoMaximize: function(autoMaximize) { return Ext.browser.is.WebView ? false : autoMaximize; }, updateAutoMaximize: function(autoMaximize) { var me = this; if (autoMaximize) { me.on('ready', 'doAutoMaximizeOnReady', me, { single: true }); me.on('orientationchange', 'doAutoMaximizeOnOrientationChange', me); } else { me.un('ready', 'doAutoMaximizeOnReady', me); me.un('orientationchange', 'doAutoMaximizeOnOrientationChange', me); } }, updatePreventPanning: function(preventPanning) { this.toggleWindowListener(preventPanning, 'touchmove', this.doPreventPanning, false); }, updatePreventZooming: function(preventZooming) { var touchstart = Ext.feature.has.TouchEvents ? 'touchstart' : 'mousedown'; this.toggleWindowListener(preventZooming, touchstart, this.doPreventZooming, false); }, doAutoMaximizeOnReady: function() { var me = this; me.isMaximizing = true; me.on('maximize', function() { me.isMaximizing = false; me.updateSize(); me.fireEvent('ready', me); }, me, { single: true }); me.maximize(); }, doAutoMaximizeOnOrientationChange: function() { var me = this; me.isMaximizing = true; me.on('maximize', function() { me.isMaximizing = false; me.updateSize(); }, me, { single: true }); me.maximize(); }, doPreventPanning: function(e) { var target = e.target, touch; // If we have an interaction on a WebComponent we need to check the actual shadow dom element selected // to determine if it is an input before preventing default behavior // Side effect to this is if the shadow input does not do anything with 'touchmove' the user could pan // the screen. if (this.isInteractiveWebComponentRegEx.test(target.tagName) && e.touches && e.touches.length > 0) { touch = e.touches[0]; if (touch && touch.target && this.isInputRegex.test(touch.target.tagName)) { return; } } if (target && target.nodeType === 1 && !this.isInputRegex.test(target.tagName)) { e.preventDefault(); } }, doPreventZooming: function(e) { // Don't prevent right mouse event if ('button' in e && e.button !== 0) { return; } var target = e.target, inputRe = this.isInputRegex, touch; if (this.isInteractiveWebComponentRegEx.test(target.tagName) && e.touches && e.touches.length > 0) { touch = e.touches[0]; if (touch && touch.target && inputRe.test(touch.target.tagName)) { return; } } if (target && target.nodeType === 1 && !inputRe.test(target.tagName)) { e.preventDefault(); } }, addWindowListener: function(eventName, fn, capturing) { window.addEventListener(eventName, fn, Boolean(capturing)); }, removeWindowListener: function(eventName, fn, capturing) { window.removeEventListener(eventName, fn, Boolean(capturing)); }, supportsOrientation: function() { return Ext.feature.has.Orientation; }, supportsMatchMedia: function() { return Ext.feature.has.MatchMedia; }, onOrientationChange: function() { this.setOrientation(this.determineOrientation()); }, determineOrientation: function() { var me = this, orientationElement = me.orientationElement, nativeOrientation, visible; // First attempt will be to use Native Orientation information if (me.supportsOrientation()) { nativeOrientation = me.getWindowOrientation(); // 90 || -90 || 270 is landscape if (Math.abs(nativeOrientation) === 90 || nativeOrientation === 270) { return me.LANDSCAPE; } else { return me.PORTRAIT; } // Second attempt will be to use MatchMedia and a media query } else if (me.supportsMatchMedia()) { return window.matchMedia('(orientation : landscape)').matches ? me.LANDSCAPE : me.PORTRAIT; // Fall back on hidden element with media query attached to it (media query in Base Theme) } else if (orientationElement) { visible = orientationElement.first().isVisible(); return visible ? me.LANDSCAPE : me.PORTRAIT; } return null; }, updateOrientation: function(newValue, oldValue) { if (oldValue) { this.fireOrientationChangeEvent(newValue, oldValue); } }, fireOrientationChangeEvent: function(newOrientation, oldOrientation) { var me = this, newSize = me.updateSize(); Ext.getBody().replaceCls(Ext.baseCSSPrefix + oldOrientation, Ext.baseCSSPrefix + newOrientation); me.fireEvent('orientationchange', me, newOrientation, newSize.width, newSize.height); }, onWindowResize: function(width, height) { var me = this, oldWidth = me.lastSize.width, oldHeight = me.lastSize.height; me.updateSize(width, height); // On devices that do not support native orientation we use resize. // orientationchange events are only dispatched when there is an actual change in orientation value // so in cases on devices with orientation change events, the setter is called an extra time, but stopped after me.setOrientation(me.determineOrientation()); // Only fire the event if we have actually resized. if (width != null) { me.fireEvent('resize', this, width, height, oldWidth, oldHeight); } }, updateSize: function(width, height) { var lastSize = this.lastSize; lastSize.width = width !== undefined ? width : this.getWindowWidth(); lastSize.height = height !== undefined ? height : this.getWindowHeight(); return lastSize; }, waitUntil: function(condition, onSatisfied, onTimeout, delay, timeoutDuration) { if (!delay) { delay = 50; } if (!timeoutDuration) { timeoutDuration = 2000; } var scope = this, elapse = 0; Ext.defer(function repeat() { elapse += delay; if (condition.call(scope) === true) { if (onSatisfied) { onSatisfied.call(scope); } } else { if (elapse >= timeoutDuration) { if (onTimeout) { onTimeout.call(scope); } } else { Ext.defer(repeat, delay); } } }, delay); }, maximize: function() { this.fireMaximizeEvent(); }, fireMaximizeEvent: function() { this.updateSize(); this.fireEvent('maximize', this); }, updateHeight: function(height, oldHeight) { Ext.getBody().setHeight(height); this.callParent([height, oldHeight]); }, updateWidth: function(width, oldWidth) { Ext.getBody().setWidth(width); this.callParent([width, oldWidth]); }, scrollToTop: function() { window.scrollTo(0, -1); }, /** * Retrieves the document width. * @return {Number} width in pixels. */ getWindowWidth: function() { return window.innerWidth; }, /** * Retrieves the document height. * @return {Number} height in pixels. */ getWindowHeight: function() { return window.innerHeight; }, getWindowOuterHeight: function() { return window.outerHeight; }, getWindowOrientation: function() { return window.orientation; }, getSize: function() { return this.lastSize; }, setItemFullScreen: function(item) { item.addCls(this.fullscreenItemCls); item.setTop(0); item.setRight(0); item.setBottom(0); item.setLeft(0); this.add(item); }, /** * Sets a menu for a given side of the Viewport. * * Adds functionality to show the menu by swiping from the side of the screen from the given side. * * If a menu is already set for a given side, it will be removed. * * Available sides are: `left`, `right`, `top`, and `bottom`. * * **Note:** The `cover` and `reveal` animation configs are mutually exclusive. * Include only one animation config or omit both to default to `cover`. * * @param {Ext.Menu/Object} menu The menu instance or config to assign to the viewport. * @param {Object} config The configuration for the menu. * @param {String} config.side The side to put the menu on. * @param {Boolean} config.cover True to cover the viewport content. Defaults to `true`. * @param {Boolean} config.reveal True to push the menu alongside the viewport * content. Defaults to `false`. * * @return {Ext.Menu} The menu. */ setMenu: function(menu, config) { config = config || {}; //<debug> if (config.reveal && config.cover) { Ext.raise('[Ext.Viewport] setMenu(): Only one of reveal or cover allowed in config'); } //</debug> var me = this, side = config.side, sideValue = sideMap[side], menus, data, modal = menu.getModal ? menu.getModal() : null; // Temporary workaround for body shifting issue if (Ext.os.is.iOS && !me.hasiOSOrientationFix) { me.hasiOSOrientationFix = true; me.on('orientationchange', function() { window.scrollTo(0, 0); }, me); } //<debug> if (!menu) { Ext.Logger.error("You must specify a side to dock the menu."); } if (!side) { Ext.Logger.error("You must specify a side to dock the menu."); } if (!sideValue) { Ext.Logger.error("You must specify a valid side (left, right, top or bottom) to dock the menu."); } //</debug> menus = me.getMenus(); if (!me.addedSwipeListener) { me.attachSwipeListeners(); me.addedSwipeListener = true; } // If we have a menu cfg and no type was passed, we need to // setup the type. This template method exists to defer // for subclasses if (!menu.isComponent) { menu = me.getMenuCfg(menu, config); menu = Ext.create(menu); modal = menu.getModal(); } menu.isViewportMenu = true; menu.setShowAnimation(null); menu.setHideAnimation(null); menu.setMasked(false); if (menus[side]) { Ext.Viewport.hideMenu(side); menus[side].$side = null; } menus[side] = menu; menu.$reveal = Boolean(config.reveal); menu.$cover = config.cover !== false && !menu.$reveal; menu.$side = side; menu.setFloated(menu.$cover); menu.setHidden(true); menu.removeCls(Ext.baseCSSPrefix + (!menu.$cover ? 'menu-cover' : 'menu-reveal' )); menu.addCls(Ext.baseCSSPrefix + (menu.$cover ? 'menu-cover' : 'menu-reveal' )); menu.replaceCls(me.allSidesCls, me.sideClsMap[side]); me.fixMenuAttributes(menu, side); me.setMenus(menus); return menu; }, attachSwipeListeners: function() { var me = this; me.element.on({ tap: me.onTap, swipestart: me.onSwipeStart, edgeswipestart: me.onEdgeSwipeStart, edgeswipe: me.onEdgeSwipe, edgeswipeend: me.onEdgeSwipeEnd, scope: me }); }, getMenuCfg: function(menu, config) { if (!menu.xclass && !menu.xtype) { return Ext.apply({ xtype: 'actionsheet' }, menu); } else { return Ext.apply({ }, menu); } }, /** * Removes a menu from a specified side. * @param {String} side The side to remove the menu from */ removeMenu: function(side) { var me = this, menus = me.getMenus() || {}, menu = menus[side]; if (menu) { menu.$side = null; me.hideMenu(side, false); menu.removeCls(me.sideClsMap[side]); // remove mask, etc., from DOM menu.setFloated(false); } delete menus[side]; me.setMenus(menus); }, /** * @private * Changes the sizing and positionof the specified menu so that it displays correctly when shown. */ fixMenuAttributes: function(menu, side) { switch (side) { case 'left': menu.setLeft(0); menu.setRight(null); menu.setTop(0); menu.setBottom(0); break; case 'right': menu.setLeft(null); menu.setRight(0); menu.setTop(0); menu.setBottom(0); break; case 'top': menu.setLeft(0); menu.setRight(0); menu.setTop(0); menu.setBottom(null); break; case 'bottom': menu.setLeft(0); menu.setRight(0); menu.setTop(null); menu.setBottom(0); break; //<debug> default: Ext.raise('Invalid side ' + side); break; //</debug> } }, /** * Shows a menu specified by the menu's side. * @param {String} side The side which the menu is placed. */ showMenu: function(side) { var me = this, sideValue = sideMap[side], menus = me.getMenus(), menu = menus[side], before, after, viewportBefore, viewportAfter, size, floatParentNode = menu.floatParentNode, data = floatParentNode ? floatParentNode.getData() : {}; if (!menu || menu.isAnimating) { return; } me.hideOtherMenus(side); before = { translateX: 0, translateY: 0 }; after = { translateX: 0, translateY: 0 }; viewportBefore = { translateX: 0, translateY: 0 }; viewportAfter = { translateX: 0, translateY: 0 }; if (!menu.$cover) { Ext.getBody().insertFirst(menu.element); } else { Ext.Viewport.add(menu); } menu.removeCls(Ext.Viewport.getLayout().itemCls); menu.show(); menu.addCls('x-floating'); size = sideValue & (LEFT | RIGHT) ? menu.element.getWidth() : menu.element.getHeight(); if (sideValue === LEFT) { before.translateX = -size; viewportAfter.translateX = size; } else if (sideValue === RIGHT) { before.translateX = size; viewportAfter.translateX = -size; } else if (sideValue === TOP) { before.translateY = -size; viewportAfter.translateY = size; } else if (sideValue === BOTTOM) { before.translateY = size; viewportAfter.translateY = -size; } if (!menu.$cover) { menu.translate(0, 0); } else { menu.translate(before.translateX, before.translateY); } if (menu.$cover) { menu.getTranslatable().on('animationend', function() { menu.isAnimating = false; }, me, { single: true }); menu.translate(after.translateX, after.translateY, { preserveEndState: true, duration: 200 }); } else { me.translate(viewportBefore.translateX, viewportBefore.translateY); me.getTranslatable().on('animationend', function() { menu.isAnimating = false; }, me, { single: true }); me.translate(viewportAfter.translateX, viewportAfter.translateY, { preserveEndState: true, duration: 200 }); } // Make the menu as animating menu.isAnimating = true; }, /** * Hides a menu specified by the menu's side. * @param {String} side The side which the menu is placed. * @param {Boolean} animate if false, the menu will be hidden without animation. */ hideMenu: function(side, animate) { var me = this, sideValue = sideMap[side], menus = me.getMenus(), menu = menus[side], after, viewportAfter, size; animate = animate !== false; if (!menu || menu.isHidden() || menu.isAnimating) { return; } after = { translateX: 0, translateY: 0 }; viewportAfter = { translateX: 0, translateY: 0 }; size = sideValue & (LEFT | RIGHT) ? menu.element.getWidth() : menu.element.getHeight(); if (sideValue === LEFT) { after.translateX = -size; } else if (sideValue === RIGHT) { after.translateX = size; } else if (sideValue === TOP) { after.translateY = -size; } else if (sideValue === BOTTOM) { after.translateY = size; } if (menu.$cover) { if (animate) { menu.isAnimating = true; menu.getTranslatable().on('animationend', function() { if (!menu.destroyed) { menu.viewportIsHiding = true; menu.hide(); } menu.isAnimating = false; }, me, { single: true }); menu.translate(after.translateX, after.translateY, { preserveEndState: true, duration: 200 }); } else { menu.translate(after.translateX, after.translateY); if (!menu.destroyed) { menu.viewportIsHiding = true; menu.hide(); } } } else { if (animate) { menu.isAnimating = true; me.getTranslatable().on('animationend', function() { if (!menu.destroyed) { menu.viewportIsHiding = true; menu.hide(); } menu.isAnimating = false; }, me, { single: true }); me.translate(viewportAfter.translateX, viewportAfter.translateY, { preserveEndState: true, duration: 200 }); } else { me.translate(viewportAfter.translateX, viewportAfter.translateY); if (!menu.destroyed) { menu.viewportIsHiding = true; menu.hide(); } } } }, /** * Hides all visible menus. */ hideAllMenus: function(animation) { var menus = this.getMenus(), side; for (side in menus) { this.hideMenu(side, animation); } }, /** * Hides all menus except for the side specified * @param {String} side Side(s) not to hide * @param {String} animation Animation to hide with */ hideOtherMenus: function(side, animation){ var menus = this.getMenus(), menu; for (menu in menus) { if (side !== menu) { this.hideMenu(menu, animation); } } }, /** * Toggles the menu specified by side * @param {String} side The side which the menu is placed. */ toggleMenu: function(side) { var menus = this.getMenus(), menu; if (menus[side]) { menu = menus[side]; menu.setDisplayed(menu.isHidden()); } }, /** * @private */ sideForDirection: function(direction) { return oppositeSide[sideMap[direction]]; }, /** * @private */ sideForSwipeDirection: function(direction) { if (direction === 'up') { return 'top'; } else if (direction === 'down') { return 'bottom'; } return direction; }, /** * @private */ onTap: function(e) { // this.hideAllMenus(); }, /** * @private */ onSwipeStart: function(e) { var side = this.sideForSwipeDirection(e.direction), menu = this.getMenus()[side]; // preventing menu scrolling from being captured as viewport swiping if (menu && !menu.owns(e)) { this.hideMenu(side); } }, /** * @private */ onEdgeSwipeStart: function(e) { var me = this, side = me.sideForDirection(e.direction), menus = me.getMenus(), menu = menus[side], menuSide, checkMenu, size, after, viewportAfter, transformStyleName, setTransform; if (!menu || !menu.isHidden()) { return; } for (menuSide in menus) { checkMenu = menus[menuSide]; if (checkMenu.isHidden() !== false) { return; } } me.$swiping = true; me.hideAllMenus(false); // show the menu first so we can calculate the size if (menu.$reveal) { Ext.getBody().insertFirst(menu.element); } else { Ext.Viewport.add(menu); } menu.show(); size = side & (LEFT | RIGHT) ? menu.element.getWidth() : menu.element.getHeight(); after = { translateX: 0, translateY: 0 }; viewportAfter = { translateX: 0, translateY: 0 }; if (side ===LEFT) { after.translateX = -size; } else if (side === RIGHT) { after.translateX = size; } else if (side === TOP) { after.translateY = -size; } else if (side === 'BOTTOM') { after.translateY = size; } transformStyleName = 'webkitTransform' in document.createElement('div').style ? 'webkitTransform' : 'transform'; setTransform = menu.element.dom.style[transformStyleName]; if (setTransform) { menu.element.dom.style[transformStyleName] = ''; } if (menu.$reveal) { menu.translate(0, 0); } else { menu.translate(after.translateX, after.translateY); } if (!menu.$cover) { if (setTransform) { me.bodyElement.dom.style[transformStyleName] = ''; } me.translate(viewportAfter.translateX, viewportAfter.translateY); } }, /** * @private */ onEdgeSwipe: function(e) { var me = this, side = me.sideForDirection(e.direction), menu = me.getMenus()[side], size, after, viewportAfter, movement, viewportMovement; if (!menu || !me.$swiping) { return; } size = side & (LEFT | RIGHT) ? menu.element.getWidth() : menu.element.getHeight(); movement = Math.min(e.distance - size, 0); viewportMovement = Math.min(e.distance, size); after = { translateX: 0, translateY: 0 }; viewportAfter = { translateX: 0, translateY: 0 }; if (side === LEFT) { after.translateX = movement; viewportAfter.translateX = viewportMovement; } else if (side === RIGHT) { after.translateX = -movement; viewportAfter.translateX = -viewportMovement; } else if (side === TOP) { after.translateY = movement; viewportAfter.translateY = viewportMovement; } else if (side === BOTTOM) { after.translateY = -movement; viewportAfter.translateY = -viewportMovement; } if (menu.$cover) { menu.translate(after.translateX, after.translateY); } else { me.translate(viewportAfter.translateX, viewportAfter.translateY); } }, /** * @private */ onEdgeSwipeEnd: function(e) { var me = this, side = me.sideForDirection(e.direction), menu = me.getMenus()[side], shouldRevert = false, size, velocity, movement, viewportMovement, after, viewportAfter; if (!menu) { return; } size = side & (LEFT | RIGHT) ? menu.element.getWidth() : menu.element.getHeight(); velocity = (e.flick) ? e.flick.velocity : 0; // check if continuing in the right direction if (side === RIGHT) { if (velocity.x > 0) { shouldRevert = true; } } else if (side === LEFT) { if (velocity.x < 0) { shouldRevert = true; } } else if (side === TOP) { if (velocity.y < 0) { shouldRevert = true; } } else if (side === BOTTOM) { if (velocity.y > 0) { shouldRevert = true; } } movement = shouldRevert ? size : 0; viewportMovement = shouldRevert ? 0 : -size; after = { translateX: 0, translateY: 0 }; viewportAfter = { translateX: 0, translateY: 0 }; if (side === LEFT) { after.translateX = -movement; viewportAfter.translateX = -viewportMovement; } else if (side === RIGHT) { after.translateX = movement; viewportAfter.translateX = viewportMovement; } else if (side === TOP) { after.translateY = -movement; viewportAfter.translateY = -viewportMovement; } else if (side === BOTTOM) { after.translateY = movement; viewportAfter.translateY = viewportMovement; } // Move the viewport if cover is not enabled if (menu.$cover) { menu.getTranslatable().on('animationend', function() { if (shouldRevert) { menu.hide(); } }, me, { single: true }); menu.translate(after.translateX, after.translateY, { preserveEndState: true, duration: 200 }); } else { me.getTranslatable().on('animationend', function() { if (shouldRevert) { menu.hide(); } }, me, { single: true }); me.translate(viewportAfter.translateX, viewportAfter.translateY, { preserveEndState: true, duration: 200 }); } me.$swiping = false; }, doDestroy: function() { // If there are floated components, they might not be be being destroyed. // Move the floatRoot back into the document. It is "sticky". if (Ext.floatRoot) { document.body.appendChild(Ext.floatRoot.dom); delete this.floatWrap; Ext.floatRoot.getData().component = null; } Ext.GlobalEvents.un('resize', 'onWindowResize', this); this.callParent(); Ext.Viewport = null; }, privates: { addMeta: function(name, content) { var meta = document.createElement('meta'); meta.setAttribute('name', name); meta.setAttribute('content', content); Ext.getHead().append(meta); }, doAddListener: function(eventName, fn, scope, options, order, caller, manager) { var me = this; if (eventName === 'ready' && me.isReady && !me.isMaximizing) { fn.call(scope); return me; } me.callParent([eventName, fn, scope, options, order, caller, manager]); }, getRootEl: function() { return Ext.get(document.documentElement); }, toggleWindowListener: function(on, eventName, fn, capturing) { if (on) { this.addWindowListener(eventName, fn, capturing); } else { this.removeWindowListener(eventName, fn, capturing); } } } };});