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