X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/ee06f37b0f6f6d94cd05a6ffae556660f7c4a2bc..c930e9176a5a85509c5b0230e2bff5c22a591432:/src/widgets/Container.js diff --git a/src/widgets/Container.js b/src/widgets/Container.js new file mode 100644 index 00000000..3a14a741 --- /dev/null +++ b/src/widgets/Container.js @@ -0,0 +1,894 @@ +/*! + * Ext JS Library 3.0.0 + * Copyright(c) 2006-2009 Ext JS, LLC + * licensing@extjs.com + * http://www.extjs.com/license + */ +/** + * @class Ext.Container + * @extends Ext.BoxComponent + *
Base class for any {@link Ext.BoxComponent} that may contain other Components. Containers handle the + * basic behavior of containing items, namely adding, inserting and removing items.
+ * + *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 + * 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:
+// explicitly create a Container
+var embeddedColumns = new Ext.Container({
+ autoEl: 'div', // This is the default
+ layout: 'column',
+ defaults: {
+ // implicitly create Container by specifying xtype
+ xtype: 'container',
+ autoEl: 'div', // This is the default.
+ layout: 'form',
+ columnWidth: 0.5,
+ style: {
+ padding: '10px'
+ }
+ },
+// The two items below will be Ext.Containers, each encapsulated by a <DIV> element.
+ items: [{
+ items: {
+ xtype: 'datefield',
+ name: 'startDate',
+ fieldLabel: 'Start date'
+ }
+ }, {
+ items: {
+ xtype: 'datefield',
+ name: 'endDate',
+ fieldLabel: 'End date'
+ }
+ }]
+});
+ *
+ * Layout
+ *Container classes delegate the rendering of child Components to a layout
+ * manager class which must be configured into the Container using the
+ * {@link #layout}
configuration property.
When either specifying child {@link #items}
of a Container,
+ * or dynamically {@link #add adding} Components to a Container, remember to
+ * consider how you wish the Container to arrange those child elements, and
+ * whether those child elements need to be sized using one of Ext's built-in
+ * {@link #layout}
schemes. By default, Containers use the
+ * {@link Ext.layout.ContainerLayout ContainerLayout} scheme which only
+ * renders child components, appending them one after the other inside the
+ * 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}
+ * 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
+ * is resized.
Certain layout managers allow dynamic addition of child components. + * Those that do include {@link Ext.layout.CardLayout}, + * {@link Ext.layout.AnchorLayout}, {@link Ext.layout.FormLayout}, and + * {@link Ext.layout.TableLayout}. For example:
+// Create the GridPanel.
+var myNewGrid = new Ext.grid.GridPanel({
+ store: myStore,
+ columns: myColumnModel,
+ title: 'Results', // the title becomes the title of the tab
+});
+
+myTabPanel.add(myNewGrid); // {@link Ext.TabPanel} implicitly uses {@link Ext.layout.CardLayout CardLayout}
+myTabPanel.{@link Ext.TabPanel#setActiveTab setActiveTab}(myNewGrid);
+ *
+ * The example above adds a newly created GridPanel to a TabPanel. Note that + * a TabPanel uses {@link Ext.layout.CardLayout} as its layout manager which + * means all its child items are sized to {@link Ext.layout.FitLayout fit} + * exactly into its client area. + *
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 + * 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 + * GridPanel will not be sized as expected.
+ + * + *
Adding via remote configuration
+ * + *A server side script can be used to add Components which are generated dynamically on the server. + * An example of adding a GridPanel to a TabPanel where the GridPanel is generated by the server + * based on certain parameters: + *
+// execute an Ajax request to invoke server side script:
+Ext.Ajax.request({
+ url: 'gen-invoice-grid.php',
+ // send additional parameters to instruct server script
+ params: {
+ startDate: Ext.getCmp('start-date').getValue(),
+ endDate: Ext.getCmp('end-date').getValue()
+ },
+ // process the response object to add it to the TabPanel:
+ success: function(xhr) {
+ var newComponent = eval(xhr.responseText); // see discussion below
+ myTabPanel.add(newComponent); // add the component to the TabPanel
+ myTabPanel.setActiveTab(newComponent);
+ },
+ failure: function() {
+ Ext.Msg.alert("Grid create failed", "Server communication failure");
+ }
+});
+
+ * 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}, + * or an instantiated Component. The server might return this for example:
+(function() {
+ function formatDate(value){
+ return value ? value.dateFormat('M d, Y') : '';
+ };
+
+ var store = new Ext.data.Store({
+ url: 'get-invoice-data.php',
+ baseParams: {
+ startDate: '01/01/2008',
+ endDate: '01/31/2008'
+ },
+ reader: new Ext.data.JsonReader({
+ record: 'transaction',
+ idProperty: 'id',
+ totalRecords: 'total'
+ }, [
+ 'customer',
+ 'invNo',
+ {name: 'date', type: 'date', dateFormat: 'm/d/Y'},
+ {name: 'value', type: 'float'}
+ ])
+ });
+
+ var grid = new Ext.grid.GridPanel({
+ title: 'Invoice Report',
+ bbar: new Ext.PagingToolbar(store),
+ store: store,
+ columns: [
+ {header: "Customer", width: 250, dataIndex: 'customer', sortable: true},
+ {header: "Invoice Number", width: 120, dataIndex: 'invNo', sortable: true},
+ {header: "Invoice Date", width: 100, dataIndex: 'date', renderer: formatDate, sortable: true},
+ {header: "Value", width: 120, dataIndex: 'value', renderer: 'usMoney', sortable: true}
+ ],
+ });
+ store.load();
+ return grid; // return instantiated component
+})();
+
+ * 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 + * 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.
+ * + * @xtype container + */ +Ext.Container = Ext.extend(Ext.BoxComponent, { + /** + * @cfg {Boolean} monitorResize + * True to automatically monitor window resize events to handle anything that is sensitive to the current size + * of the viewport. This value is typically managed by the chosen{@link #layout}
and should not need
+ * to be set manually.
+ */
+ /**
+ * @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:
+new Ext.Window({
+ width:300, height: 300,
+ layout: 'fit', // explicitly set layout manager: override the default (layout:'auto')
+ items: [{
+ title: 'Panel inside a 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:
+ *
+layout: {
+ type: 'vbox',
+ padding: '5',
+ align: 'left'
+}
+
+ *
+ * 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:
+ *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 + * specified.
+ * + *
+layout: 'vbox',
+layoutConfig: {
+ padding: '5',
+ align: 'left'
+}
+
+ * The layout type to be used for this container (see list + * of valid layout type values above).
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.
+ *{@link #layout}
if {@link #layout}
+ * has been specified as a string.
+ */
+ /**
+ * @cfg {Boolean/Number} bufferResize
+ * When set to true (100 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.
+ */
+ bufferResize: 100,
+
+ /**
+ * @cfg {String/Number} activeItem
+ * A string component id or the numeric index of the component that should be initially activated within the
+ * container's layout on render. For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
+ * item in the container's collection). activeItem only applies to layout styles that can display
+ * items one at a time (like {@link Ext.layout.AccordionLayout}, {@link Ext.layout.CardLayout} and
+ * {@link Ext.layout.FitLayout}). Related to {@link Ext.layout.ContainerLayout#activeItem}.
+ */
+ /**
+ * @cfg {Object/Array} items
+ * ** IMPORTANT: be sure to specify a {@link #layout}
! **
+ * A single item, or an array of child Components to be added to this container, + * for example:
+ *
+// specifying a single item
+items: {...},
+layout: 'fit', // specify a layout!
+
+// specifying multiple items
+items: [{...}, {...}],
+layout: 'anchor', // specify a layout!
+ *
+ * Each item may be:
+ *{@link Ext.Component#xtype xtype}
{@link Ext.Component#xtype xtype}
is not explicitly
+ * specified, the {@link #defaultType} for that Container is used.Notes:
+ *{@link Ext.Panel#contentEl contentEl}
/
+ * {@link Ext.Panel#html html}
with items
.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:
+defaults: { // defaults are applied to items, not the container
+ autoScroll:true
+},
+items: [
+ {
+ xtype: 'panel', // defaults do not have precedence over
+ id: 'panel1', // options in config objects, so the defaults
+ autoScroll: false // will not be applied here, panel1 will be autoScroll:false
+ },
+ new Ext.Panel({ // defaults do have precedence over options
+ id: 'panel2', // options in components, so the defaults
+ autoScroll: false // will be applied here, panel2 will be autoScroll:true.
+ })
+]
+ *
+ */
+
+
+ /** @cfg {Boolean} autoDestroy
+ * If true the container will automatically destroy any contained component that is removed from it, else
+ * destruction must be handled manually (defaults to true).
+ */
+ autoDestroy : true,
+
+ /** @cfg {Boolean} forceLayout
+ * If true the container will force a layout initially even if hidden or collapsed. This option
+ * is useful for forcing forms to render in collapsed or hidden containers. (defaults to false).
+ */
+ forceLayout: false,
+
+ /** @cfg {Boolean} hideBorders
+ * True to hide the borders of each contained component, false to defer to the component's existing
+ * border settings (defaults to false).
+ */
+ /** @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'.
+ */ + defaultType : 'panel', + + // private + initComponent : function(){ + Ext.Container.superclass.initComponent.call(this); + + this.addEvents( + /** + * @event afterlayout + * Fires when the components in this container are arranged by the associated layout manager. + * @param {Ext.Container} this + * @param {ContainerLayout} layout The ContainerLayout implementation for this container + */ + 'afterlayout', + /** + * @event beforeadd + * Fires before any {@link Ext.Component} is added or inserted into the container. + * A handler can return false to cancel the add. + * @param {Ext.Container} this + * @param {Ext.Component} component The component being added + * @param {Number} index The index at which the component will be added to the container's items collection + */ + 'beforeadd', + /** + * @event beforeremove + * Fires before any {@link Ext.Component} is removed from the container. A handler can return + * false to cancel the remove. + * @param {Ext.Container} this + * @param {Ext.Component} component The component being removed + */ + 'beforeremove', + /** + * @event add + * @bubbles + * Fires after any {@link Ext.Component} is added or inserted into the container. + * @param {Ext.Container} this + * @param {Ext.Component} component The component that was added + * @param {Number} index The index at which the component was added to the container's items collection + */ + 'add', + /** + * @event remove + * @bubbles + * Fires after any {@link Ext.Component} is removed from the container. + * @param {Ext.Container} this + * @param {Ext.Component} component The component that was removed + */ + 'remove' + ); + + this.enableBubble('add', 'remove'); + + /** + * The collection of components in this container as a {@link Ext.util.MixedCollection} + * @type MixedCollection + * @property 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); + } + } + }, + + // private + initItems : function(){ + if(!this.items){ + this.items = new Ext.util.MixedCollection(false, this.getComponentId); + this.getLayout(); // initialize the layout + } + }, + + // private + setLayout : function(layout){ + if(this.layout && this.layout != layout){ + this.layout.setContainer(null); + } + this.initItems(); + this.layout = layout; + 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); + + if(this.activeItem !== undefined){ + var item = this.activeItem; + delete this.activeItem; + this.layout.setActiveItem(item); + } + } + if(!this.ownerCt){ + // force a layout if no ownerCt is set + this.doLayout(false, true); + } + if(this.monitorResize === true){ + Ext.EventManager.onWindowResize(this.doLayout, this, [false]); + } + }, + + /** + *Returns the Element to be used to contain the child Components of this Container.
+ *An implementation is provided which returns the Container's {@link #getEl Element}, but + * if there is a more complex structure to a Container, this may be overridden to return + * the element into which the {@link #layout layout} renders child Components.
+ * @return {Ext.Element} The Element to render child Components into. + */ + getLayoutTarget : function(){ + return this.el; + }, + + // private - used as the key lookup function for the items collection + getComponentId : function(comp){ + return comp.getItemId(); + }, + + /** + *Adds {@link Ext.Component Component}(s) to this Container.
+ *Description : + *
{@link #defaults}
for details).Notes : + *
+var tb = new {@link Ext.Toolbar}();
+tb.render(document.body); // toolbar is rendered
+tb.add({text:'Button 1'}); // add multiple items ({@link #defaultType} for {@link Ext.Toolbar Toolbar} is 'button')
+tb.add({text:'Button 2'});
+tb.{@link #doLayout}(); // refresh the layout
+ *
Either a single component or an Array of components to add. See
+ * {@link #items}
for additional information.
{@link #items}
property
+ * and gets a direct child component of this container.
+ * @param {String/Number} comp This parameter may be any of the following:
+ * {@link Ext.Component#itemId itemId}
+ * or {@link Ext.Component#id id}
of the child component {@link #items}
propertyFor additional information see {@link Ext.util.MixedCollection#get}. + * @return Ext.Component The component (if found). + */ + getComponent : function(comp){ + if(Ext.isObject(comp)){ + return comp; + } + return this.items.get(comp); + }, + + // private + lookupComponent : function(comp){ + if(typeof comp == 'string'){ + return Ext.ComponentMgr.get(comp); + }else if(!comp.events){ + return this.createComponent(comp); + } + return comp; + }, + + // private + createComponent : function(config){ + return Ext.create(config, this.defaultType); + }, + + /** + * Force this container's layout to be recalculated. A call to this function is required after adding a new component + * to an already rendered container, or possibly after changing sizing/position properties of child components. + * @param {Boolean} shallow (optional) True to only calc the layout of this component, and let child components auto + * calc layouts as required (defaults to false, which calls doLayout recursively for each subcontainer) + * @param {Boolean} force (optional) True to force a layout to occur, even if the item is hidden. + * @return {Ext.Container} this + */ + doLayout: function(shallow, force){ + var rendered = this.rendered, + forceLayout = this.forceLayout; + + if(!this.isVisible() || this.collapsed){ + this.deferLayout = this.deferLayout || !shallow; + if(!(force || forceLayout)){ + return; + } + shallow = shallow && !this.deferLayout; + } else { + delete this.deferLayout; + } + 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(); + } + } + } + if(rendered){ + this.onLayout(shallow, force); + } + delete this.forceLayout; + }, + + //private + onLayout : Ext.emptyFn, + + onShow : function(){ + Ext.Container.superclass.onShow.call(this); + if(this.deferLayout !== undefined){ + this.doLayout(true); + } + }, + + /** + * Returns the layout currently in use by the container. If the container does not currently have a layout + * set, a default {@link Ext.layout.ContainerLayout} will be created and set as the container's layout. + * @return {ContainerLayout} layout The container's layout + */ + getLayout : function(){ + if(!this.layout){ + var layout = new Ext.layout.ContainerLayout(this.layoutConfig); + this.setLayout(layout); + } + return this.layout; + }, + + // private + beforeDestroy : function(){ + if(this.items){ + Ext.destroy.apply(Ext, this.items.items); + } + if(this.monitorResize){ + Ext.EventManager.removeResizeListener(this.doLayout, this); + } + Ext.destroy(this.layout); + Ext.Container.superclass.beforeDestroy.call(this); + }, + + /** + * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope (this) of + * function call will be the scope provided or the current component. The arguments to the function + * will be the args provided or the current component. If the function returns false at any point, + * the bubble is stopped. + * @param {Function} fn The function to call + * @param {Object} scope (optional) The scope of the function (defaults to current node) + * @param {Array} args (optional) The args to call the function with (default to passing the current component) + * @return {Ext.Container} this + */ + bubble : function(fn, scope, args){ + var p = this; + while(p){ + if(fn.apply(scope || p, args || [p]) === false){ + break; + } + p = p.ownerCt; + } + return this; + }, + + /** + * Cascades down the component/container heirarchy from this component (called first), calling the specified function with + * each component. The scope (this) of + * function call will be the scope provided or the current component. The arguments to the function + * will be the args provided or the current component. If the function returns false at any point, + * the cascade is stopped on that branch. + * @param {Function} fn The function to call + * @param {Object} scope (optional) The scope of the function (defaults to current component) + * @param {Array} args (optional) The args to call the function with (defaults to passing the current component) + * @return {Ext.Container} this + */ + cascade : function(fn, scope, args){ + if(fn.apply(scope || this, args || [this]) !== false){ + if(this.items){ + var cs = this.items.items; + for(var i = 0, len = cs.length; i < len; i++){ + if(cs[i].cascade){ + cs[i].cascade(fn, scope, args); + }else{ + fn.apply(scope || cs[i], args || [cs[i]]); + } + } + } + } + return this; + }, + + /** + * Find a component under this container at any level by id + * @param {String} id + * @return Ext.Component + */ + findById : function(id){ + var m, ct = this; + this.cascade(function(c){ + if(ct != c && c.id === id){ + m = c; + return false; + } + }); + return m || null; + }, + + /** + * Find a component under this container at any level by xtype or class + * @param {String/Class} xtype The xtype string for a component, or the class of the component directly + * @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is + * the default), or true to check whether this Component is directly of the specified xtype. + * @return {Array} Array of Ext.Components + */ + findByType : function(xtype, shallow){ + return this.findBy(function(c){ + return c.isXType(xtype, shallow); + }); + }, + + /** + * Find a component under this container at any level by property + * @param {String} prop + * @param {String} value + * @return {Array} Array of Ext.Components + */ + find : function(prop, value){ + return this.findBy(function(c){ + return c[prop] === value; + }); + }, + + /** + * Find a component under this container at any level by a custom function. If the passed function returns + * true, the component will be included in the results. The passed function is called with the arguments (component, this container). + * @param {Function} fn The function to call + * @param {Object} scope (optional) + * @return {Array} Array of Ext.Components + */ + findBy : function(fn, scope){ + var m = [], ct = this; + this.cascade(function(c){ + if(ct != c && fn.call(scope || c, c, ct) === true){ + m.push(c); + } + }); + return m; + }, + + /** + * Get a component contained by this container (alias for items.get(key)) + * @param {String/Number} key The index or id of the component + * @return {Ext.Component} Ext.Component + */ + get : function(key){ + return this.items.get(key); + } +}); + +Ext.Container.LAYOUTS = {}; +Ext.reg('container', Ext.Container);