X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/c930e9176a5a85509c5b0230e2bff5c22a591432..6e39d509471fe9b4e2660e0d1631b350d0c66f40:/src/widgets/Container.js diff --git a/src/widgets/Container.js b/src/widgets/Container.js index 3a14a741..4ed1e406 100644 --- a/src/widgets/Container.js +++ b/src/widgets/Container.js @@ -1,5 +1,5 @@ /*! - * Ext JS Library 3.0.0 + * Ext JS Library 3.1.0 * Copyright(c) 2006-2009 Ext JS, LLC * licensing@extjs.com * http://www.extjs.com/license @@ -13,12 +13,12 @@ *

The most commonly used Container classes are {@link Ext.Panel}, {@link Ext.Window} and {@link Ext.TabPanel}. * If you do not need the capabilities offered by the aforementioned classes you can create a lightweight * Container to be encapsulated by an HTML element to your specifications by using the - * {@link Ext.Component#autoEl autoEl} config option. This is a useful technique when creating + * {@link Ext.Component#autoEl autoEl} config option. This is a useful technique when creating * embedded {@link Ext.layout.ColumnLayout column} layouts inside {@link Ext.form.FormPanel FormPanels} * for example.

* *

The code below illustrates both how to explicitly create a Container, and how to implicitly - * create one using the 'container' xtype:


+ * create one using the 'container' xtype:

 // explicitly create a Container
 var embeddedColumns = new Ext.Container({
     autoEl: 'div',  // This is the default
@@ -63,7 +63,7 @@ var embeddedColumns = new Ext.Container({
  * Container, and does not apply any sizing at all.

*

A common mistake is when a developer neglects to specify a * {@link #layout} (e.g. widgets like GridPanels or - * TreePanels are added to Containers for which no {@link #layout} + * TreePanels are added to Containers for which no {@link #layout} * has been specified). If a Container is left to use the default * {@link Ext.layout.ContainerLayout ContainerLayout} scheme, none of its * child components will be resized, or changed in any way when the Container @@ -89,12 +89,11 @@ myTabPanel.{@link Ext.TabPanel#setActiveTab setActiveTab}(myNewGrid); *

Overnesting is a common problem. * An example of overnesting occurs when a GridPanel is added to a TabPanel * by wrapping the GridPanel inside a wrapping Panel (that has no - * {@link #layout} specified) and then add that wrapping Panel + * {@link #layout} specified) and then add that wrapping Panel * to the TabPanel. The point to realize is that a GridPanel is a * Component which can be added directly to a Container. If the wrapping Panel - * has no {@link #layout} configuration, then the overnested + * has no {@link #layout} configuration, then the overnested * GridPanel will not be sized as expected.

-

* *

Adding via remote configuration

* @@ -122,7 +121,7 @@ Ext.Ajax.request({ });
*

The server script needs to return an executable Javascript statement which, when processed - * using eval(), will return either a config object with an {@link Ext.Component#xtype xtype}, + * using eval(), will return either a config object with an {@link Ext.Component#xtype xtype}, * or an instantiated Component. The server might return this for example:


 (function() {
     function formatDate(value){
@@ -162,10 +161,10 @@ Ext.Ajax.request({
     return grid;  // return instantiated component
 })();
 
- *

When the above code fragment is passed through the eval function in the success handler + *

When the above code fragment is passed through the eval function in the success handler * of the Ajax request, the code is executed by the Javascript processor, and the anonymous function * runs, and returns the instantiated grid component.

- *

Note: since the code above is generated by a server script, the baseParams for + *

Note: since the code above is generated by a server script, the baseParams for * the Store, the metadata to allow generation of the Record layout, and the ColumnModel * can all be generated into the code since these are all known on the server.

* @@ -180,11 +179,12 @@ Ext.Container = Ext.extend(Ext.BoxComponent, { */ /** * @cfg {String/Object} layout - * When creating complex UIs, it is important to remember that sizing and - * positioning of child items is the responsibility of the Container's - * layout manager. If you expect child items to be sized in response to - * user interactions, you must specify a layout manager which - * creates and manages the type of layout you have in mind. For example:

+     * 

*Important: In order for child items to be correctly sized and + * positioned, typically a layout manager must be specified through + * the layout configuration option.

+ *

The sizing and positioning of child {@link items} is the responsibility of + * the Container's layout manager which creates and manages the type of layout + * you have in mind. For example:


 new Ext.Window({
     width:300, height: 300,
     layout: 'fit', // explicitly set layout manager: override the default (layout:'auto')
@@ -193,13 +193,17 @@ new Ext.Window({
     }]
 }).show();
      * 
- *

Omitting the {@link #layout} config means that the - * {@link Ext.layout.ContainerLayout default layout manager} will be used which does - * nothing but render child components sequentially into the Container (no sizing or - * positioning will be performed in this situation).

- *

The layout manager class for this container may be specified as either as an - * Object or as a String:

- *
* - *
  • type
  • + *
  • type
  • *

    The layout type to be used for this container. If not specified, * a default {@link Ext.layout.ContainerLayout} will be created and used.

    - *

    Valid layout type values are:

    + *

    Valid layout type values are:

    *
    * *
  • Layout specific configuration properties
  • *

    Additional layout specific configuration properties may also be * specified. For complete details regarding the valid config options for - * each layout type, see the layout class corresponding to the type + * each layout type, see the layout class corresponding to the type * specified.

    * * @@ -251,13 +255,13 @@ layoutConfig: { align: 'left' } - *
  • layout
  • - *

    The layout type to be used for this container (see list + *

  • layout
  • + *

    The layout type to be used for this container (see list * of valid layout type values above).


    - *
  • {@link #layoutConfig}
  • + *
  • {@link #layoutConfig}
  • *

    Additional layout specific configuration properties. For complete * details regarding the valid config options for each layout type, see the - * layout class corresponding to the layout specified.

    + * layout class corresponding to the layout specified.

    * */ /** @@ -268,12 +272,12 @@ layoutConfig: { */ /** * @cfg {Boolean/Number} bufferResize - * When set to true (100 milliseconds) or a number of milliseconds, the layout assigned for this container will buffer + * When set to true (50 milliseconds) or a number of milliseconds, the layout assigned for this container will buffer * the frequency it calculates and does a re-layout of components. This is useful for heavy containers or containers - * with a large quantity of sub-components for which frequent layout calls would be expensive. + * with a large quantity of sub-components for which frequent layout calls would be expensive. Defaults to 50. */ - bufferResize: 100, - + bufferResize: 50, + /** * @cfg {String/Number} activeItem * A string component id or the numeric index of the component that should be initially activated within the @@ -284,7 +288,7 @@ layoutConfig: { */ /** * @cfg {Object/Array} items - *
    ** IMPORTANT: be sure to specify a {@link #layout} ! **
    + *
    ** IMPORTANT: be sure to {@link #layout specify a layout} if needed ! **
    *

    A single item, or an array of child Components to be added to this container, * for example:

    *
    
    @@ -321,14 +325,18 @@ layout: 'anchor', // specify a layout!
          * 
          */
         /**
    -     * @cfg {Object} defaults
    -     * 

    A config object that will be applied to all components added to this container either via the {@link #items} - * config or via the {@link #add} or {@link #insert} methods. The defaults config can contain any - * number of name/value property pairs to be added to each item, and should be valid for the types of items - * being added to the container. For example, to automatically apply padding to the body of each of a set of - * contained {@link Ext.Panel} items, you could pass: defaults: {bodyStyle:'padding:15px'}.


    - *

    Note: defaults will not be applied to config objects if the option is already specified. - * For example:

    
    +     * @cfg {Object|Function} defaults
    +     * 

    This option is a means of applying default settings to all added items whether added through the {@link #items} + * config or via the {@link #add} or {@link #insert} methods.

    + *

    If an added item is a config object, and not an instantiated Component, then the default properties are + * unconditionally applied. If the added item is an instantiated Component, then the default properties are + * applied conditionally so as not to override existing properties in the item.

    + *

    If the defaults option is specified as a function, then the function will be called using this Container as the + * scope (this reference) and passing the added item as the first parameter. Any resulting object + * from that call is then applied to the item as default properties.

    + *

    For example, to automatically apply padding to the body of each of a set of + * contained {@link Ext.Panel} items, you could pass: defaults: {bodyStyle:'padding:15px'}.

    + *

    Usage:

    
     defaults: {               // defaults are applied to items, not the container
         autoScroll:true
     },
    @@ -366,11 +374,24 @@ items: [
         /** @cfg {String} defaultType
          * 

    The default {@link Ext.Component xtype} of child Components to create in this Container when * a child item is specified as a raw configuration object, rather than as an instantiated Component.

    - *

    Defaults to 'panel', except {@link Ext.menu.Menu} which defaults to 'menuitem', - * and {@link Ext.Toolbar} and {@link Ext.ButtonGroup} which default to 'button'.

    + *

    Defaults to 'panel', except {@link Ext.menu.Menu} which defaults to 'menuitem', + * and {@link Ext.Toolbar} and {@link Ext.ButtonGroup} which default to 'button'.

    */ defaultType : 'panel', + /** @cfg {String} resizeEvent + * The event to listen to for resizing in layouts. Defaults to 'resize'. + */ + resizeEvent: 'resize', + + /** + * @cfg {Array} bubbleEvents + *

    An array of events that, when fired, should be bubbled to any parent container. + * See {@link Ext.util.Observable#enableBubble}. + * Defaults to ['add', 'remove']. + */ + bubbleEvents: ['add', 'remove'], + // private initComponent : function(){ Ext.Container.superclass.initComponent.call(this); @@ -419,7 +440,7 @@ items: [ 'remove' ); - this.enableBubble('add', 'remove'); + this.enableBubble(this.bubbleEvents); /** * The collection of components in this container as a {@link Ext.util.MixedCollection} @@ -429,11 +450,7 @@ items: [ var items = this.items; if(items){ delete this.items; - if(Ext.isArray(items) && items.length > 0){ - this.add.apply(this, items); - }else{ - this.add(items); - } + this.add(items); } }, @@ -455,29 +472,35 @@ items: [ layout.setContainer(this); }, - // private - render : function(){ - Ext.Container.superclass.render.apply(this, arguments); - if(this.layout){ - if(Ext.isObject(this.layout) && !this.layout.layout){ - this.layoutConfig = this.layout; - this.layout = this.layoutConfig.type; - } - if(typeof this.layout == 'string'){ - this.layout = new Ext.Container.LAYOUTS[this.layout.toLowerCase()](this.layoutConfig); - } - this.setLayout(this.layout); + afterRender: function(){ + this.layoutDone = false; + if(!this.layout){ + this.layout = 'auto'; + } + if(Ext.isObject(this.layout) && !this.layout.layout){ + this.layoutConfig = this.layout; + this.layout = this.layoutConfig.type; + } + if(Ext.isString(this.layout)){ + this.layout = new Ext.Container.LAYOUTS[this.layout.toLowerCase()](this.layoutConfig); + } + this.setLayout(this.layout); - if(this.activeItem !== undefined){ - var item = this.activeItem; - delete this.activeItem; - this.layout.setActiveItem(item); - } + // BoxComponent's afterRender will set the size. + // This will will trigger a layout if the layout is configured to monitor resize + Ext.Container.superclass.afterRender.call(this); + + if(Ext.isDefined(this.activeItem)){ + var item = this.activeItem; + delete this.activeItem; + this.layout.setActiveItem(item); } - if(!this.ownerCt){ - // force a layout if no ownerCt is set + + // If we have no ownerCt and the BoxComponent's sizing did not trigger a layout, force a layout + if(!this.ownerCt && !this.layoutDone){ this.doLayout(false, true); } + if(this.monitorResize === true){ Ext.EventManager.onWindowResize(this.doLayout, this, [false]); } @@ -510,10 +533,10 @@ items: [ * *

    Notes : *

      - *
    • If the Container is already rendered when add + *
    • If the Container is already rendered when add * is called, you may need to call {@link #doLayout} to refresh the view which causes * any unrendered child Components to be rendered. This is required so that you can - * add multiple child components if needed while only refreshing the layout + * add multiple child components if needed while only refreshing the layout * once. For example:
      
       var tb = new {@link Ext.Toolbar}();
       tb.render(document.body);  // toolbar is rendered
      @@ -536,21 +559,40 @@ tb.{@link #doLayout}();             // refresh the layout
               this.initItems();
               var args = arguments.length > 1;
               if(args || Ext.isArray(comp)){
      +            var result = [];
                   Ext.each(args ? arguments : comp, function(c){
      -                this.add(c);
      +                result.push(this.add(c));
                   }, this);
      -            return;
      +            return result;
               }
               var c = this.lookupComponent(this.applyDefaults(comp));
      -        var pos = this.items.length;
      -        if(this.fireEvent('beforeadd', this, c, pos) !== false && this.onBeforeAdd(c) !== false){
      +        var index = this.items.length;
      +        if(this.fireEvent('beforeadd', this, c, index) !== false && this.onBeforeAdd(c) !== false){
                   this.items.add(c);
      -            c.ownerCt = this;
      -            this.fireEvent('add', this, c, pos);
      +            // *onAdded
      +            c.onAdded(this, index);
      +            this.onAdd(c);
      +            this.fireEvent('add', this, c, index);
               }
               return c;
           },
       
      +    onAdd : function(c){
      +        // Empty template method
      +    },
      +    
      +    // private
      +    onAdded : function(container, pos) {
      +        //overridden here so we can cascade down, not worth creating a template method.
      +        this.ownerCt = container;
      +        this.initRef();
      +        //initialize references for child items
      +        this.cascade(function(c){
      +            c.initRef();
      +        });
      +        this.fireEvent('added', this, container, pos);
      +    },
      +
           /**
            * Inserts a Component into this Container at a specified index. Fires the
            * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
      @@ -573,20 +615,21 @@ tb.{@link #doLayout}();             // refresh the layout
               this.initItems();
               var a = arguments, len = a.length;
               if(len > 2){
      +            var result = [];
                   for(var i = len-1; i >= 1; --i) {
      -                this.insert(index, a[i]);
      +                result.push(this.insert(index, a[i]));
                   }
      -            return;
      +            return result;
               }
               var c = this.lookupComponent(this.applyDefaults(comp));
      -
      -        if(c.ownerCt == this && this.items.indexOf(c) < index){
      -            --index;
      -        }
      -
      +        index = Math.min(index, this.items.length);
               if(this.fireEvent('beforeadd', this, c, index) !== false && this.onBeforeAdd(c) !== false){
      +            if(c.ownerCt == this){
      +                this.items.remove(c);
      +            }
                   this.items.insert(index, c);
      -            c.ownerCt = this;
      +            c.onAdded(this, index);
      +            this.onAdd(c);
                   this.fireEvent('add', this, c, index);
               }
               return c;
      @@ -594,14 +637,18 @@ tb.{@link #doLayout}();             // refresh the layout
       
           // private
           applyDefaults : function(c){
      -        if(this.defaults){
      -            if(typeof c == 'string'){
      +        var d = this.defaults;
      +        if(d){
      +            if(Ext.isFunction(d)){
      +                d = d.call(this, c);
      +            }
      +            if(Ext.isString(c)){
                       c = Ext.ComponentMgr.get(c);
      -                Ext.apply(c, this.defaults);
      +                Ext.apply(c, d);
                   }else if(!c.events){
      -                Ext.applyIf(c, this.defaults);
      +                Ext.applyIf(c, d);
                   }else{
      -                Ext.apply(c, this.defaults);
      +                Ext.apply(c, d);
                   }
               }
               return c;
      @@ -629,19 +676,29 @@ tb.{@link #doLayout}();             // refresh the layout
               this.initItems();
               var c = this.getComponent(comp);
               if(c && this.fireEvent('beforeremove', this, c) !== false){
      -            this.items.remove(c);
      -            delete c.ownerCt;
      -            if(autoDestroy === true || (autoDestroy !== false && this.autoDestroy)){
      -                c.destroy();
      -            }
      -            if(this.layout && this.layout.activeItem == c){
      -                delete this.layout.activeItem;
      -            }
      +            this.doRemove(c, autoDestroy);
                   this.fireEvent('remove', this, c);
               }
               return c;
           },
       
      +    onRemove: function(c){
      +        // Empty template method
      +    },
      +
      +    // private
      +    doRemove: function(c, autoDestroy){
      +        if(this.layout && this.rendered){
      +            this.layout.onRemove(c);
      +        }
      +        this.items.remove(c);
      +        c.onRemoved();
      +        this.onRemove(c);
      +        if(autoDestroy === true || (autoDestroy !== false && this.autoDestroy)){
      +            c.destroy();
      +        }
      +    },
      +
           /**
            * Removes all components from this container.
            * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
      @@ -669,9 +726,9 @@ tb.{@link #doLayout}();             // refresh the layout
            * and gets a direct child component of this container.
            * @param {String/Number} comp This parameter may be any of the following:
            * 
        - *
      • a String : representing the {@link Ext.Component#itemId itemId} + *
      • a String : representing the {@link Ext.Component#itemId itemId} * or {@link Ext.Component#id id} of the child component
      • - *
      • a Number : representing the position of the child component + *
      • a Number : representing the position of the child component * within the {@link #items} property
      • *
      *

      For additional information see {@link Ext.util.MixedCollection#get}. @@ -679,14 +736,14 @@ tb.{@link #doLayout}(); // refresh the layout */ getComponent : function(comp){ if(Ext.isObject(comp)){ - return comp; + comp = comp.getItemId(); } return this.items.get(comp); }, // private lookupComponent : function(comp){ - if(typeof comp == 'string'){ + if(Ext.isString(comp)){ return Ext.ComponentMgr.get(comp); }else if(!comp.events){ return this.createComponent(comp); @@ -695,8 +752,23 @@ tb.{@link #doLayout}(); // refresh the layout }, // private - createComponent : function(config){ - return Ext.create(config, this.defaultType); + createComponent : function(config, defaultType){ + // add in ownerCt at creation time but then immediately + // remove so that onBeforeAdd can handle it + var c = config.render ? config : Ext.create(Ext.apply({ + ownerCt: this + }, config), defaultType || this.defaultType); + delete c.ownerCt; + return c; + }, + + /** + * We can only lay out if there is a view area in which to layout. + * display:none on the layout target, *or any of its parent elements* will mean it has no view area. + */ + canLayout: function() { + var el = this.getLayoutTarget(), vs; + return !!(el && (vs = el.dom.offsetWidth || el.dom.offsetHeight)); }, /** @@ -709,42 +781,80 @@ tb.{@link #doLayout}(); // refresh the layout */ doLayout: function(shallow, force){ var rendered = this.rendered, - forceLayout = this.forceLayout; + forceLayout = force || this.forceLayout, + cs, i, len, c; - if(!this.isVisible() || this.collapsed){ + this.layoutDone = true; + if(!this.canLayout() || this.collapsed){ this.deferLayout = this.deferLayout || !shallow; - if(!(force || forceLayout)){ + if(!forceLayout){ return; } shallow = shallow && !this.deferLayout; } else { delete this.deferLayout; } + + cs = (shallow !== true && this.items) ? this.items.items : []; + +// Inhibit child Containers from relaying on resize since we are about to to explicitly call doLayout on them all! + for(i = 0, len = cs.length; i < len; i++){ + if ((c = cs[i]).layout) { + c.suspendLayoutResize = true; + } + } + +// Tell the layout manager to ensure all child items are rendered, and sized according to their rules. +// Will not cause the child items to relayout. if(rendered && this.layout){ this.layout.layout(); } - if(shallow !== true && this.items){ - var cs = this.items.items; - for(var i = 0, len = cs.length; i < len; i++){ - var c = cs[i]; - if(c.doLayout){ - c.forceLayout = forceLayout; - c.doLayout(); - } + +// Explicitly lay out all child items + for(i = 0; i < len; i++){ + if((c = cs[i]).doLayout){ + c.doLayout(false, forceLayout); } } if(rendered){ - this.onLayout(shallow, force); + this.onLayout(shallow, forceLayout); } + // Initial layout completed + this.hasLayout = true; delete this.forceLayout; + +// Re-enable child layouts relaying on resize. + for(i = 0; i < len; i++){ + if ((c = cs[i]).layout) { + delete c.suspendLayoutResize; + } + } }, //private onLayout : Ext.emptyFn, + onResize: function(adjWidth, adjHeight, rawWidth, rawHeight){ + Ext.Container.superclass.onResize.apply(this, arguments); + if ((this.rendered && this.layout && this.layout.monitorResize) && !this.suspendLayoutResize) { + this.layout.onResize(); + } + }, + + // private + hasLayoutPending: function(){ + // Traverse hierarchy to see if any parent container has a pending layout. + var pending = this.layoutPending; + this.ownerCt.bubble(function(c){ + return !(pending = c.layoutPending); + }); + return pending; + + }, + onShow : function(){ Ext.Container.superclass.onShow.call(this); - if(this.deferLayout !== undefined){ + if(Ext.isDefined(this.deferLayout)){ this.doLayout(true); } }, @@ -764,8 +874,11 @@ tb.{@link #doLayout}(); // refresh the layout // private beforeDestroy : function(){ + var c; if(this.items){ - Ext.destroy.apply(Ext, this.items.items); + while(c = this.items.first()){ + this.doRemove(c, true); + } } if(this.monitorResize){ Ext.EventManager.removeResizeListener(this.doLayout, this);