/** * A specialized panel intended for use as an application window. Windows are floated, {@link #resizable}, and * {@link #cfg-draggable} by default. Windows can be {@link #maximizable maximized} to fill the viewport, restored to * their prior size, and can be {@link #method-minimize}d. * * Windows can also be linked to a {@link Ext.ZIndexManager} or managed by the {@link Ext.WindowManager} to provide * grouping, activation, to front, to back and other application-specific behavior. * * By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element specify * {@link Ext.Component#renderTo renderTo}. * * **As with all {@link Ext.container.Container Container}s, it is important to consider how you want the Window to size * and arrange any child Components. Choose an appropriate {@link #layout} configuration which lays out child Components * in the required manner.** * * @example * Ext.create('Ext.window.Window', { * title: 'Hello', * height: 200, * width: 400, * layout: 'fit', * items: { // Let's put an empty grid in just to illustrate fit layout * xtype: 'grid', * border: false, * columns: [{header: 'World'}], // One header just for show. There's no data, * store: Ext.create('Ext.data.ArrayStore', {}) // A dummy empty data store * } * }).show(); */ Ext.define('Ext.window.Window', { extend: 'Ext.panel.Panel', alternateClassName: 'Ext.Window', requires: ['Ext.util.ComponentDragger', 'Ext.util.Region', 'Ext.EventManager'], alias: 'widget.window', /** * @cfg {Number} x * The X position of the left edge of the window on initial showing. Defaults to centering the Window within the * width of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to). */ /** * @cfg {Number} y * The Y position of the top edge of the window on initial showing. Defaults to centering the Window within the * height of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to). */ /** * @cfg {Boolean} [modal=false] * True to make the window modal and mask everything behind it when displayed, false to display it without * restricting access to other UI elements. */ /** * @cfg {String/Ext.Element} [animateTarget=null] * Id or element from which the window should animate while opening. */ /** * @cfg {String/Number/Ext.Component} defaultFocus * Specifies a Component to receive focus when this Window is focused. * * This may be one of: * * - The index of a footer Button. * - The id or {@link Ext.AbstractComponent#itemId} of a descendant Component. * - A Component. */ /** * @cfg {Function} onEsc * Allows override of the built-in processing for the escape key. Default action is to close the Window (performing * whatever action is specified in {@link #closeAction}. To prevent the Window closing when the escape key is * pressed, specify this as {@link Ext#emptyFn Ext.emptyFn}. */ /** * @cfg {Boolean} [collapsed=false] * True to render the window collapsed, false to render it expanded. Note that if {@link #expandOnShow} * is true (the default) it will override the `collapsed` config and the window will always be * expanded when shown. */ /** * @cfg {Boolean} [maximized=false] * True to initially display the window in a maximized state. */ /** * @cfg {String} [baseCls='x-window'] * The base CSS class to apply to this panel's element. */ baseCls: Ext.baseCSSPrefix + 'window', /** * @cfg {Boolean/Object} resizable * Specify as `true` to allow user resizing at each edge and corner of the window, false to disable resizing. * * This may also be specified as a config object to Ext.resizer.Resizer */ resizable: true, /** * @cfg {Boolean} draggable * True to allow the window to be dragged by the header bar, false to disable dragging. Note that * by default the window will be centered in the viewport, so if dragging is disabled the window may need to be * positioned programmatically after render (e.g., myWindow.setPosition(100, 100);). */ draggable: true, /** * @cfg {Boolean} constrain * True to constrain the window within its containing element, false to allow it to fall outside of its containing * element. By default the window will be rendered to document.body. To render and constrain the window within * another element specify {@link #renderTo}. Optionally the header only can be constrained * using {@link #constrainHeader}. */ constrain: false, /** * @cfg {Boolean} constrainHeader * True to constrain the window header within its containing element (allowing the window body to fall outside of * its containing element) or false to allow the header to fall outside its containing element. * Optionally the entire window can be constrained using {@link #constrain}. */ constrainHeader: false, /** * @cfg {Ext.util.Region/Ext.Element} constrainTo * A {@link Ext.util.Region Region} (or an element from which a Region measurement will be read) which is used * to constrain the window. */ /** * @cfg {Boolean} plain * True to render the window body with a transparent background so that it will blend into the framing elements, * false to add a lighter background color to visually highlight the body element and separate it more distinctly * from the surrounding frame. */ plain: false, /** * @cfg {Boolean} minimizable * True to display the 'minimize' tool button and allow the user to minimize the window, false to hide the button * and disallow minimizing the window. Note that this button provides no implementation -- the * behavior of minimizing a window is implementation-specific, so the minimize event must be handled and a custom * minimize behavior implemented for this option to be useful. */ minimizable: false, /** * @cfg {Boolean} maximizable * True to display the 'maximize' tool button and allow the user to maximize the window, false to hide the button * and disallow maximizing the window. Note that when a window is maximized, the tool button * will automatically change to a 'restore' button with the appropriate behavior already built-in that will restore * the window to its previous size. */ maximizable: false, // inherit docs minHeight: 50, // inherit docs minWidth: 50, /** * @cfg {Boolean} expandOnShow * True to always expand the window when it is displayed, false to keep it in its current state (which may be * {@link #collapsed}) when displayed. */ expandOnShow: true, // inherited docs, same default collapsible: false, /** * @cfg {Boolean} closable * True to display the 'close' tool button and allow the user to close the window, false to hide the button and * disallow closing the window. * * By default, when close is requested by either clicking the close button in the header or pressing ESC when the * Window has focus, the {@link #method-close} method will be called. This will _{@link Ext.Component#method-destroy destroy}_ the * Window and its content meaning that it may not be reused. * * To make closing a Window _hide_ the Window so that it may be reused, set {@link #closeAction} to 'hide'. */ closable: true, /** * @cfg {Boolean} hidden * Render this Window hidden. If `true`, the {@link #method-hide} method will be called internally. */ hidden: true, /** * @cfg * @inheritdoc * Windows render to the body on first show. */ autoRender: true, /** * @cfg * @inheritdoc * Windows hide using offsets in order to preserve the scroll positions of their descendants. */ hideMode: 'offsets', /** * @cfg * @private */ floating: true, ariaRole: 'alertdialog', itemCls: Ext.baseCSSPrefix + 'window-item', initialAlphaNum: /^[a-z0-9]/, overlapHeader: true, ignoreHeaderBorderManagement: true, // Flag to Renderable to always look up the framing styles for this Component alwaysFramed: true, /** * @property {Boolean} isWindow * `true` in this class to identify an object as an instantiated Window, or subclass thereof. */ isWindow: true, // private initComponent: function() { var me = this; // Explicitly set frame to false, since alwaysFramed is // true, we only want to lookup framing in a specific instance me.frame = false; me.callParent(); me.addEvents( /** * @event activate * Fires after the window has been visually activated via {@link #setActive}. * @param {Ext.window.Window} this */ /** * @event deactivate * Fires after the window has been visually deactivated via {@link #setActive}. * @param {Ext.window.Window} this */ /** * @event resize * Fires after the window has been resized. * @param {Ext.window.Window} this * @param {Number} width The window's new width * @param {Number} height The window's new height */ 'resize', /** * @event maximize * Fires after the window has been maximized. * @param {Ext.window.Window} this */ 'maximize', /** * @event minimize * Fires after the window has been minimized. * @param {Ext.window.Window} this */ 'minimize', /** * @event restore * Fires after the window has been restored to its original size after being maximized. * @param {Ext.window.Window} this */ 'restore' ); if (me.plain) { me.addClsWithUI('plain'); } if (me.modal) { me.ariaRole = 'dialog'; } // clickToRaise if (me.floating) { me.on({ element: 'el', mousedown: me.onMouseDown, scope: me }); } me.addStateEvents(['maximize', 'restore', 'resize', 'dragend']); }, getElConfig: function () { var me = this, elConfig; elConfig = me.callParent(); elConfig.tabIndex = -1; return elConfig; }, // State Management // private getState: function() { var me = this, state = me.callParent() || {}, maximized = !!me.maximized; state.maximized = maximized; Ext.apply(state, { size: maximized ? me.restoreSize : me.getSize(), pos: maximized ? me.restorePos : me.getPosition() }); return state; }, applyState: function(state){ var me = this; if (state) { me.maximized = state.maximized; if (me.maximized) { me.hasSavedRestore = true; me.restoreSize = state.size; me.restorePos = state.pos; } else { Ext.apply(me, { width: state.size.width, height: state.size.height, x: state.pos[0], y: state.pos[1] }); } } }, // private onMouseDown: function (e) { var preventFocus; if (this.floating) { if (Ext.fly(e.getTarget()).focusable()) { preventFocus = true; } this.toFront(preventFocus); } }, // private onRender: function(ct, position) { var me = this; me.callParent(arguments); me.focusEl = me.el; // Double clicking a header will toggleMaximize if (me.maximizable) { me.header.on({ scope: me, dblclick: me.toggleMaximize }); } }, // private afterRender: function() { var me = this, keyMap; me.callParent(); // Initialize if (me.maximized) { me.maximized = false; me.maximize(); } if (me.closable) { keyMap = me.getKeyMap(); keyMap.on(27, me.onEsc, me); } else { keyMap = me.keyMap; } if (keyMap && me.hidden) { keyMap.disable(); } }, /** * @private * Override Component.initDraggable. * Window uses the header element as the delegate. */ initDraggable: function() { var me = this, ddConfig; if (!me.header) { me.updateHeader(true); } /* * Check the header here again. If for whatever reason it wasn't created in * updateHeader (we were configured with header: false) then we'll just ignore the rest since the * header acts as the drag handle. */ if (me.header) { ddConfig = Ext.applyIf({ el: me.el, delegate: '#' + Ext.escapeId(me.header.id) }, me.draggable); // Add extra configs if Window is specified to be constrained if (me.constrain || me.constrainHeader) { ddConfig.constrain = me.constrain; ddConfig.constrainDelegate = me.constrainHeader; ddConfig.constrainTo = me.constrainTo || me.container; } /** * @property {Ext.util.ComponentDragger} dd * If this Window is configured {@link #cfg-draggable}, this property will contain an instance of * {@link Ext.util.ComponentDragger} (A subclass of {@link Ext.dd.DragTracker DragTracker}) which handles dragging * the Window's DOM Element, and constraining according to the {@link #constrain} and {@link #constrainHeader} . * * This has implementations of `onBeforeStart`, `onDrag` and `onEnd` which perform the dragging action. If * extra logic is needed at these points, use {@link Ext.Function#createInterceptor createInterceptor} or * {@link Ext.Function#createSequence createSequence} to augment the existing implementations. */ me.dd = new Ext.util.ComponentDragger(this, ddConfig); me.relayEvents(me.dd, ['dragstart', 'drag', 'dragend']); } }, // private onEsc: function(k, e) { // Only process ESC if the FocusManager is not doing it if (!Ext.FocusManager || !Ext.FocusManager.enabled || Ext.FocusManager.focusedCmp === this) { e.stopEvent(); this.close(); } }, // private beforeDestroy: function() { var me = this; if (me.rendered) { delete this.animateTarget; me.hide(); Ext.destroy( me.keyMap ); } me.callParent(); }, /** * @private * Contribute class-specific tools to the header. * Called by Panel's initTools. */ addTools: function() { var me = this; // Call Panel's initTools me.callParent(); if (me.minimizable) { me.addTool({ type: 'minimize', handler: Ext.Function.bind(me.minimize, me, []) }); } if (me.maximizable) { me.addTool({ type: 'maximize', handler: Ext.Function.bind(me.maximize, me, []) }); me.addTool({ type: 'restore', handler: Ext.Function.bind(me.restore, me, []), hidden: true }); } }, /** * @private * Returns the focus holder element associated with this Window. By default, this is the Window's element. * @returns {Ext.Element/Ext.Component} the focus holding element or Component. */ getFocusEl: function() { return this.getDefaultFocus(); }, /** * Gets the configured default focus item. If a {@link #defaultFocus} is set, it will * receive focus when the Window's focus method is called, otherwise the * Window itself will receive focus. */ getDefaultFocus: function() { var me = this, result, defaultComp = me.defaultButton || me.defaultFocus, selector; if (defaultComp !== undefined) { // Number is index of Button if (Ext.isNumber(defaultComp)) { result = me.query('button')[defaultComp]; } // String is ID or CQ selector else if (Ext.isString(defaultComp)) { selector = defaultComp; // Try id/itemId match if selector begins with alphanumeric if (selector.match(me.initialAlphaNum)) { result = me.down('#' + selector); } // If not found, use as selector if (!result) { result = me.down(selector); } } // Otherwise, if it's got a focus method, use it else if (defaultComp.focus) { result = defaultComp; } } return result || me.el; }, /** * @private * Called when a Component's focusEl receives focus. * If there is a valid default focus Component to jump to, focus that, * otherwise continue as usual, focus this Component. */ onFocus: function() { var me = this, focusDescendant; // If the FocusManager is enabled, then we must noy jumpt to focus the default focus. We must focus the Window if ((Ext.FocusManager && Ext.FocusManager.enabled) || ((focusDescendant = me.getDefaultFocus()) === me)) { me.callParent(arguments); } else { focusDescendant.focus(); } }, beforeLayout: function () { var shadow = this.el.shadow; this.callParent(); if (shadow) { shadow.hide(); } }, onShow: function() { var me = this; me.callParent(arguments); if (me.expandOnShow) { me.expand(false); } me.syncMonitorWindowResize(); if (me.keyMap) { me.keyMap.enable(); } }, // private doClose: function() { var me = this; // Being called as callback after going through the hide call below if (me.hidden) { me.fireEvent('close', me); if (me.closeAction == 'destroy') { this.destroy(); } } else { // close after hiding me.hide(me.animateTarget, me.doClose, me); } }, // private afterHide: function() { var me = this; // No longer subscribe to resizing now that we're hidden me.syncMonitorWindowResize(); // Turn off keyboard handling once window is hidden if (me.keyMap) { me.keyMap.disable(); } // Perform superclass's afterHide tasks. me.callParent(arguments); }, // private onWindowResize: function() { var me = this, sizeModel; if (me.maximized) { me.fitContainer(); } else { sizeModel = me.getSizeModel(); if (sizeModel.width.natural || sizeModel.height.natural) { me.updateLayout(); } } me.doConstrain(); }, /** * Placeholder method for minimizing the window. By default, this method simply fires the {@link #event-minimize} event * since the behavior of minimizing a window is application-specific. To implement custom minimize behavior, either * the minimize event can be handled or this method can be overridden. * @return {Ext.window.Window} this */ minimize: function() { this.fireEvent('minimize', this); return this; }, afterCollapse: function() { var me = this; if (me.maximizable) { me.tools.maximize.hide(); me.tools.restore.hide(); } if (me.resizer) { me.resizer.disable(); } me.callParent(arguments); }, afterExpand: function() { var me = this; if (me.maximized) { me.tools.restore.show(); } else if (me.maximizable) { me.tools.maximize.show(); } if (me.resizer) { me.resizer.enable(); } me.callParent(arguments); }, /** * Fits the window within its current container and automatically replaces the {@link #maximizable 'maximize' tool * button} with the 'restore' tool button. Also see {@link #toggleMaximize}. * @return {Ext.window.Window} this */ maximize: function() { var me = this; if (!me.maximized) { me.expand(false); if (!me.hasSavedRestore) { me.restoreSize = me.getSize(); me.restorePos = me.getPosition(true); } if (me.maximizable) { me.tools.maximize.hide(); me.tools.restore.show(); } me.maximized = true; me.el.disableShadow(); if (me.dd) { me.dd.disable(); } if (me.resizer) { me.resizer.disable(); } if (me.collapseTool) { me.collapseTool.hide(); } me.el.addCls(Ext.baseCSSPrefix + 'window-maximized'); me.container.addCls(Ext.baseCSSPrefix + 'window-maximized-ct'); me.syncMonitorWindowResize(); me.fitContainer(); me.fireEvent('maximize', me); } return me; }, /** * Restores a {@link #maximizable maximized} window back to its original size and position prior to being maximized * and also replaces the 'restore' tool button with the 'maximize' tool button. Also see {@link #toggleMaximize}. * @return {Ext.window.Window} this */ restore: function() { var me = this, tools = me.tools; if (me.maximized) { delete me.hasSavedRestore; me.removeCls(Ext.baseCSSPrefix + 'window-maximized'); // Toggle tool visibility if (tools.restore) { tools.restore.hide(); } if (tools.maximize) { tools.maximize.show(); } if (me.collapseTool) { me.collapseTool.show(); } me.maximized = false; // Restore the position/sizing me.setPosition(me.restorePos); me.setSize(me.restoreSize); // Unset old position/sizing delete me.restorePos; delete me.restoreSize; me.el.enableShadow(true); // Allow users to drag and drop again if (me.dd) { me.dd.enable(); } if (me.resizer) { me.resizer.enable(); } me.container.removeCls(Ext.baseCSSPrefix + 'window-maximized-ct'); me.syncMonitorWindowResize(); me.doConstrain(); me.fireEvent('restore', me); } return me; }, /** * Synchronizes the presence of our listener for window resize events. This method * should be called whenever this status might change. * @private */ syncMonitorWindowResize: function () { var me = this, currentlyMonitoring = me._monitoringResize, // all the states where we should be listening to window resize: yes = me.monitorResize || me.constrain || me.constrainHeader || me.maximized, // all the states where we veto this: veto = me.hidden || me.destroying || me.isDestroyed; if (yes && !veto) { // we should be listening... if (!currentlyMonitoring) { // but we aren't, so set it up Ext.EventManager.onWindowResize(me.onWindowResize, me); me._monitoringResize = true; } } else if (currentlyMonitoring) { // we should not be listening, but we are, so tear it down Ext.EventManager.removeResizeListener(me.onWindowResize, me); me._monitoringResize = false; } }, /** * A shortcut method for toggling between {@link #method-maximize} and {@link #method-restore} based on the current maximized * state of the window. * @return {Ext.window.Window} this */ toggleMaximize: function() { return this[this.maximized ? 'restore': 'maximize'](); } });