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