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