Upgrade to ExtJS 4.0.1 - Released 05/18/2011
[extjs.git] / src / window / Window.js
1 /**
2  * @class Ext.window.Window
3  * @extends Ext.panel.Panel
4  * <p>A specialized panel intended for use as an application window.  Windows are floated, {@link #resizable}, and
5  * {@link #draggable} by default.  Windows can be {@link #maximizable maximized} to fill the viewport,
6  * restored to their prior size, and can be {@link #minimize}d.</p>
7  * <p>Windows can also be linked to a {@link Ext.ZIndexManager} or managed by the {@link Ext.WindowManager} to provide
8  * grouping, activation, to front, to back and other application-specific behavior.</p>
9  * <p>By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element
10  * specify {@link Ext.Component#renderTo renderTo}.</p>
11  * <p><b>As with all {@link Ext.container.Container Container}s, it is important to consider how you want the Window
12  * to size and arrange any child Components. Choose an appropriate {@link #layout} configuration which lays out
13  * child Components in the required manner.</b></p>
14  * {@img Ext.window.Window/Ext.window.Window.png Window component}
15  * Example:<code><pre>
16 Ext.create('Ext.window.Window', {
17     title: 'Hello',
18     height: 200,
19     width: 400,
20     layout: 'fit',
21     items: {  // Let's put an empty grid in just to illustrate fit layout
22         xtype: 'grid',
23         border: false,
24         columns: [{header: 'World'}],                 // One header just for show. There's no data,
25         store: Ext.create('Ext.data.ArrayStore', {}) // A dummy empty data store
26     }
27 }).show();
28 </pre></code>
29  * @constructor
30  * @param {Object} config The config object
31  * @xtype window
32  */
33 Ext.define('Ext.window.Window', {
34     extend: 'Ext.panel.Panel',
35
36     alternateClassName: 'Ext.Window',
37
38     requires: ['Ext.util.ComponentDragger', 'Ext.util.Region', 'Ext.EventManager'],
39
40     alias: 'widget.window',
41
42     /**
43      * @cfg {Number} x
44      * The X position of the left edge of the window on initial showing. Defaults to centering the Window within
45      * the width of the Window's container {@link Ext.core.Element Element) (The Element that the Window is rendered to).
46      */
47     /**
48      * @cfg {Number} y
49      * The Y position of the top edge of the window on initial showing. Defaults to centering the Window within
50      * the height of the Window's container {@link Ext.core.Element Element) (The Element that the Window is rendered to).
51      */
52     /**
53      * @cfg {Boolean} modal
54      * True to make the window modal and mask everything behind it when displayed, false to display it without
55      * restricting access to other UI elements (defaults to false).
56      */
57     /**
58      * @cfg {String/Element} animateTarget
59      * Id or element from which the window should animate while opening (defaults to null with no animation).
60      */
61     /**
62      * @cfg {String/Number/Component} defaultFocus
63      * <p>Specifies a Component to receive focus when this Window is focused.</p>
64      * <p>This may be one of:</p><div class="mdetail-params"><ul>
65      * <li>The index of a footer Button.</li>
66      * <li>The id or {@link Ext.AbstractComponent#itemId} of a descendant Component.</li>
67      * <li>A Component.</li>
68      * </ul></div>
69      */
70     /**
71      * @cfg {Function} onEsc
72      * Allows override of the built-in processing for the escape key. Default action
73      * is to close the Window (performing whatever action is specified in {@link #closeAction}.
74      * To prevent the Window closing when the escape key is pressed, specify this as
75      * Ext.emptyFn (See {@link Ext#emptyFn Ext.emptyFn}).
76      */
77     /**
78      * @cfg {Boolean} collapsed
79      * True to render the window collapsed, false to render it expanded (defaults to false). Note that if
80      * {@link #expandOnShow} is true (the default) it will override the <code>collapsed</code> config and the window
81      * will always be expanded when shown.
82      */
83     /**
84      * @cfg {Boolean} maximized
85      * True to initially display the window in a maximized state. (Defaults to false).
86      */
87
88     /**
89     * @cfg {String} baseCls
90     * The base CSS class to apply to this panel's element (defaults to 'x-window').
91     */
92     baseCls: Ext.baseCSSPrefix + 'window',
93
94     /**
95      * @cfg {Mixed} resizable
96      * <p>Specify as <code>true</code> to allow user resizing at each edge and corner of the window, false to disable
97      * resizing (defaults to true).</p>
98      * <p>This may also be specified as a config object to </p>
99      */
100     resizable: true,
101
102     /**
103      * @cfg {Boolean} draggable
104      * <p>True to allow the window to be dragged by the header bar, false to disable dragging (defaults to true).  Note
105      * that by default the window will be centered in the viewport, so if dragging is disabled the window may need
106      * to be positioned programmatically after render (e.g., myWindow.setPosition(100, 100);).<p>
107      */
108     draggable: true,
109
110     /**
111      * @cfg {Boolean} constrain
112      * True to constrain the window within its containing element, false to allow it to fall outside of its
113      * containing element. By default the window will be rendered to document.body.  To render and constrain the
114      * window within another element specify {@link #renderTo}.
115      * (defaults to false).  Optionally the header only can be constrained using {@link #constrainHeader}.
116      */
117     constrain: false,
118
119     /**
120      * @cfg {Boolean} constrainHeader
121      * True to constrain the window header within its containing element (allowing the window body to fall outside
122      * of its containing element) or false to allow the header to fall outside its containing element (defaults to
123      * false). Optionally the entire window can be constrained using {@link #constrain}.
124      */
125     constrainHeader: false,
126
127     /**
128      * @cfg {Boolean} plain
129      * True to render the window body with a transparent background so that it will blend into the framing
130      * elements, false to add a lighter background color to visually highlight the body element and separate it
131      * more distinctly from the surrounding frame (defaults to false).
132      */
133     plain: false,
134
135     /**
136      * @cfg {Boolean} minimizable
137      * True to display the 'minimize' tool button and allow the user to minimize the window, false to hide the button
138      * and disallow minimizing the window (defaults to false).  Note that this button provides no implementation --
139      * the behavior of minimizing a window is implementation-specific, so the minimize event must be handled and a
140      * custom minimize behavior implemented for this option to be useful.
141      */
142     minimizable: false,
143
144     /**
145      * @cfg {Boolean} maximizable
146      * True to display the 'maximize' tool button and allow the user to maximize the window, false to hide the button
147      * and disallow maximizing the window (defaults to false).  Note that when a window is maximized, the tool button
148      * will automatically change to a 'restore' button with the appropriate behavior already built-in that will
149      * restore the window to its previous size.
150      */
151     maximizable: false,
152
153     // inherit docs
154     minHeight: 100,
155
156     // inherit docs
157     minWidth: 200,
158
159     /**
160      * @cfg {Boolean} expandOnShow
161      * True to always expand the window when it is displayed, false to keep it in its current state (which may be
162      * {@link #collapsed}) when displayed (defaults to true).
163      */
164     expandOnShow: true,
165
166     // inherited docs, same default
167     collapsible: false,
168
169     /**
170      * @cfg {Boolean} closable
171      * <p>True to display the 'close' tool button and allow the user to close the window, false to
172      * hide the button and disallow closing the window (defaults to <code>true</code>).</p>
173      * <p>By default, when close is requested by either clicking the close button in the header
174      * or pressing ESC when the Window has focus, the {@link #close} method will be called. This
175      * will <i>{@link Ext.Component#destroy destroy}</i> the Window and its content meaning that
176      * it may not be reused.</p>
177      * <p>To make closing a Window <i>hide</i> the Window so that it may be reused, set
178      * {@link #closeAction} to 'hide'.</p>
179      */
180     closable: true,
181
182     /**
183      * @cfg {Boolean} hidden
184      * Render this Window hidden (default is <code>true</code>). If <code>true</code>, the
185      * {@link #hide} method will be called internally.
186      */
187     hidden: true,
188
189     // Inherit docs from Component. Windows render to the body on first show.
190     autoRender: true,
191
192     // Inherit docs from Component. Windows hide using visibility.
193     hideMode: 'visibility',
194
195     /** @cfg {Boolean} floating @hide Windows are always floating*/
196     floating: true,
197
198     ariaRole: 'alertdialog',
199     
200     itemCls: 'x-window-item',
201
202     overlapHeader: true,
203     
204     ignoreHeaderBorderManagement: true,
205
206     // private
207     initComponent: function() {
208         var me = this;
209         me.callParent();
210         me.addEvents(
211             /**
212              * @event activate
213              * Fires after the window has been visually activated via {@link #setActive}.
214              * @param {Ext.window.Window} this
215              */
216             /**
217              * @event deactivate
218              * Fires after the window has been visually deactivated via {@link #setActive}.
219              * @param {Ext.window.Window} this
220              */
221             /**
222              * @event resize
223              * Fires after the window has been resized.
224              * @param {Ext.window.Window} this
225              * @param {Number} width The window's new width
226              * @param {Number} height The window's new height
227              */
228             'resize',
229             /**
230              * @event maximize
231              * Fires after the window has been maximized.
232              * @param {Ext.window.Window} this
233              */
234             'maximize',
235             /**
236              * @event minimize
237              * Fires after the window has been minimized.
238              * @param {Ext.window.Window} this
239              */
240             'minimize',
241             /**
242              * @event restore
243              * Fires after the window has been restored to its original size after being maximized.
244              * @param {Ext.window.Window} this
245              */
246             'restore'
247         );
248
249         if (me.plain) {
250             me.addClsWithUI('plain');
251         }
252
253         if (me.modal) {
254             me.ariaRole = 'dialog';
255         }
256     },
257
258     // State Management
259     // private
260
261     initStateEvents: function(){
262         var events = this.stateEvents;
263         // push on stateEvents if they don't exist
264         Ext.each(['maximize', 'restore', 'resize', 'dragend'], function(event){
265             if (Ext.Array.indexOf(events, event)) {
266                 events.push(event);
267             }
268         });
269         this.callParent();
270     },
271
272     getState: function() {
273         var me = this,
274             state = me.callParent() || {},
275             maximized = !!me.maximized;
276
277         state.maximized = maximized;
278         Ext.apply(state, {
279             size: maximized ? me.restoreSize : me.getSize(),
280             pos: maximized ? me.restorePos : me.getPosition()
281         });
282         return state;
283     },
284
285     applyState: function(state){
286         var me = this;
287
288         if (state) {
289             me.maximized = state.maximized;
290             if (me.maximized) {
291                 me.hasSavedRestore = true;
292                 me.restoreSize = state.size;
293                 me.restorePos = state.pos;
294             } else {
295                 Ext.apply(me, {
296                     width: state.size.width,
297                     height: state.size.height,
298                     x: state.pos[0],
299                     y: state.pos[1]
300                 });
301             }
302         }
303     },
304
305     // private
306     onMouseDown: function () {
307         if (this.floating) {
308             this.toFront();
309         }
310     },
311
312     // private
313     onRender: function(ct, position) {
314         var me = this;
315         me.callParent(arguments);
316         me.focusEl = me.el;
317
318         // Double clicking a header will toggleMaximize
319         if (me.maximizable) {
320             me.header.on({
321                 dblclick: {
322                     fn: me.toggleMaximize,
323                     element: 'el',
324                     scope: me
325                 }
326             });
327         }
328     },
329
330     // private
331     afterRender: function() {
332         var me = this,
333             hidden = me.hidden,
334             keyMap;
335
336         me.hidden = false;
337         // Component's afterRender sizes and positions the Component
338         me.callParent();
339         me.hidden = hidden;
340
341         // Create the proxy after the size has been applied in Component.afterRender
342         me.proxy = me.getProxy();
343
344         // clickToRaise
345         me.mon(me.el, 'mousedown', me.onMouseDown, me);
346
347         // Initialize
348         if (me.maximized) {
349             me.maximized = false;
350             me.maximize();
351         }
352
353         if (me.closable) {
354             keyMap = me.getKeyMap();
355             keyMap.on(27, me.onEsc, me);
356
357             //if (hidden) { ? would be consistent w/before/afterShow...
358                 keyMap.disable();
359             //}
360         }
361
362         if (!hidden) {
363             me.syncMonitorWindowResize();
364             me.doConstrain();
365         }
366     },
367
368     /**
369      * @private
370      * @override
371      * Override Component.initDraggable.
372      * Window uses the header element as the delegate.
373      */
374     initDraggable: function() {
375         var me = this,
376             ddConfig;
377
378         if (!me.header) {
379             me.updateHeader(true);
380         }
381         
382         /*
383          * Check the header here again. If for whatever reason it wasn't created in
384          * updateHeader (preventHeader) then we'll just ignore the rest since the
385          * header acts as the drag handle.
386          */
387         if (me.header) {
388             ddConfig = Ext.applyIf({
389                 el: me.el,
390                 delegate: '#' + me.header.id
391             }, me.draggable);
392
393             // Add extra configs if Window is specified to be constrained
394             if (me.constrain || me.constrainHeader) {
395                 ddConfig.constrain = me.constrain;
396                 ddConfig.constrainDelegate = me.constrainHeader;
397                 ddConfig.constrainTo = me.constrainTo || me.container;
398             }
399
400             /**
401              * <p>If this Window is configured {@link #draggable}, this property will contain
402              * an instance of {@link Ext.util.ComponentDragger} (A subclass of {@link Ext.dd.DragTracker DragTracker})
403              * which handles dragging the Window's DOM Element, and constraining according to the {@link #constrain}
404              * and {@link #constrainHeader} .</p>
405              * <p>This has implementations of <code>onBeforeStart</code>, <code>onDrag</code> and <code>onEnd</code>
406              * which perform the dragging action. If extra logic is needed at these points, use
407              * {@link Ext.Function#createInterceptor createInterceptor} or {@link Ext.Function#createSequence createSequence} to
408              * augment the existing implementations.</p>
409              * @type Ext.util.ComponentDragger
410              * @property dd
411              */
412             me.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
413             me.relayEvents(me.dd, ['dragstart', 'drag', 'dragend']);
414         }
415     },
416
417     // private
418     onEsc: function(k, e) {
419         e.stopEvent();
420         this[this.closeAction]();
421     },
422
423     // private
424     beforeDestroy: function() {
425         var me = this;
426         if (me.rendered) {
427             delete this.animateTarget;
428             me.hide();
429             Ext.destroy(
430                 me.keyMap
431             );
432         }
433         me.callParent();
434     },
435
436     /**
437      * @private
438      * @override
439      * Contribute class-specific tools to the header.
440      * Called by Panel's initTools.
441      */
442     addTools: function() {
443         var me = this;
444
445         // Call Panel's initTools
446         me.callParent();
447
448         if (me.minimizable) {
449             me.addTool({
450                 type: 'minimize',
451                 handler: Ext.Function.bind(me.minimize, me, [])
452             });
453         }
454         if (me.maximizable) {
455             me.addTool({
456                 type: 'maximize',
457                 handler: Ext.Function.bind(me.maximize, me, [])
458             });
459             me.addTool({
460                 type: 'restore',
461                 handler: Ext.Function.bind(me.restore, me, []),
462                 hidden: true
463             });
464         }
465     },
466
467     /**
468      * Gets the configured default focus item.  If a {@link #defaultFocus} is set, it will receive focus, otherwise the
469      * Container itself will receive focus.
470      */
471     getFocusEl: function() {
472         var me = this,
473             f = me.focusEl,
474             defaultComp = me.defaultButton || me.defaultFocus,
475             t = typeof db,
476             el,
477             ct;
478
479         if (Ext.isDefined(defaultComp)) {
480             if (Ext.isNumber(defaultComp)) {
481                 f = me.query('button')[defaultComp];
482             } else if (Ext.isString(defaultComp)) {
483                 f = me.down('#' + defaultComp);
484             } else {
485                 f = defaultComp;
486             }
487         }
488         return f || me.focusEl;
489     },
490
491     // private
492     beforeShow: function() {
493         this.callParent();
494
495         if (this.expandOnShow) {
496             this.expand(false);
497         }
498     },
499
500     // private
501     afterShow: function(animateTarget) {
502         var me = this;
503
504         // Perform superclass's afterShow tasks
505         // Which might include animating a proxy from an animTarget
506         me.callParent(arguments);
507
508         if (me.maximized) {
509             me.fitContainer();
510         }
511
512         me.syncMonitorWindowResize();
513         me.doConstrain();
514
515         if (me.keyMap) {
516             me.keyMap.enable();
517         }
518     },
519
520     // private
521     doClose: function() {
522         var me = this;
523
524         // immediate close
525         if (me.hidden) {
526             me.fireEvent('close', me);
527             me[me.closeAction]();
528         } else {
529             // close after hiding
530             me.hide(me.animTarget, me.doClose, me);
531         }
532     },
533
534     // private
535     afterHide: function() {
536         var me = this;
537
538         // No longer subscribe to resizing now that we're hidden
539         me.syncMonitorWindowResize();
540
541         // Turn off keyboard handling once window is hidden
542         if (me.keyMap) {
543             me.keyMap.disable();
544         }
545
546         // Perform superclass's afterHide tasks.
547         me.callParent(arguments);
548     },
549
550     // private
551     onWindowResize: function() {
552         if (this.maximized) {
553             this.fitContainer();
554         }
555         this.doConstrain();
556     },
557
558     /**
559      * Placeholder method for minimizing the window.  By default, this method simply fires the {@link #minimize} event
560      * since the behavior of minimizing a window is application-specific.  To implement custom minimize behavior,
561      * either the minimize event can be handled or this method can be overridden.
562      * @return {Ext.window.Window} this
563      */
564     minimize: function() {
565         this.fireEvent('minimize', this);
566         return this;
567     },
568
569     afterCollapse: function() {
570         var me = this;
571
572         if (me.maximizable) {
573             me.tools.maximize.hide();
574             me.tools.restore.hide();
575         }
576         if (me.resizer) {
577             me.resizer.disable();
578         }
579         me.callParent(arguments);
580     },
581
582     afterExpand: function() {
583         var me = this;
584
585         if (me.maximized) {
586             me.tools.restore.show();
587         } else if (me.maximizable) {
588             me.tools.maximize.show();
589         }
590         if (me.resizer) {
591             me.resizer.enable();
592         }
593         me.callParent(arguments);
594     },
595
596     /**
597      * Fits the window within its current container and automatically replaces
598      * the {@link #maximizable 'maximize' tool button} with the 'restore' tool button.
599      * Also see {@link #toggleMaximize}.
600      * @return {Ext.window.Window} this
601      */
602     maximize: function() {
603         var me = this;
604
605         if (!me.maximized) {
606             me.expand(false);
607             if (!me.hasSavedRestore) {
608                 me.restoreSize = me.getSize();
609                 me.restorePos = me.getPosition(true);
610             }
611             if (me.maximizable) {
612                 me.tools.maximize.hide();
613                 me.tools.restore.show();
614             }
615             me.maximized = true;
616             me.el.disableShadow();
617
618             if (me.dd) {
619                 me.dd.disable();
620             }
621             if (me.collapseTool) {
622                 me.collapseTool.hide();
623             }
624             me.el.addCls(Ext.baseCSSPrefix + 'window-maximized');
625             me.container.addCls(Ext.baseCSSPrefix + 'window-maximized-ct');
626
627             me.syncMonitorWindowResize();
628             me.setPosition(0, 0);
629             me.fitContainer();
630             me.fireEvent('maximize', me);
631         }
632         return me;
633     },
634
635     /**
636      * Restores a {@link #maximizable maximized}  window back to its original
637      * size and position prior to being maximized and also replaces
638      * the 'restore' tool button with the 'maximize' tool button.
639      * Also see {@link #toggleMaximize}.
640      * @return {Ext.window.Window} this
641      */
642     restore: function() {
643         var me = this,
644             tools = me.tools;
645
646         if (me.maximized) {
647             delete me.hasSavedRestore;
648             me.removeCls(Ext.baseCSSPrefix + 'window-maximized');
649
650             // Toggle tool visibility
651             if (tools.restore) {
652                 tools.restore.hide();
653             }
654             if (tools.maximize) {
655                 tools.maximize.show();
656             }
657             if (me.collapseTool) {
658                 me.collapseTool.show();
659             }
660
661             // Restore the position/sizing
662             me.setPosition(me.restorePos);
663             me.setSize(me.restoreSize);
664
665             // Unset old position/sizing
666             delete me.restorePos;
667             delete me.restoreSize;
668
669             me.maximized = false;
670
671             me.el.enableShadow(true);
672
673             // Allow users to drag and drop again
674             if (me.dd) {
675                 me.dd.enable();
676             }
677
678             me.container.removeCls(Ext.baseCSSPrefix + 'window-maximized-ct');
679
680             me.syncMonitorWindowResize();
681             me.doConstrain();
682             me.fireEvent('restore', me);
683         }
684         return me;
685     },
686
687     /**
688      * Synchronizes the presence of our listener for window resize events. This method
689      * should be called whenever this status might change.
690      * @private
691      */
692     syncMonitorWindowResize: function () {
693         var me = this,
694             currentlyMonitoring = me._monitoringResize,
695             // all the states where we should be listening to window resize:
696             yes = me.monitorResize || me.constrain || me.constrainHeader || me.maximized,
697             // all the states where we veto this:
698             veto = me.hidden || me.destroying || me.isDestroyed;
699
700         if (yes && !veto) {
701             // we should be listening...
702             if (!currentlyMonitoring) {
703                 // but we aren't, so set it up
704                 Ext.EventManager.onWindowResize(me.onWindowResize, me);
705                 me._monitoringResize = true;
706             }
707         } else if (currentlyMonitoring) {
708             // we should not be listening, but we are, so tear it down
709             Ext.EventManager.removeResizeListener(me.onWindowResize, me);
710             me._monitoringResize = false;
711         }
712     },
713
714     /**
715      * A shortcut method for toggling between {@link #maximize} and {@link #restore} based on the current maximized
716      * state of the window.
717      * @return {Ext.window.Window} this
718      */
719     toggleMaximize: function() {
720         return this[this.maximized ? 'restore': 'maximize']();
721     }
722
723     /**
724      * @cfg {Boolean} autoWidth @hide
725      * Absolute positioned element and therefore cannot support autoWidth.
726      * A width is a required configuration.
727      **/
728 });