/** * This plugin can be applied to any `Component` (although almost always to a `Container`) * to make it fill the browser viewport. This plugin is used internally by the more familiar * `Ext.container.Viewport` class. * * The `Viewport` container is commonly used but it can be an issue if you need to fill the * viewport with a container that derives from another class (e.g., `Ext.tab.Panel`). Prior * to this plugin, you would have to do this: * * Ext.create('Ext.container.Viewport', { * layout: 'fit', // full the viewport with the tab panel * * items: [{ * xtype: 'tabpanel', * items: [{ * ... * }] * }] * }); * * With this plugin you can create the `tabpanel` as the viewport: * * Ext.create('Ext.tab.Panel', { * plugins: 'viewport', * * items: [{ * ... * }] * }); * * More importantly perhaps is that as a plugin, the view class can be reused in other * contexts such as the content of a `{@link Ext.window.Window window}`. * * The Viewport renders itself to the document body, and automatically sizes itself to the size of * the browser viewport and manages window resizing. There may only be one Viewport created * in a page. * * ## Responsive Design * * This plugin enables {@link Ext.mixin.Responsive#responsiveConfig} for the target component. * * @since 5.0.0 */Ext.define('Ext.plugin.Viewport', { extend: 'Ext.plugin.Responsive', alias: 'plugin.viewport', /** * @cfg {Number} [maxUserScale=1] * The maximum zoom scale. Only applicable for touch devices. Set this to 1 to * disable zooming. Setting this to any value other than "1" will disable all * multi-touch gestures. */ setCmp: function (cmp) { this.cmp = cmp; if (cmp && !cmp.isViewport) { this.apply(cmp); if (cmp.renderConfigs) { cmp.flushRenderConfigs(); } cmp.setupViewport(); } }, statics: { apply: function (target) { Ext.applyIf(target.prototype || target, { ariaRole: 'application', viewportCls: Ext.baseCSSPrefix + 'viewport' }); Ext.override(target, { isViewport: true, preserveElOnDestroy: true, initComponent : function() { this.callParent(); this.setupViewport(); }, handleViewportResize: function () { var me = this, Element = Ext.dom.Element, width = Element.getViewportWidth(), height = Element.getViewportHeight(); if (width != me.width || height != me.height) { me.setSize(width, height); } }, setupViewport : function() { var me = this, html = document.body.parentNode, el = me.el = Ext.getBody(); // Get the DOM disruption over with before the Viewport renders and begins a layout Ext.getScrollbarSize(); // Clear any dimensions, we will size later on me.width = me.height = undefined; Ext.fly(html).addCls(me.viewportCls); if (me.autoScroll) { Ext.fly(html).setStyle(me.getOverflowStyle()); delete me.autoScroll; } el.setHeight = el.setWidth = Ext.emptyFn; el.dom.scroll = 'no'; me.allowDomMove = false; me.renderTo = me.el; if (Ext.supports.Touch) { me.initMeta(); } }, afterLayout: function(layout) { if (Ext.supports.Touch) { document.body.scrollTop = 0; } this.callParent([layout]); }, onRender: function() { var me = this; me.callParent(arguments); // Important to start life as the proper size (to avoid extra layouts) // But after render so that the size is not stamped into the body me.width = Ext.Element.getViewportWidth(); me.height = Ext.Element.getViewportHeight(); // prevent touchmove from panning the viewport in mobile safari if (Ext.supports.TouchEvents) { me.mon(Ext.getDoc(), { touchmove: function(e) { e.preventDefault(); }, translate: false, delegated: false }); } }, 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 ]); } }, beforeDestroy: function(){ var me = this; me.removeUIFromElement(); me.el.removeCls(me.baseCls); Ext.fly(document.body.parentNode).removeCls(me.viewportCls); me.callParent(); }, addMeta: function(name, content) { var meta = document.createElement('meta'); meta.setAttribute('name', name); meta.setAttribute('content', content); Ext.getHead().appendChild(meta); }, initMeta: function() { var me = this, maxScale = me.maxUserScale || 1; me.addMeta('viewport', 'width=device-width, initial-scale=1, maximum-scale=' + maxScale + ', user-scalable=' + (maxScale !== 1 ? 'yes' : 'no')); me.addMeta('apple-mobile-web-app-capable', 'yes'); }, privates: { // override here to prevent an extraneous warning applyTargetCls: function (targetCls) { this.el.addCls(targetCls); }, // Override here to prevent tabIndex set/reset on the body disableTabbing: function() { var el = this.el; if (el) { el.saveChildrenTabbableState(); } }, enableTabbing: function() { var el = this.el; if (el) { el.restoreChildrenTabbableState(); } } } }); } }, privates: { updateResponsiveState: function () { // By providing this method we are in sync with the layout suspend/resume as // well as other changes to configs that need to happen during this pulse of // size change. // This plugin instance is response, but the cmp is what needs to be handling // the resize: this.cmp.handleViewportResize(); this.callParent(); } }},function (Viewport) { Viewport.prototype.apply = Viewport.apply;});