/**
* @class Ext.window.Window
* @extends Ext.panel.Panel
* <p>A specialized panel intended for use as an application window. Windows are floated, {@link #resizable}, and
* {@link #draggable} by default. Windows can be {@link #maximizable maximized} to fill the viewport,
* restored to their prior size, and can be {@link #minimize}d.</p>
* <p>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.</p>
* <p>By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element
* specify {@link Ext.Component#renderTo renderTo}.</p>
* <p><b>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.</b></p>
* {@img Ext.window.Window/Ext.window.Window.png Window component}
* Example:<code><pre>
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();
</pre></code>
* @constructor
* @param {Object} config The config object
* @xtype window
*/
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.core.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.core.Element Element) (The Element that the Window is rendered to).
*/
/**
* @cfg {Boolean} modal
* True to make the window modal and mask everything behind it when displayed, false to display it without
* restricting access to other UI elements (defaults to false).
*/
/**
* @cfg {String/Element} animateTarget
* Id or element from which the window should animate while opening (defaults to null with no animation).
*/
/**
* @cfg {String/Number/Component} defaultFocus
* <p>Specifies a Component to receive focus when this Window is focused.</p>
* <p>This may be one of:</p><div class="mdetail-params"><ul>
* <li>The index of a footer Button.</li>
* <li>The id or {@link Ext.AbstractComponent#itemId} of a descendant Component.</li>
* <li>A Component.</li>
* </ul></div>
*/
/**
* @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
* Ext.emptyFn (See {@link Ext#emptyFn Ext.emptyFn}).
*/
/**
* @cfg {Boolean} collapsed
* True to render the window collapsed, false to render it expanded (defaults to false). Note that if
* {@link #expandOnShow} is true (the default) it will override the <code>collapsed</code> config and the window
* will always be expanded when shown.
*/
/**
* @cfg {Boolean} maximized
* True to initially display the window in a maximized state. (Defaults to false).
*/
/**
* @cfg {String} baseCls
* The base CSS class to apply to this panel's element (defaults to 'x-window').
*/
baseCls: Ext.baseCSSPrefix + 'window',
/**
* @cfg {Mixed} resizable
* <p>Specify as <code>true</code> to allow user resizing at each edge and corner of the window, false to disable
* resizing (defaults to true).</p>
* <p>This may also be specified as a config object to </p>
*/
resizable: true,
/**
* @cfg {Boolean} draggable
* <p>True to allow the window to be dragged by the header bar, false to disable dragging (defaults to true). 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);).<p>
*/
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}.
* (defaults to false). 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 (defaults to
* false). Optionally the entire window can be constrained using {@link #constrain}.
*/
constrainHeader: false,
/**
* @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 (defaults to false).
*/
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 (defaults to false). 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 (defaults to false). 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: 100,
// inherit docs
minWidth: 200,
/**
* @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 (defaults to true).
*/
expandOnShow: true,
// inherited docs, same default
collapsible: false,
/**
* @cfg {Boolean} closable
* <p>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 (defaults to <code>true</code>).</p>
* <p>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 #close} method will be called. This
* will <i>{@link Ext.Component#destroy destroy}</i> the Window and its content meaning that
* it may not be reused.</p>
* <p>To make closing a Window <i>hide</i> the Window so that it may be reused, set
* {@link #closeAction} to 'hide'.</p>
*/
closable: true,
/**
* @cfg {Boolean} hidden
* Render this Window hidden (default is <code>true</code>). If <code>true</code>, the
* {@link #hide} method will be called internally.
*/
hidden: true,
// Inherit docs from Component. Windows render to the body on first show.
autoRender: true,
// Inherit docs from Component. Windows hide using visibility.
hideMode: 'visibility',
/** @cfg {Boolean} floating @hide Windows are always floating*/
floating: true,
ariaRole: 'alertdialog',
itemCls: 'x-window-item',
overlapHeader: true,
ignoreHeaderBorderManagement: true,
// private
initComponent: function() {
var me = this;
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';
}
},
// State Management
// private
initStateEvents: function(){
var events = this.stateEvents;
// push on stateEvents if they don't exist
Ext.each(['maximize', 'restore', 'resize', 'dragend'], function(event){
if (Ext.Array.indexOf(events, event)) {
events.push(event);
}
});
this.callParent();
},
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 () {
if (this.floating) {
this.toFront();
}
},
// 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({
dblclick: {
fn: me.toggleMaximize,
element: 'el',
scope: me
}
});
}
},
// private
afterRender: function() {
var me = this,
hidden = me.hidden,
keyMap;
me.hidden = false;
// Component's afterRender sizes and positions the Component
me.callParent();
me.hidden = hidden;
// Create the proxy after the size has been applied in Component.afterRender
me.proxy = me.getProxy();
// clickToRaise
me.mon(me.el, 'mousedown', me.onMouseDown, me);
// Initialize
if (me.maximized) {
me.maximized = false;
me.maximize();
}
if (me.closable) {
keyMap = me.getKeyMap();
keyMap.on(27, me.onEsc, me);
keyMap.disable();
}
},
/**
* @private
* @override
* Override Component.initDraggable.
* Window uses the header element as the delegate.
*/
initDraggable: function() {
var me = this,
ddConfig;
if (!me.header) {
me.updateHeader(true);
}
ddConfig = Ext.applyIf({
el: me.el,
delegate: '#' + 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;
}
/**
* <p>If this Window is configured {@link #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} .</p>
* <p>This has implementations of <code>onBeforeStart</code>, <code>onDrag</code> and <code>onEnd</code>
* 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.</p>
* @type Ext.util.ComponentDragger
* @property dd
*/
me.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
me.relayEvents(me.dd, ['dragstart', 'drag', 'dragend']);
},
// private
onEsc: function(k, e) {
e.stopEvent();
this[this.closeAction]();
},
// private
beforeDestroy: function() {
var me = this;
if (me.rendered) {
delete this.animateTarget;
me.hide();
Ext.destroy(
me.keyMap
);
}
me.callParent();
},
/**
* @private
* @override
* 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
});
}
},
/**
* Gets the configured default focus item. If a {@link #defaultFocus} is set, it will receive focus, otherwise the
* Container itself will receive focus.
*/
getFocusEl: function() {
var me = this,
f = me.focusEl,
defaultComp = me.defaultButton || me.defaultFocus,
t = typeof db,
el,
ct;
if (Ext.isDefined(defaultComp)) {
if (Ext.isNumber(defaultComp)) {
f = me.query('button')[defaultComp];
} else if (Ext.isString(defaultComp)) {
f = me.down('#' + defaultComp);
} else {
f = defaultComp;
}
}
return f || me.focusEl;
},
// private
beforeShow: function() {
this.callParent();
if (this.expandOnShow) {
this.expand(false);
}
},
// private
afterShow: function(animateTarget) {
var me = this,
size;
// Perform superclass's afterShow tasks
// Which might include animating a proxy from an animTarget
me.callParent(arguments);
if (me.maximized) {
me.fitContainer();
}
if (me.monitorResize || me.constrain || me.constrainHeader) {
Ext.EventManager.onWindowResize(me.onWindowResize, me);
}
me.doConstrain();
if (me.keyMap) {
me.keyMap.enable();
}
},
// private
doClose: function() {
var me = this;
// immediate close
if (me.hidden) {
me.fireEvent('close', me);
me[me.closeAction]();
} else {
// close after hiding
me.hide(me.animTarget, me.doClose, me);
}
},
// private
afterHide: function() {
var me = this;
// No longer subscribe to resizing now that we're hidden
if (me.monitorResize || me.constrain || me.constrainHeader) {
Ext.EventManager.removeResizeListener(me.onWindowResize, me);
}
// 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() {
if (this.maximized) {
this.fitContainer();
}
this.doConstrain();
},
/**
* Placeholder method for minimizing the window. By default, this method simply fires the {@link #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.collapseTool) {
me.collapseTool.hide();
}
me.el.addCls(Ext.baseCSSPrefix + 'window-maximized');
me.container.addCls(Ext.baseCSSPrefix + 'window-maximized-ct');
me.setPosition(0, 0);
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();
}
// Restore the position/sizing
me.setPosition(me.restorePos);
me.setSize(me.restoreSize);
// Unset old position/sizing
delete me.restorePos;
delete me.restoreSize;
me.maximized = false;
me.el.enableShadow(true);
// Allow users to drag and drop again
if (me.dd) {
me.dd.enable();
}
me.container.removeCls(Ext.baseCSSPrefix + 'window-maximized-ct');
me.doConstrain();
me.fireEvent('restore', me);
}
return me;
},
/**
* A shortcut method for toggling between {@link #maximize} and {@link #restore} based on the current maximized
* state of the window.
* @return {Ext.window.Window} this
*/
toggleMaximize: function() {
return this[this.maximized ? 'restore': 'maximize']();
}
/**
* @cfg {Boolean} autoWidth @hide
* Absolute positioned element and therefore cannot support autoWidth.
* A width is a required configuration.
**/
});