]> git.proxmox.com Git - extjs.git/blame - extjs/modern/modern/src/viewport/Default.js
add extjs 6.0.1 sources
[extjs.git] / extjs / modern / modern / src / viewport / Default.js
CommitLineData
6527f429
DM
1/**\r
2 * @private\r
3 * Base class for iOS and Android viewports.\r
4 */\r
5Ext.define('Ext.viewport.Default', {\r
6 extend: 'Ext.Container',\r
7\r
8 xtype: 'viewport',\r
9\r
10 PORTRAIT: 'portrait',\r
11\r
12 LANDSCAPE: 'landscape',\r
13\r
14 requires: [\r
15 'Ext.GlobalEvents',\r
16 'Ext.LoadMask',\r
17 'Ext.layout.Card',\r
18 'Ext.util.InputBlocker'\r
19 ],\r
20\r
21 /**\r
22 * @event ready\r
23 * Fires when the Viewport is in the DOM and ready.\r
24 * @param {Ext.Viewport} this\r
25 */\r
26\r
27 /**\r
28 * @event maximize\r
29 * Fires when the Viewport is maximized.\r
30 * @param {Ext.Viewport} this\r
31 */\r
32\r
33 /**\r
34 * @event orientationchange\r
35 * Fires when the Viewport orientation has changed.\r
36 * @param {Ext.Viewport} this\r
37 * @param {String} newOrientation The new orientation.\r
38 * @param {Number} width The width of the Viewport.\r
39 * @param {Number} height The height of the Viewport.\r
40 */\r
41\r
42 config: {\r
43 /**\r
44 * @private\r
45 */\r
46 autoMaximize: false,\r
47\r
48 /**\r
49 * @private\r
50 *\r
51 * Auto blur the focused element when touching on a non-input. This is used to work around Android bugs\r
52 * where the virtual keyboard is not hidden when tapping outside an input.\r
53 */\r
54 autoBlurInput: true,\r
55\r
56 /**\r
57 * @cfg {Boolean} preventPanning\r
58 * Whether or not to always prevent default panning behavior of the\r
59 * browser's viewport.\r
60 * @accessor\r
61 */\r
62 preventPanning: true,\r
63\r
64 /**\r
65 * @cfg {Boolean} preventZooming\r
66 * `true` to attempt to stop zooming when you double tap on the screen on mobile devices,\r
67 * typically HTC devices with HTC Sense UI.\r
68 * @accessor\r
69 */\r
70 preventZooming: false,\r
71\r
72 /**\r
73 * @cfg\r
74 * @private\r
75 */\r
76 autoRender: true,\r
77\r
78 /**\r
79 * @cfg {Object/String} layout Configuration for this Container's layout. Example:\r
80 *\r
81 * Ext.create('Ext.Container', {\r
82 * layout: {\r
83 * type: 'hbox',\r
84 * align: 'middle'\r
85 * },\r
86 * items: [\r
87 * {\r
88 * xtype: 'panel',\r
89 * flex: 1,\r
90 * style: 'background-color: red;'\r
91 * },\r
92 * {\r
93 * xtype: 'panel',\r
94 * flex: 2,\r
95 * style: 'background-color: green'\r
96 * }\r
97 * ]\r
98 * });\r
99 *\r
100 * See the [layouts guide](#!/guides/layouts) for more information.\r
101 *\r
102 * @accessor\r
103 */\r
104 layout: 'card',\r
105\r
106 /**\r
107 * @cfg\r
108 * @private\r
109 */\r
110 width: '100%',\r
111\r
112 /**\r
113 * @cfg\r
114 * @private\r
115 */\r
116 height: '100%',\r
117\r
118 useBodyElement: true,\r
119\r
120 /**\r
121 * An object of all the menus on this viewport.\r
122 * @private\r
123 */\r
124 menus: {}\r
125 },\r
126\r
127 /**\r
128 * @property {Boolean} isReady\r
129 * `true` if the DOM is ready.\r
130 */\r
131 isReady: false,\r
132\r
133 isViewport: true,\r
134\r
135 isMaximizing: false,\r
136\r
137 id: 'ext-viewport',\r
138\r
139 isInputRegex: /^(input|textarea|select|a)$/i,\r
140\r
141 isInteractiveWebComponentRegEx: /^(audio|video)$/i,\r
142\r
143 focusedElement: null,\r
144\r
145 /**\r
146 * @private\r
147 */\r
148 fullscreenItemCls: Ext.baseCSSPrefix + 'fullscreen',\r
149\r
150 constructor: function(config) {\r
151 var me = this,\r
152 bind = Ext.Function.bind,\r
153 Component = Ext.Component,\r
154 DomScroller = Ext.scroll.DomScroller;\r
155\r
156 // By default document.body is monitored by a special DomScroller singleton so that\r
157 // the global scroll event fires when the document scrolls.\r
158 // A Viewport's Scroller will take over from this one.\r
159 if (DomScroller.document) {\r
160 DomScroller.document = DomScroller.document.destroy();\r
161 }\r
162\r
163 me.doPreventPanning = bind(me.doPreventPanning, me);\r
164 me.doPreventZooming = bind(me.doPreventZooming, me);\r
165 me.doBlurInput = bind(me.doBlurInput, me);\r
166\r
167 me.maximizeOnEvents = [\r
168 'ready',\r
169 'orientationchange'\r
170 ];\r
171\r
172 // set default devicePixelRatio if it is not explicitly defined\r
173 window.devicePixelRatio = window.devicePixelRatio || 1;\r
174\r
175 me.callParent([config]);\r
176\r
177 me.orientation = me.determineOrientation();\r
178 me.windowWidth = me.getWindowWidth();\r
179 me.windowHeight = me.getWindowHeight();\r
180 me.windowOuterHeight = me.getWindowOuterHeight();\r
181\r
182 me.stretchHeights = me.stretchHeights || {};\r
183\r
184 // Android is handled separately\r
185 if (!Ext.os.is.Android || Ext.browser.is.ChromeMobile) {\r
186 if (me.supportsOrientation()) {\r
187 me.addWindowListener('orientationchange', bind(me.onOrientationChange, me));\r
188 } else {\r
189 me.addWindowListener('resize', bind(me.onResize, me));\r
190 }\r
191 }\r
192\r
193 document.addEventListener('focus', bind(me.onElementFocus, me), true);\r
194 document.addEventListener('blur', bind(me.onElementBlur, me), true);\r
195\r
196 Ext.onDocumentReady(me.onDomReady, me);\r
197\r
198 if (!Component.on) {\r
199 Ext.util.Observable.observe(Component);\r
200 }\r
201\r
202 Component.on('fullscreen', 'onItemFullscreenChange', me);\r
203\r
204 return me;\r
205 },\r
206\r
207 initialize: function() {\r
208 var me = this;\r
209\r
210 me.addMeta('apple-mobile-web-app-capable', 'yes');\r
211 me.addMeta('apple-touch-fullscreen', 'yes');\r
212\r
213 me.callParent();\r
214 },\r
215\r
216 initInheritedState: function (inheritedState, inheritedStateInner) {\r
217 var me = this,\r
218 root = Ext.rootInheritedState;\r
219\r
220 if (inheritedState !== root) {\r
221 // We need to go at this again but with the rootInheritedState object. Let\r
222 // any derived class poke on the proper object!\r
223 me.initInheritedState(me.inheritedState = root,\r
224 me.inheritedStateInner = Ext.Object.chain(root));\r
225 } else {\r
226 me.callParent([inheritedState, inheritedStateInner]);\r
227 }\r
228 },\r
229\r
230 onAppLaunch: function() {\r
231 var me = this;\r
232 if (!me.isReady) {\r
233 me.onDomReady();\r
234 }\r
235 },\r
236\r
237 onDomReady: function() {\r
238 var me = this;\r
239\r
240 if (me.isReady) {\r
241 return;\r
242 }\r
243\r
244 me.isReady = true;\r
245 me.updateSize();\r
246 me.onReady();\r
247 me.fireEvent('ready', me);\r
248 Ext.GlobalEvents.fireEvent('viewportready', me);\r
249 },\r
250\r
251 onReady: function() {\r
252 if (this.getAutoRender()) {\r
253 this.render();\r
254 }\r
255 if (Ext.browser.name === 'ChromeiOS') {\r
256 this.setHeight('-webkit-calc(100% - ' + ((window.outerHeight - window.innerHeight) / 2) + 'px)');\r
257 }\r
258 },\r
259\r
260 onElementFocus: function(e) {\r
261 this.focusedElement = e.target;\r
262 },\r
263\r
264 onElementBlur: function() {\r
265 this.focusedElement = null;\r
266 },\r
267\r
268 render: function() {\r
269 if (!this.rendered) {\r
270 var body = Ext.getBody(),\r
271 clsPrefix = Ext.baseCSSPrefix,\r
272 classList = [],\r
273 osEnv = Ext.os,\r
274 osName = osEnv.name.toLowerCase(),\r
275 browserName = Ext.browser.name.toLowerCase(),\r
276 osMajorVersion = osEnv.version.getMajor(),\r
277 orientation = this.getOrientation(),\r
278 theme;\r
279\r
280 this.renderTo(body);\r
281\r
282 classList.push(clsPrefix + osEnv.deviceType.toLowerCase());\r
283\r
284 if (osEnv.is.iPad) {\r
285 classList.push(clsPrefix + 'ipad');\r
286 }\r
287\r
288 classList.push(clsPrefix + osName);\r
289 classList.push(clsPrefix + browserName);\r
290\r
291 if (osMajorVersion) {\r
292 classList.push(clsPrefix + osName + '-' + osMajorVersion);\r
293 }\r
294\r
295 if (osEnv.is.BlackBerry) {\r
296 classList.push(clsPrefix + 'bb');\r
297 if (Ext.browser.userAgent.match(/Kbd/gi)) {\r
298 classList.push(clsPrefix + 'bb-keyboard');\r
299 }\r
300 }\r
301\r
302 if (Ext.browser.is.WebKit) {\r
303 classList.push(clsPrefix + 'webkit');\r
304 }\r
305\r
306 if (Ext.browser.is.Standalone) {\r
307 classList.push(clsPrefix + 'standalone');\r
308 }\r
309\r
310 if (Ext.browser.is.AndroidStock) {\r
311 classList.push(clsPrefix + 'android-stock');\r
312 }\r
313\r
314 if (Ext.browser.is.GoogleGlass) {\r
315 classList.push(clsPrefix + 'google-glass');\r
316 }\r
317\r
318 classList.push(clsPrefix + orientation);\r
319\r
320 body.addCls(classList);\r
321\r
322 theme = Ext.theme;\r
323 if (theme && theme.getDocCls) {\r
324 // hook for theme overrides to add css classes to the <html> element\r
325 Ext.fly(document.documentElement).addCls(theme.getDocCls());\r
326 }\r
327 }\r
328 },\r
329\r
330 updateAutoBlurInput: function(autoBlurInput) {\r
331 var touchstart = Ext.feature.has.TouchEvents ? 'touchstart' : 'mousedown';\r
332 this.toggleWindowListener(autoBlurInput, touchstart, this.doBlurInput, false);\r
333 },\r
334\r
335 applyAutoMaximize: function(autoMaximize) {\r
336 return Ext.browser.is.WebView ? false : autoMaximize;\r
337 },\r
338\r
339 updateAutoMaximize: function(autoMaximize) {\r
340 var me = this;\r
341\r
342 if (autoMaximize) {\r
343 me.on('ready', 'doAutoMaximizeOnReady', me, { single: true });\r
344 me.on('orientationchange', 'doAutoMaximizeOnOrientationChange', me);\r
345 } else {\r
346 me.un('ready', 'doAutoMaximizeOnReady', me);\r
347 me.un('orientationchange', 'doAutoMaximizeOnOrientationChange', me);\r
348 }\r
349 },\r
350\r
351 updatePreventPanning: function(preventPanning) {\r
352 this.toggleWindowListener(preventPanning, 'touchmove', this.doPreventPanning, false);\r
353 },\r
354\r
355 updatePreventZooming: function(preventZooming) {\r
356 var touchstart = Ext.feature.has.TouchEvents ? 'touchstart' : 'mousedown';\r
357 this.toggleWindowListener(preventZooming, touchstart, this.doPreventZooming, false);\r
358 },\r
359\r
360 doAutoMaximizeOnReady: function() {\r
361 var me = this;\r
362\r
363 me.isMaximizing = true;\r
364\r
365 me.on('maximize', function() {\r
366 me.isMaximizing = false;\r
367\r
368 me.updateSize();\r
369\r
370 me.fireEvent('ready', me);\r
371 }, me, { single: true });\r
372\r
373 me.maximize();\r
374 },\r
375\r
376 doAutoMaximizeOnOrientationChange: function() {\r
377 var me = this;\r
378\r
379 me.isMaximizing = true;\r
380\r
381 me.on('maximize', function() {\r
382 me.isMaximizing = false;\r
383\r
384 me.updateSize();\r
385 }, me, { single: true });\r
386\r
387 me.maximize();\r
388 },\r
389\r
390 doBlurInput: function(e) {\r
391 var target = e.target,\r
392 focusedElement = this.focusedElement;\r
393 //In IE9/10 browser window loses focus and becomes inactive if focused element is <body>. So we shouldn't call blur for <body>\r
394 // In FF, the focusedElement can be the document which doesn't have a blur method\r
395 if (focusedElement && focusedElement.blur && focusedElement.nodeName.toUpperCase() != 'BODY' && !this.isInputRegex.test(target.tagName)) {\r
396 delete this.focusedElement;\r
397 focusedElement.blur();\r
398 }\r
399 },\r
400\r
401 doPreventPanning: function(e) {\r
402 var target = e.target, \r
403 touch;\r
404\r
405 // If we have an interaction on a WebComponent we need to check the actual shadow dom element selected\r
406 // to determine if it is an input before preventing default behavior\r
407 // Side effect to this is if the shadow input does not do anything with 'touchmove' the user could pan\r
408 // the screen.\r
409 if (this.isInteractiveWebComponentRegEx.test(target.tagName) && e.touches && e.touches.length > 0) {\r
410 touch = e.touches[0];\r
411 if (touch && touch.target && this.isInputRegex.test(touch.target.tagName)) {\r
412 return;\r
413 }\r
414 }\r
415\r
416 if (target && target.nodeType === 1 && !this.isInputRegex.test(target.tagName)) {\r
417 e.preventDefault();\r
418 }\r
419 },\r
420\r
421 doPreventZooming: function(e) {\r
422 // Don't prevent right mouse event\r
423 if ('button' in e && e.button !== 0) {\r
424 return;\r
425 }\r
426\r
427 var target = e.target, \r
428 inputRe = this.isInputRegex,\r
429 touch;\r
430\r
431 if (this.isInteractiveWebComponentRegEx.test(target.tagName) && e.touches && e.touches.length > 0) {\r
432 touch = e.touches[0];\r
433 if (touch && touch.target && inputRe.test(touch.target.tagName)) {\r
434 return;\r
435 }\r
436 }\r
437\r
438 if (target && target.nodeType === 1 && !inputRe.test(target.tagName)) {\r
439 e.preventDefault();\r
440 }\r
441 },\r
442\r
443 addWindowListener: function(eventName, fn, capturing) {\r
444 window.addEventListener(eventName, fn, Boolean(capturing));\r
445 },\r
446\r
447 removeWindowListener: function(eventName, fn, capturing) {\r
448 window.removeEventListener(eventName, fn, Boolean(capturing));\r
449 },\r
450\r
451 supportsOrientation: function() {\r
452 return Ext.feature.has.Orientation;\r
453 },\r
454\r
455 onResize: function() {\r
456 var me = this,\r
457 oldWidth = me.windowWidth,\r
458 oldHeight = me.windowHeight,\r
459 width = me.getWindowWidth(),\r
460 height = me.getWindowHeight(),\r
461 currentOrientation = me.getOrientation(),\r
462 newOrientation = me.determineOrientation();\r
463\r
464 // Determine orientation change via resize. BOTH width AND height much change, otherwise\r
465 // this is a keyboard popping up.\r
466 if ((oldWidth !== width && oldHeight !== height) && currentOrientation !== newOrientation) {\r
467 me.fireOrientationChangeEvent(newOrientation, currentOrientation);\r
468 }\r
469 },\r
470\r
471 onOrientationChange: function() {\r
472 var currentOrientation = this.getOrientation(),\r
473 newOrientation = this.determineOrientation();\r
474\r
475 if (newOrientation !== currentOrientation) {\r
476 this.fireOrientationChangeEvent(newOrientation, currentOrientation);\r
477 }\r
478 },\r
479\r
480 fireOrientationChangeEvent: function(newOrientation, oldOrientation) {\r
481 var me = this,\r
482 clsPrefix = Ext.baseCSSPrefix;\r
483\r
484 Ext.getBody().replaceCls(clsPrefix + oldOrientation, clsPrefix + newOrientation);\r
485\r
486 me.orientation = newOrientation;\r
487\r
488 me.updateSize();\r
489 me.fireEvent('orientationchange', me, newOrientation, me.windowWidth, me.windowHeight);\r
490 },\r
491\r
492 updateSize: function(width, height) {\r
493 var me = this;\r
494\r
495 me.windowWidth = width !== undefined ? width : me.getWindowWidth();\r
496 me.windowHeight = height !== undefined ? height : me.getWindowHeight();\r
497\r
498 return me;\r
499 },\r
500\r
501 waitUntil: function(condition, onSatisfied, onTimeout, delay, timeoutDuration) {\r
502 if (!delay) {\r
503 delay = 50;\r
504 }\r
505\r
506 if (!timeoutDuration) {\r
507 timeoutDuration = 2000;\r
508 }\r
509\r
510 var scope = this,\r
511 elapse = 0;\r
512\r
513 Ext.defer(function repeat() {\r
514 elapse += delay;\r
515\r
516 if (condition.call(scope) === true) {\r
517 if (onSatisfied) {\r
518 onSatisfied.call(scope);\r
519 }\r
520 }\r
521 else {\r
522 if (elapse >= timeoutDuration) {\r
523 if (onTimeout) {\r
524 onTimeout.call(scope);\r
525 }\r
526 }\r
527 else {\r
528 Ext.defer(repeat, delay);\r
529 }\r
530 }\r
531 }, delay);\r
532 },\r
533\r
534 maximize: function() {\r
535 this.fireMaximizeEvent();\r
536 },\r
537\r
538 fireMaximizeEvent: function() {\r
539 this.updateSize();\r
540 this.fireEvent('maximize', this);\r
541 },\r
542\r
543 updateHeight: function(height, oldHeight) {\r
544 Ext.getBody().setHeight(height);\r
545 this.callParent([height, oldHeight]);\r
546 },\r
547\r
548 updateWidth: function(width, oldWidth) {\r
549 Ext.getBody().setWidth(width);\r
550 this.callParent([width, oldWidth]);\r
551 },\r
552\r
553 scrollToTop: function() {\r
554 window.scrollTo(0, -1);\r
555 },\r
556\r
557 /**\r
558 * Retrieves the document width.\r
559 * @return {Number} width in pixels.\r
560 */\r
561 getWindowWidth: function() {\r
562 return window.innerWidth;\r
563 },\r
564\r
565 /**\r
566 * Retrieves the document height.\r
567 * @return {Number} height in pixels.\r
568 */\r
569 getWindowHeight: function() {\r
570 return window.innerHeight;\r
571 },\r
572\r
573 getWindowOuterHeight: function() {\r
574 return window.outerHeight;\r
575 },\r
576\r
577 getWindowOrientation: function() {\r
578 return window.orientation;\r
579 },\r
580\r
581 /**\r
582 * Returns the current orientation.\r
583 * @return {String} `portrait` or `landscape`\r
584 */\r
585 getOrientation: function() {\r
586 return this.orientation;\r
587 },\r
588\r
589 getSize: function() {\r
590 return {\r
591 width: this.windowWidth,\r
592 height: this.windowHeight\r
593 };\r
594 },\r
595\r
596 determineOrientation: function() {\r
597 var me = this,\r
598 portrait = me.PORTRAIT,\r
599 landscape = me.LANDSCAPE;\r
600\r
601 if (!Ext.os.is.Android && me.supportsOrientation()) {\r
602 if (me.getWindowOrientation() % 180 === 0) {\r
603 return portrait;\r
604 }\r
605\r
606 return landscape;\r
607 }\r
608 else {\r
609 if (me.getWindowHeight() >= me.getWindowWidth()) {\r
610 return portrait;\r
611 }\r
612\r
613 return landscape;\r
614 }\r
615 },\r
616\r
617 onItemFullscreenChange: function(item) {\r
618 item.addCls(this.fullscreenItemCls);\r
619 this.add(item);\r
620 },\r
621\r
622 /**\r
623 * Sets a menu for a given side of the Viewport.\r
624 *\r
625 * Adds functionality to show the menu by swiping from the side of the screen from the given side.\r
626 *\r
627 * If a menu is already set for a given side, it will be removed.\r
628 *\r
629 * Available sides are: `left`, `right`, `top`, and `bottom`.\r
630 *\r
631 * @param {Ext.Menu/Object} menu The menu instance or config to assign to the viewport.\r
632 * @param {Object} config The configuration for the menu.\r
633 * @param {String} config.side The side to put the menu on.\r
634 * @param {Boolean} config.cover True to cover the viewport content. Defaults to `true`.\r
635 *\r
636 * @return {Ext.Menu} The menu.\r
637 */\r
638 setMenu: function(menu, config) {\r
639 config = config || {};\r
640\r
641 var me = this,\r
642 side = config.side,\r
643 menus;\r
644\r
645 // Temporary workaround for body shifting issue\r
646 if (Ext.os.is.iOS && !me.hasiOSOrientationFix) {\r
647 me.hasiOSOrientationFix = true;\r
648 me.on('orientationchange', function() {\r
649 window.scrollTo(0, 0);\r
650 }, me);\r
651 }\r
652\r
653 //<debug>\r
654 if (!menu) {\r
655 Ext.Logger.error("You must specify a side to dock the menu.");\r
656 }\r
657\r
658 if (!side) {\r
659 Ext.Logger.error("You must specify a side to dock the menu.");\r
660 }\r
661\r
662 if (['left', 'right', 'top', 'bottom'].indexOf(side) == -1) {\r
663 Ext.Logger.error("You must specify a valid side (left, right, top or botom) to dock the menu.");\r
664 }\r
665 //</debug>\r
666\r
667 menus = me.getMenus();\r
668\r
669 if (!menus) {\r
670 menus = {};\r
671 }\r
672\r
673 if (!me.addedSwipeListener) {\r
674 me.attachSwipeListeners();\r
675 me.addedSwipeListener = true;\r
676 }\r
677\r
678 // If we have a menu cfg and no type was passed, we need to\r
679 // setup the type. This template method exists to defer\r
680 // for subclasses\r
681 if (!menu.isComponent) {\r
682 if (!menu.xclass && !menu.xtype) {\r
683 menu = me.getMenuCfg(menu, side);\r
684 }\r
685 menu = Ext.create(menu);\r
686 }\r
687\r
688 menus[side] = menu;\r
689 menu.$reveal = Boolean(config.reveal);\r
690 menu.$cover = config.cover !== false && !menu.$reveal;\r
691 menu.$side = side;\r
692\r
693 me.fixMenuSize(menu, side);\r
694\r
695 if (side == 'left') {\r
696 menu.setLeft(0);\r
697 menu.setRight(null);\r
698 menu.setTop(0);\r
699 menu.setBottom(0);\r
700 } else if (side == 'right') {\r
701 menu.setLeft(null);\r
702 menu.setRight(0);\r
703 menu.setTop(0);\r
704 menu.setBottom(0);\r
705 } else if (side == 'top') {\r
706 menu.setLeft(0);\r
707 menu.setRight(0);\r
708 menu.setTop(0);\r
709 menu.setBottom(null);\r
710 } else if (side == 'bottom') {\r
711 menu.setLeft(0);\r
712 menu.setRight(0);\r
713 menu.setTop(null);\r
714 menu.setBottom(0);\r
715 }\r
716\r
717 me.setMenus(menus);\r
718\r
719 return menu;\r
720 },\r
721\r
722 attachSwipeListeners: function() {\r
723 var me = this;\r
724\r
725 me.element.on({\r
726 tap: me.onTap,\r
727 swipestart: me.onSwipeStart,\r
728 edgeswipestart: me.onEdgeSwipeStart,\r
729 edgeswipe: me.onEdgeSwipe,\r
730 edgeswipeend: me.onEdgeSwipeEnd,\r
731 scope: me\r
732 });\r
733 },\r
734\r
735 getMenuCfg: function(menu, side) {\r
736 return Ext.apply({\r
737 xtype: 'menu'\r
738 }, menu);\r
739 },\r
740\r
741 /**\r
742 * Removes a menu from a specified side.\r
743 * @param {String} side The side to remove the menu from\r
744 */\r
745 removeMenu: function(side) {\r
746 var menus = this.getMenus() || {},\r
747 menu = menus[side];\r
748\r
749 if (menu) {\r
750 this.hideMenu(side);\r
751 }\r
752 delete menus[side];\r
753 this.setMenus(menus);\r
754 },\r
755\r
756 /**\r
757 * @private\r
758 * Changes the sizing of the specified menu so that it displays correctly when shown.\r
759 */\r
760 fixMenuSize: function(menu, side) {\r
761 if (side == 'top' || side == 'bottom') {\r
762 menu.setWidth('100%');\r
763 } else if (side == 'left' || side == 'right') {\r
764 menu.setHeight('100%');\r
765 }\r
766 },\r
767\r
768 /**\r
769 * Shows a menu specified by the menu's side.\r
770 * @param {String} side The side which the menu is placed.\r
771 */\r
772 showMenu: function(side) {\r
773 var me = this,\r
774 menus = me.getMenus(),\r
775 menu = menus[side],\r
776 before, after,\r
777 viewportBefore, viewportAfter, size;\r
778\r
779 if (!menu || menu.isAnimating) {\r
780 return;\r
781 }\r
782\r
783 me.hideOtherMenus(side);\r
784\r
785 before = {\r
786 translateX: 0,\r
787 translateY: 0\r
788 };\r
789\r
790 after = {\r
791 translateX: 0,\r
792 translateY: 0\r
793 };\r
794\r
795 viewportBefore = {\r
796 translateX: 0,\r
797 translateY: 0\r
798 };\r
799\r
800 viewportAfter = {\r
801 translateX: 0,\r
802 translateY: 0\r
803 };\r
804\r
805 if (menu.$reveal) {\r
806 Ext.getBody().insertFirst(menu.element);\r
807 } else {\r
808 Ext.Viewport.add(menu);\r
809 }\r
810\r
811 menu.show();\r
812 menu.addCls('x-' + side);\r
813\r
814 size = (side == 'left' || side == 'right') ? menu.element.getWidth() : menu.element.getHeight();\r
815\r
816 if (side == 'left') {\r
817 before.translateX = -size;\r
818 viewportAfter.translateX = size;\r
819 } else if (side == 'right') {\r
820 before.translateX = size;\r
821 viewportAfter.translateX = -size;\r
822 } else if (side == 'top') {\r
823 before.translateY = -size;\r
824 viewportAfter.translateY = size;\r
825 } else if (side == 'bottom') {\r
826 before.translateY = size;\r
827 viewportAfter.translateY = -size;\r
828 }\r
829\r
830 if (menu.$reveal) {\r
831 if (Ext.browser.getPreferredTranslationMethod() != 'scrollposition') {\r
832 menu.translate(0, 0);\r
833 }\r
834 } else {\r
835 menu.translate(before.translateX, before.translateY);\r
836 }\r
837\r
838 if (menu.$cover) {\r
839 menu.getTranslatable().on('animationend', function() {\r
840 menu.isAnimating = false;\r
841 }, me, {\r
842 single: true\r
843 });\r
844\r
845 menu.translate(after.translateX, after.translateY, {\r
846 preserveEndState: true,\r
847 duration: 200\r
848 });\r
849\r
850 } else {\r
851 me.translate(viewportBefore.translateX, viewportBefore.translateY);\r
852\r
853\r
854 me.getTranslatable().on('animationend', function() {\r
855 menu.isAnimating = false;\r
856 }, me, {\r
857 single: true\r
858 });\r
859\r
860 me.translate(viewportAfter.translateX, viewportAfter.translateY, {\r
861 preserveEndState: true,\r
862 duration: 200\r
863 });\r
864 }\r
865\r
866 // Make the menu as animating\r
867 menu.isAnimating = true;\r
868 },\r
869\r
870 /**\r
871 * Hides a menu specified by the menu's side.\r
872 * @param {String} side The side which the menu is placed.\r
873 */\r
874 hideMenu: function(side, animate) {\r
875 var me = this,\r
876 menus = this.getMenus(),\r
877 menu = menus[side],\r
878 after, viewportAfter,\r
879 size;\r
880\r
881 animate = animate !== false;\r
882\r
883 if (!menu || (menu.isHidden() || menu.isAnimating)) {\r
884 return;\r
885 }\r
886\r
887 after = {\r
888 translateX: 0,\r
889 translateY: 0\r
890 };\r
891\r
892 viewportAfter = {\r
893 translateX: 0,\r
894 translateY: 0\r
895 };\r
896\r
897 size = (side == 'left' || side == 'right') ? menu.element.getWidth() : menu.element.getHeight();\r
898\r
899 if (side == 'left') {\r
900 after.translateX = -size;\r
901 } else if (side == 'right') {\r
902 after.translateX = size;\r
903 } else if (side == 'top') {\r
904 after.translateY = -size;\r
905 } else if (side == 'bottom') {\r
906 after.translateY = size;\r
907 }\r
908\r
909 if (menu.$cover) {\r
910 if (animate) {\r
911 menu.getTranslatable().on('animationend', function() {\r
912 menu.isAnimating = false;\r
913 menu.hide();\r
914 }, me, {\r
915 single: true\r
916 });\r
917\r
918 menu.translate(after.translateX, after.translateY, {\r
919 preserveEndState: true,\r
920 duration: 200\r
921 });\r
922 } else {\r
923 menu.translate(after.translateX, after.translateY);\r
924 menu.hide()\r
925 }\r
926 } else {\r
927 if (animate) {\r
928 me.getTranslatable().on('animationend', function() {\r
929 menu.isAnimating = false;\r
930 menu.hide();\r
931 }, me, {\r
932 single: true\r
933 });\r
934\r
935 me.translate(viewportAfter.translateX, viewportAfter.translateY, {\r
936 preserveEndState: true,\r
937 duration: 200\r
938 });\r
939 } else {\r
940 me.translate(viewportAfter.translateX, viewportAfter.translateY);\r
941 menu.hide();\r
942 }\r
943 }\r
944 },\r
945\r
946 /**\r
947 * Hides all visible menus.\r
948 */\r
949 hideAllMenus: function(animation) {\r
950 var menus = this.getMenus(),\r
951 side;\r
952\r
953 for (side in menus) {\r
954 this.hideMenu(side, animation);\r
955 }\r
956 },\r
957\r
958 /**\r
959 * Hides all menus except for the side specified\r
960 * @param {String} side Side(s) not to hide\r
961 * @param {String} animation Animation to hide with\r
962 */\r
963 hideOtherMenus: function(side, animation){\r
964 var menus = this.getMenus(),\r
965 menu;\r
966\r
967 for (menu in menus) {\r
968 if (side !== menu) {\r
969 this.hideMenu(menu, animation);\r
970 }\r
971 }\r
972 },\r
973\r
974 /**\r
975 * Toggles the menu specified by side\r
976 * @param {String} side The side which the menu is placed.\r
977 */\r
978 toggleMenu: function(side) {\r
979 var menus = this.getMenus(), \r
980 menu;\r
981\r
982 if (menus[side]) {\r
983 menu = menus[side];\r
984 if (menu.isHidden()) {\r
985 this.showMenu(side);\r
986 } else {\r
987 this.hideMenu(side);\r
988 }\r
989 }\r
990 },\r
991\r
992 /**\r
993 * @private\r
994 */\r
995 sideForDirection: function(direction) {\r
996 if (direction === 'left') {\r
997 return 'right';\r
998 } else if (direction === 'right') {\r
999 return 'left';\r
1000 } else if (direction == 'up') {\r
1001 return 'bottom';\r
1002 } else if (direction == 'down') {\r
1003 return 'top';\r
1004 }\r
1005 },\r
1006\r
1007 /**\r
1008 * @private\r
1009 */\r
1010 sideForSwipeDirection: function(direction) {\r
1011 if (direction == 'up') {\r
1012 return 'top';\r
1013 } else if (direction == 'down') {\r
1014 return 'bottom';\r
1015 }\r
1016 return direction;\r
1017 },\r
1018\r
1019 /**\r
1020 * @private\r
1021 */\r
1022 onTap: function(e) {\r
1023 // this.hideAllMenus();\r
1024 },\r
1025\r
1026 /**\r
1027 * @private\r
1028 */\r
1029 onSwipeStart: function(e) {\r
1030 var side = this.sideForSwipeDirection(e.direction);\r
1031 this.hideMenu(side);\r
1032 },\r
1033\r
1034 /**\r
1035 * @private\r
1036 */\r
1037 onEdgeSwipeStart: function(e) {\r
1038 var me = this,\r
1039 side = me.sideForDirection(e.direction),\r
1040 menus = me.getMenus(),\r
1041 menu = menus[side],\r
1042 menuSide, checkMenu, size,\r
1043 after, viewportAfter,\r
1044 transformStyleName, setTransform;\r
1045\r
1046 if (!menu || !menu.isHidden()) {\r
1047 return;\r
1048 }\r
1049\r
1050 for (menuSide in menus) {\r
1051 checkMenu = menus[menuSide];\r
1052 if (checkMenu.isHidden() !== false) {\r
1053 return;\r
1054 }\r
1055 }\r
1056\r
1057 me.$swiping = true;\r
1058\r
1059 me.hideAllMenus(false);\r
1060\r
1061 // show the menu first so we can calculate the size\r
1062 if (menu.$reveal) {\r
1063 Ext.getBody().insertFirst(menu.element);\r
1064 } else {\r
1065 Ext.Viewport.add(menu);\r
1066 }\r
1067 menu.show();\r
1068\r
1069 size = (side == 'left' || side == 'right') ? menu.element.getWidth() : menu.element.getHeight();\r
1070\r
1071 after = {\r
1072 translateX: 0,\r
1073 translateY: 0\r
1074 };\r
1075\r
1076 viewportAfter = {\r
1077 translateX: 0,\r
1078 translateY: 0\r
1079 };\r
1080\r
1081 if (side == 'left') {\r
1082 after.translateX = -size;\r
1083 } else if (side == 'right') {\r
1084 after.translateX = size;\r
1085 } else if (side == 'top') {\r
1086 after.translateY = -size;\r
1087 } else if (side == 'bottom') {\r
1088 after.translateY = size;\r
1089 }\r
1090\r
1091 transformStyleName = 'webkitTransform' in document.createElement('div').style ? 'webkitTransform' : 'transform';\r
1092 setTransform = menu.element.dom.style[transformStyleName];\r
1093\r
1094 if (setTransform) {\r
1095 menu.element.dom.style[transformStyleName] = '';\r
1096 }\r
1097\r
1098 if (menu.$reveal) {\r
1099 if (Ext.browser.getPreferredTranslationMethod() != 'scrollposition') {\r
1100 menu.translate(0, 0);\r
1101 }\r
1102 } else {\r
1103 menu.translate(after.translateX, after.translateY);\r
1104 }\r
1105\r
1106 if (!menu.$cover) {\r
1107 if (setTransform) {\r
1108 me.innerElement.dom.style[transformStyleName] = '';\r
1109 }\r
1110\r
1111 me.translate(viewportAfter.translateX, viewportAfter.translateY);\r
1112 }\r
1113 },\r
1114\r
1115 /**\r
1116 * @private\r
1117 */\r
1118 onEdgeSwipe: function(e) {\r
1119 var me = this,\r
1120 side = me.sideForDirection(e.direction),\r
1121 menu = me.getMenus()[side],\r
1122 size, after, viewportAfter,\r
1123 movement, viewportMovement;\r
1124\r
1125 if (!menu || !me.$swiping) {\r
1126 return;\r
1127 }\r
1128\r
1129 size = (side == 'left' || side == 'right') ? menu.element.getWidth() : menu.element.getHeight();\r
1130 movement = Math.min(e.distance - size, 0);\r
1131 viewportMovement = Math.min(e.distance, size);\r
1132\r
1133 after = {\r
1134 translateX: 0,\r
1135 translateY: 0\r
1136 };\r
1137\r
1138 viewportAfter = {\r
1139 translateX: 0,\r
1140 translateY: 0\r
1141 };\r
1142\r
1143 if (side == 'left') {\r
1144 after.translateX = movement;\r
1145 viewportAfter.translateX = viewportMovement;\r
1146 } else if (side == 'right') {\r
1147 after.translateX = -movement;\r
1148 viewportAfter.translateX = -viewportMovement;\r
1149 } else if (side == 'top') {\r
1150 after.translateY = movement;\r
1151 viewportAfter.translateY = viewportMovement;\r
1152 } else if (side == 'bottom') {\r
1153 after.translateY = -movement;\r
1154 viewportAfter.translateY = -viewportMovement;\r
1155 }\r
1156\r
1157 if (menu.$cover) {\r
1158 menu.translate(after.translateX, after.translateY);\r
1159 } else {\r
1160 me.translate(viewportAfter.translateX, viewportAfter.translateY);\r
1161 }\r
1162 },\r
1163\r
1164 /**\r
1165 * @private\r
1166 */\r
1167 onEdgeSwipeEnd: function(e) {\r
1168 var me = this,\r
1169 side = me.sideForDirection(e.direction),\r
1170 menu = me.getMenus()[side],\r
1171 shouldRevert = false,\r
1172 size, velocity, movement, viewportMovement,\r
1173 after, viewportAfter;\r
1174\r
1175 if (!menu) {\r
1176 return;\r
1177 }\r
1178\r
1179 size = (side == 'left' || side == 'right') ? menu.element.getWidth() : menu.element.getHeight();\r
1180 velocity = (e.flick) ? e.flick.velocity : 0;\r
1181\r
1182 // check if continuing in the right direction\r
1183 if (side == 'right') {\r
1184 if (velocity.x > 0) {\r
1185 shouldRevert = true;\r
1186 }\r
1187 } else if (side == 'left') {\r
1188 if (velocity.x < 0) {\r
1189 shouldRevert = true;\r
1190 }\r
1191 } else if (side == 'top') {\r
1192 if (velocity.y < 0) {\r
1193 shouldRevert = true;\r
1194 }\r
1195 } else if (side == 'bottom') {\r
1196 if (velocity.y > 0) {\r
1197 shouldRevert = true;\r
1198 }\r
1199 }\r
1200\r
1201 movement = shouldRevert ? size : 0;\r
1202 viewportMovement = shouldRevert ? 0 : -size;\r
1203\r
1204 after = {\r
1205 translateX: 0,\r
1206 translateY: 0\r
1207 };\r
1208\r
1209 viewportAfter = {\r
1210 translateX: 0,\r
1211 translateY: 0\r
1212 };\r
1213\r
1214 if (side == 'left') {\r
1215 after.translateX = -movement;\r
1216 viewportAfter.translateX = -viewportMovement;\r
1217 } else if (side == 'right') {\r
1218 after.translateX = movement;\r
1219 viewportAfter.translateX = viewportMovement;\r
1220 } else if (side == 'top') {\r
1221 after.translateY = -movement;\r
1222 viewportAfter.translateY = -viewportMovement;\r
1223 } else if (side == 'bottom') {\r
1224 after.translateY = movement;\r
1225 viewportAfter.translateY = viewportMovement;\r
1226 }\r
1227\r
1228 // Move the viewport if cover is not enabled\r
1229 if (menu.$cover) {\r
1230 menu.getTranslatable().on('animationend', function() {\r
1231 if (shouldRevert) {\r
1232 menu.hide();\r
1233 }\r
1234 }, me, {\r
1235 single: true\r
1236 });\r
1237\r
1238 menu.translate(after.translateX, after.translateY, {\r
1239 preserveEndState: true,\r
1240 duration: 200\r
1241 });\r
1242\r
1243 } else {\r
1244 me.getTranslatable().on('animationend', function() {\r
1245 if (shouldRevert) {\r
1246 menu.hide();\r
1247 }\r
1248 }, me, {\r
1249 single: true\r
1250 });\r
1251\r
1252 me.translate(viewportAfter.translateX, viewportAfter.translateY, {\r
1253 preserveEndState: true,\r
1254 duration: 200\r
1255 });\r
1256 }\r
1257\r
1258 me.$swiping = false;\r
1259 },\r
1260\r
1261 privates: {\r
1262 addMeta: function(name, content) {\r
1263 var meta = document.createElement('meta');\r
1264\r
1265 meta.setAttribute('name', name);\r
1266 meta.setAttribute('content', content);\r
1267 Ext.getHead().append(meta);\r
1268 },\r
1269\r
1270 doAddListener: function(eventName, fn, scope, options, order, caller, manager) {\r
1271 var me = this;\r
1272 if (eventName === 'ready' && me.isReady && !me.isMaximizing) {\r
1273 fn.call(scope);\r
1274 return me;\r
1275 }\r
1276\r
1277 me.callParent([eventName, fn, scope, options, order, caller, manager]);\r
1278 },\r
1279\r
1280 toggleWindowListener: function(on, eventName, fn, capturing) {\r
1281 if (on) {\r
1282 this.addWindowListener(eventName, fn, capturing);\r
1283 } else {\r
1284 this.removeWindowListener(eventName, fn, capturing);\r
1285 }\r
1286 }\r
1287 }\r
1288});\r