Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / panel / AbstractPanel.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.panel.AbstractPanel
17  * @extends Ext.container.Container
18  * <p>A base class which provides methods common to Panel classes across the Sencha product range.</p>
19  * <p>Please refer to sub class's documentation</p>
20  */
21 Ext.define('Ext.panel.AbstractPanel', {
22
23     /* Begin Definitions */
24
25     extend: 'Ext.container.Container',
26
27     requires: ['Ext.util.MixedCollection', 'Ext.core.Element', 'Ext.toolbar.Toolbar'],
28
29     /* End Definitions */
30
31     /**
32      * @cfg {String} baseCls
33      * The base CSS class to apply to this panel's element (defaults to <code>'x-panel'</code>).
34      */
35     baseCls : Ext.baseCSSPrefix + 'panel',
36
37     /**
38      * @cfg {Number/String} bodyPadding
39      * A shortcut for setting a padding style on the body element. The value can either be
40      * a number to be applied to all sides, or a normal css string describing padding.
41      * Defaults to <code>undefined</code>.
42      */
43
44     /**
45      * @cfg {Boolean} bodyBorder
46      * A shortcut to add or remove the border on the body of a panel. This only applies to a panel which has the {@link #frame} configuration set to `true`.
47      * Defaults to <code>undefined</code>.
48      */
49
50     /**
51      * @cfg {String/Object/Function} bodyStyle
52      * Custom CSS styles to be applied to the panel's body element, which can be supplied as a valid CSS style string,
53      * an object containing style property name/value pairs or a function that returns such a string or object.
54      * For example, these two formats are interpreted to be equivalent:<pre><code>
55 bodyStyle: 'background:#ffc; padding:10px;'
56
57 bodyStyle: {
58     background: '#ffc',
59     padding: '10px'
60 }
61      * </code></pre>
62      */
63
64     /**
65      * @cfg {String/Array} bodyCls
66      * A CSS class, space-delimited string of classes, or array of classes to be applied to the panel's body element.
67      * The following examples are all valid:<pre><code>
68 bodyCls: 'foo'
69 bodyCls: 'foo bar'
70 bodyCls: ['foo', 'bar']
71      * </code></pre>
72      */
73
74     isPanel: true,
75
76     componentLayout: 'dock',
77
78     /**
79      * @cfg {Object} defaultDockWeights
80      * This object holds the default weights applied to dockedItems that have no weight. These start with a
81      * weight of 1, to allow negative weights to insert before top items and are odd numbers
82      * so that even weights can be used to get between different dock orders.
83      * 
84      * To make default docking order match border layout, do this:
85      * <pre><code>
86 Ext.panel.AbstractPanel.prototype.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };</code></pre>
87      * Changing these defaults as above or individually on this object will effect all Panels.
88      * To change the defaults on a single panel, you should replace the entire object:
89      * <pre><code>
90 initComponent: function () {
91     // NOTE: Don't change members of defaultDockWeights since the object is shared.
92     this.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };
93
94     this.callParent();
95 }</code></pre>
96      *
97      * To change only one of the default values, you do this:
98      * <pre><code>
99 initComponent: function () {
100     // NOTE: Don't change members of defaultDockWeights since the object is shared.
101     this.defaultDockWeights = Ext.applyIf({ top: 10 }, this.defaultDockWeights);
102
103     this.callParent();
104 }</code></pre>
105      */
106     defaultDockWeights: { top: 1, left: 3, right: 5, bottom: 7 },
107
108     renderTpl: ['<div class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl> {baseCls}-body-{ui}<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl></tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>'],
109
110     // TODO: Move code examples into product-specific files. The code snippet below is Touch only.
111     /**
112      * @cfg {Object/Array} dockedItems
113      * A component or series of components to be added as docked items to this panel.
114      * The docked items can be docked to either the top, right, left or bottom of a panel.
115      * This is typically used for things like toolbars or tab bars:
116      * <pre><code>
117 var panel = new Ext.panel.Panel({
118     fullscreen: true,
119     dockedItems: [{
120         xtype: 'toolbar',
121         dock: 'top',
122         items: [{
123             text: 'Docked to the top'
124         }]
125     }]
126 });</code></pre>
127      */
128
129     border: true,
130
131     initComponent : function() {
132         var me = this;
133
134         me.addEvents(
135             /**
136              * @event bodyresize
137              * Fires after the Panel has been resized.
138              * @param {Ext.panel.Panel} p the Panel which has been resized.
139              * @param {Number} width The Panel body's new width.
140              * @param {Number} height The Panel body's new height.
141              */
142             'bodyresize'
143             // // inherited
144             // 'activate',
145             // // inherited
146             // 'deactivate'
147         );
148
149         Ext.applyIf(me.renderSelectors, {
150             body: '.' + me.baseCls + '-body'
151         });
152
153         //!frame
154         //!border
155
156         if (me.frame && me.border && me.bodyBorder === undefined) {
157             me.bodyBorder = false;
158         }
159         if (me.frame && me.border && (me.bodyBorder === false || me.bodyBorder === 0)) {
160             me.manageBodyBorders = true;
161         }
162
163         me.callParent();
164     },
165
166     // @private
167     initItems : function() {
168         var me = this,
169             items = me.dockedItems;
170
171         me.callParent();
172         me.dockedItems = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
173         if (items) {
174             me.addDocked(items);
175         }
176     },
177
178     /**
179      * Finds a docked component by id, itemId or position. Also see {@link #getDockedItems}
180      * @param {String/Number} comp The id, itemId or position of the docked component (see {@link #getComponent} for details)
181      * @return {Ext.Component} The docked component (if found)
182      */
183     getDockedComponent: function(comp) {
184         if (Ext.isObject(comp)) {
185             comp = comp.getItemId();
186         }
187         return this.dockedItems.get(comp);
188     },
189
190     /**
191      * Attempts a default component lookup (see {@link Ext.container.Container#getComponent}). If the component is not found in the normal
192      * items, the dockedItems are searched and the matched component (if any) returned (see {@loink #getDockedComponent}). Note that docked
193      * items will only be matched by component id or itemId -- if you pass a numeric index only non-docked child components will be searched.
194      * @param {String/Number} comp The component id, itemId or position to find
195      * @return {Ext.Component} The component (if found)
196      */
197     getComponent: function(comp) {
198         var component = this.callParent(arguments);
199         if (component === undefined && !Ext.isNumber(comp)) {
200             // If the arg is a numeric index skip docked items
201             component = this.getDockedComponent(comp);
202         }
203         return component;
204     },
205
206     /**
207      * Parses the {@link bodyStyle} config if available to create a style string that will be applied to the body element.
208      * This also includes {@link bodyPadding} and {@link bodyBorder} if available.
209      * @return {String} A CSS style string with body styles, padding and border.
210      * @private
211      */
212     initBodyStyles: function() {
213         var me = this,
214             bodyStyle = me.bodyStyle,
215             styles = [],
216             Element = Ext.core.Element,
217             prop;
218
219         if (Ext.isFunction(bodyStyle)) {
220             bodyStyle = bodyStyle();
221         }
222         if (Ext.isString(bodyStyle)) {
223             styles = bodyStyle.split(';');
224         } else {
225             for (prop in bodyStyle) {
226                 if (bodyStyle.hasOwnProperty(prop)) {
227                     styles.push(prop + ':' + bodyStyle[prop]);
228                 }
229             }
230         }
231
232         if (me.bodyPadding !== undefined) {
233             styles.push('padding: ' + Element.unitizeBox((me.bodyPadding === true) ? 5 : me.bodyPadding));
234         }
235         if (me.frame && me.bodyBorder) {
236             if (!Ext.isNumber(me.bodyBorder)) {
237                 me.bodyBorder = 1;
238             }
239             styles.push('border-width: ' + Element.unitizeBox(me.bodyBorder));
240         }
241         delete me.bodyStyle;
242         return styles.length ? styles.join(';') : undefined;
243     },
244
245     /**
246      * Parse the {@link bodyCls} config if available to create a comma-delimited string of
247      * CSS classes to be applied to the body element.
248      * @return {String} The CSS class(es)
249      * @private
250      */
251     initBodyCls: function() {
252         var me = this,
253             cls = '',
254             bodyCls = me.bodyCls;
255
256         if (bodyCls) {
257             Ext.each(bodyCls, function(v) {
258                 cls += " " + v;
259             });
260             delete me.bodyCls;
261         }
262         return cls.length > 0 ? cls : undefined;
263     },
264
265     /**
266      * Initialized the renderData to be used when rendering the renderTpl.
267      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
268      * @private
269      */
270     initRenderData: function() {
271         return Ext.applyIf(this.callParent(), {
272             bodyStyle: this.initBodyStyles(),
273             bodyCls: this.initBodyCls()
274         });
275     },
276
277     /**
278      * Adds docked item(s) to the panel.
279      * @param {Object/Array} component The Component or array of components to add. The components
280      * must include a 'dock' parameter on each component to indicate where it should be docked ('top', 'right',
281      * 'bottom', 'left').
282      * @param {Number} pos (optional) The index at which the Component will be added
283      */
284     addDocked : function(items, pos) {
285         var me = this,
286             i = 0,
287             item, length;
288
289         items = me.prepareItems(items);
290         length = items.length;
291
292         for (; i < length; i++) {
293             item = items[i];
294             item.dock = item.dock || 'top';
295
296             // Allow older browsers to target docked items to style without borders
297             if (me.border === false) {
298                 // item.cls = item.cls || '' + ' ' + me.baseCls + '-noborder-docked-' + item.dock;
299             }
300
301             if (pos !== undefined) {
302                 me.dockedItems.insert(pos + i, item);
303             }
304             else {
305                 me.dockedItems.add(item);
306             }
307             item.onAdded(me, i);
308             me.onDockedAdd(item);
309         }
310
311         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
312         me.componentLayout.childrenChanged = true;
313         if (me.rendered && !me.suspendLayout) {
314             me.doComponentLayout();
315         }
316         return items;
317     },
318
319     // Placeholder empty functions
320     onDockedAdd : Ext.emptyFn,
321     onDockedRemove : Ext.emptyFn,
322
323     /**
324      * Inserts docked item(s) to the panel at the indicated position.
325      * @param {Number} pos The index at which the Component will be inserted
326      * @param {Object/Array} component. The Component or array of components to add. The components
327      * must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
328      * 'bottom', 'left').
329      */
330     insertDocked : function(pos, items) {
331         this.addDocked(items, pos);
332     },
333
334     /**
335      * Removes the docked item from the panel.
336      * @param {Ext.Component} item. The Component to remove.
337      * @param {Boolean} autoDestroy (optional) Destroy the component after removal.
338      */
339     removeDocked : function(item, autoDestroy) {
340         var me = this,
341             layout,
342             hasLayout;
343
344         if (!me.dockedItems.contains(item)) {
345             return item;
346         }
347
348         layout = me.componentLayout;
349         hasLayout = layout && me.rendered;
350
351         if (hasLayout) {
352             layout.onRemove(item);
353         }
354
355         me.dockedItems.remove(item);
356         item.onRemoved();
357         me.onDockedRemove(item);
358
359         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
360             item.destroy();
361         }
362
363         if (hasLayout && !autoDestroy) {
364             layout.afterRemove(item);
365         }
366
367
368         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
369         me.componentLayout.childrenChanged = true;
370         if (!me.destroying && !me.suspendLayout) {
371             me.doComponentLayout();
372         }
373
374         return item;
375     },
376
377     /**
378      * Retrieve an array of all currently docked Components.
379      * @param {String} cqSelector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
380      * @return {Array} An array of components.
381      */
382     getDockedItems : function(cqSelector) {
383         var me = this,
384             defaultWeight = me.defaultDockWeights,
385             dockedItems;
386
387         if (me.dockedItems && me.dockedItems.items.length) {
388             // Allow filtering of returned docked items by CQ selector.
389             if (cqSelector) {
390                 dockedItems = Ext.ComponentQuery.query(cqSelector, me.dockedItems.items);
391             } else {
392                 dockedItems = me.dockedItems.items.slice();
393             }
394
395             Ext.Array.sort(dockedItems, function(a, b) {
396                 // Docked items are ordered by their visual representation by default (t,l,r,b)
397                 var aw = a.weight || defaultWeight[a.dock],
398                     bw = b.weight || defaultWeight[b.dock];
399                 if (Ext.isNumber(aw) && Ext.isNumber(bw)) {
400                     return aw - bw;
401                 }
402                 return 0;
403             });
404
405             return dockedItems;
406         }
407         return [];
408     },
409
410     // inherit docs
411     addUIClsToElement: function(cls, force) {
412         var me = this,
413             result = me.callParent(arguments),
414             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
415             array, i;
416
417         if (!force && me.rendered) {
418             if (me.bodyCls) {
419                 me.body.addCls(me.bodyCls);
420             } else {
421                 me.body.addCls(classes);
422             }
423         } else {
424             if (me.bodyCls) {
425                 array = me.bodyCls.split(' ');
426
427                 for (i = 0; i < classes.length; i++) {
428                     if (!Ext.Array.contains(array, classes[i])) {
429                         array.push(classes[i]);
430                     }
431                 }
432
433                 me.bodyCls = array.join(' ');
434             } else {
435                 me.bodyCls = classes.join(' ');
436             }
437         }
438
439         return result;
440     },
441
442     // inherit docs
443     removeUIClsFromElement: function(cls, force) {
444         var me = this,
445             result = me.callParent(arguments),
446             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
447             array, i;
448
449         if (!force && me.rendered) {
450             if (me.bodyCls) {
451                 me.body.removeCls(me.bodyCls);
452             } else {
453                 me.body.removeCls(classes);
454             }
455         } else {
456             if (me.bodyCls) {
457                 array = me.bodyCls.split(' ');
458
459                 for (i = 0; i < classes.length; i++) {
460                     Ext.Array.remove(array, classes[i]);
461                 }
462
463                 me.bodyCls = array.join(' ');
464             }
465         }
466
467         return result;
468     },
469
470     // inherit docs
471     addUIToElement: function(force) {
472         var me = this,
473             cls = me.baseCls + '-body-' + me.ui,
474             array;
475
476         me.callParent(arguments);
477
478         if (!force && me.rendered) {
479             if (me.bodyCls) {
480                 me.body.addCls(me.bodyCls);
481             } else {
482                 me.body.addCls(cls);
483             }
484         } else {
485             if (me.bodyCls) {
486                 array = me.bodyCls.split(' ');
487
488                 if (!Ext.Array.contains(array, cls)) {
489                     array.push(cls);
490                 }
491
492                 me.bodyCls = array.join(' ');
493             } else {
494                 me.bodyCls = cls;
495             }
496         }
497     },
498
499     // inherit docs
500     removeUIFromElement: function() {
501         var me = this,
502             cls = me.baseCls + '-body-' + me.ui,
503             array;
504
505         me.callParent(arguments);
506
507         if (me.rendered) {
508             if (me.bodyCls) {
509                 me.body.removeCls(me.bodyCls);
510             } else {
511                 me.body.removeCls(cls);
512             }
513         } else {
514             if (me.bodyCls) {
515                 array = me.bodyCls.split(' ');
516                 Ext.Array.remove(array, cls);
517                 me.bodyCls = array.join(' ');
518             } else {
519                 me.bodyCls = cls;
520             }
521         }
522     },
523
524     // @private
525     getTargetEl : function() {
526         return this.body;
527     },
528
529     getRefItems: function(deep) {
530         var items = this.callParent(arguments),
531             // deep fetches all docked items, and their descendants using '*' selector and then '* *'
532             dockedItems = this.getDockedItems(deep ? '*,* *' : undefined),
533             ln = dockedItems.length,
534             i = 0,
535             item;
536
537         // Find the index where we go from top/left docked items to right/bottom docked items
538         for (; i < ln; i++) {
539             item = dockedItems[i];
540             if (item.dock === 'right' || item.dock === 'bottom') {
541                 break;
542             }
543         }
544
545         // Return docked items in the top/left position before our container items, and
546         // return right/bottom positioned items after our container items.
547         // See AbstractDock.renderItems() for more information.
548         return Ext.Array.splice(dockedItems, 0, i).concat(items).concat(dockedItems);
549     },
550
551     beforeDestroy: function(){
552         var docked = this.dockedItems,
553             c;
554
555         if (docked) {
556             while ((c = docked.first())) {
557                 this.removeDocked(c, true);
558             }
559         }
560         this.callParent();
561     },
562
563     setBorder: function(border) {
564         var me = this;
565         me.border = (border !== undefined) ? border : true;
566         if (me.rendered) {
567             me.doComponentLayout();
568         }
569     }
570 });