X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/c930e9176a5a85509c5b0230e2bff5c22a591432..b37ceabb82336ee82757cd32efe353cfab8ec267:/pkgs/cmp-foundation-debug.js diff --git a/pkgs/cmp-foundation-debug.js b/pkgs/cmp-foundation-debug.js index 94a91ae5..c5bccc7f 100644 --- a/pkgs/cmp-foundation-debug.js +++ b/pkgs/cmp-foundation-debug.js @@ -1,6 +1,6 @@ /*! - * Ext JS Library 3.0.0 - * Copyright(c) 2006-2009 Ext JS, LLC + * Ext JS Library 3.2.2 + * Copyright(c) 2006-2010 Ext JS, Inc. * licensing@extjs.com * http://www.extjs.com/license */ @@ -11,12 +11,12 @@ * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).

*

This object also provides a registry of available Component classes * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}. - * The {@link Ext.Component#xtype xtype} provides a way to avoid instantiating child Components + * The {@link Ext.Component#xtype xtype} provides a way to avoid instantiating child Components * when creating a full, nested config object for a complete Ext page.

*

A child Component may be specified simply as a config object - * as long as the correct {@link Ext.Component#xtype xtype} is specified so that if and when the Component + * as long as the correct {@link Ext.Component#xtype xtype} is specified so that if and when the Component * needs rendering, the correct type can be looked up for lazy instantiation.

- *

For a list of all available {@link Ext.Component#xtype xtypes}, see {@link Ext.Component}.

+ *

For a list of all available {@link Ext.Component#xtype xtypes}, see {@link Ext.Component}.

* @singleton */ Ext.ComponentMgr = function(){ @@ -45,7 +45,7 @@ Ext.ComponentMgr = function(){ * Returns a component by {@link Ext.Component#id id}. * For additional details see {@link Ext.util.MixedCollection#get}. * @param {String} id The component {@link Ext.Component#id id} - * @return Ext.Component The Component, undefined if not found, or null if a + * @return Ext.Component The Component, undefined if not found, or null if a * Class was found. */ get : function(id){ @@ -53,10 +53,10 @@ Ext.ComponentMgr = function(){ }, /** - * Registers a function that will be called when a specified component is added to ComponentMgr + * Registers a function that will be called when a Component with the specified id is added to ComponentMgr. This will happen on instantiation. * @param {String} id The component {@link Ext.Component#id id} * @param {Function} fn The callback function - * @param {Object} scope The scope of the callback + * @param {Object} scope The scope (this reference) in which the callback is executed. Defaults to the Component. */ onAvailable : function(id, fn, scope){ all.on("add", function(index, o){ @@ -74,6 +74,18 @@ Ext.ComponentMgr = function(){ */ all : all, + /** + * The xtypes that have been registered with the component manager. + * @type {Object} + */ + types : types, + + /** + * The ptypes that have been registered with the component manager. + * @type {Object} + */ + ptypes: ptypes, + /** * Checks if a Component type is registered. * @param {Ext.Component} xtype The mnemonic string by which the Component class may be looked up @@ -82,6 +94,15 @@ Ext.ComponentMgr = function(){ isRegistered : function(xtype){ return types[xtype] !== undefined; }, + + /** + * Checks if a Plugin type is registered. + * @param {Ext.Component} ptype The mnemonic string by which the Plugin class may be looked up + * @return {Boolean} Whether the type is registered. + */ + isPluginRegistered : function(ptype){ + return ptypes[ptype] !== undefined; + }, /** *

Registers a new Component constructor, keyed by a new @@ -103,7 +124,7 @@ Ext.ComponentMgr = function(){ * config object's {@link Ext.component#xtype xtype} to determine the class to instantiate. * @param {Object} config A configuration object for the Component you wish to create. * @param {Constructor} defaultType The constructor to provide the default Component type if - * the config object does not contain a xtype. (Optional if the config contains a xtype). + * the config object does not contain a xtype. (Optional if the config contains a xtype). * @return {Ext.Component} The newly instantiated Component. */ create : function(config, defaultType){ @@ -129,11 +150,16 @@ Ext.ComponentMgr = function(){ * config object's {@link Ext.component#ptype ptype} to determine the class to instantiate. * @param {Object} config A configuration object for the Plugin you wish to create. * @param {Constructor} defaultType The constructor to provide the default Plugin type if - * the config object does not contain a ptype. (Optional if the config contains a ptype). + * the config object does not contain a ptype. (Optional if the config contains a ptype). * @return {Ext.Component} The newly instantiated Plugin. */ createPlugin : function(config, defaultType){ - return new ptypes[config.ptype || defaultType](config); + var PluginCls = ptypes[config.ptype || defaultType]; + if (PluginCls.init) { + return PluginCls; + } else { + return new PluginCls(config); + } } }; }(); @@ -156,8 +182,18 @@ Ext.reg = Ext.ComponentMgr.registerType; // this will be called a lot internally * @method preg */ Ext.preg = Ext.ComponentMgr.registerPlugin; -Ext.create = Ext.ComponentMgr.create; /** + * Shorthand for {@link Ext.ComponentMgr#create} + * Creates a new Component from the specified config object using the + * config object's {@link Ext.component#xtype xtype} to determine the class to instantiate. + * @param {Object} config A configuration object for the Component you wish to create. + * @param {Constructor} defaultType The constructor to provide the default Component type if + * the config object does not contain a xtype. (Optional if the config contains a xtype). + * @return {Ext.Component} The newly instantiated Component. + * @member Ext + * @method create + */ +Ext.create = Ext.ComponentMgr.create;/** * @class Ext.Component * @extends Ext.util.Observable *

Base class for all Ext components. All subclasses of Component may participate in the automated @@ -226,7 +262,7 @@ menutextitem {@link Ext.menu.TextItem} Form components --------------------------------------- -form {@link Ext.FormPanel} +form {@link Ext.form.FormPanel} checkbox {@link Ext.form.Checkbox} checkboxgroup {@link Ext.form.CheckboxGroup} combo {@link Ext.form.ComboBox} @@ -295,6 +331,14 @@ Ext.Component = function(config){ Ext.apply(this, config); this.addEvents( + /** + * @event added + * Fires when a component is added to an Ext.Container + * @param {Ext.Component} this + * @param {Ext.Container} ownerCt Container which holds the component + * @param {number} index Position at which the component was added + */ + 'added', /** * @event disable * Fires after the component is disabled. @@ -334,6 +378,13 @@ Ext.Component = function(config){ * @param {Ext.Component} this */ 'hide', + /** + * @event removed + * Fires when a component is removed from an Ext.Container + * @param {Ext.Component} this + * @param {Ext.Container} ownerCt Container which holds the component + */ + 'removed', /** * @event beforerender * Fires before the component is {@link #rendered}. Return false from an @@ -429,7 +480,7 @@ Ext.Component = function(config){ } if(this.stateful !== false){ - this.initState(config); + this.initState(); } if(this.applyTo){ @@ -445,7 +496,7 @@ Ext.Component = function(config){ Ext.Component.AUTO_ID = 1000; Ext.extend(Ext.Component, Ext.util.Observable, { - // Configs below are used for all Components when rendered by FormLayout. + // Configs below are used for all Components when rendered by FormLayout. /** * @cfg {String} fieldLabel

The label text to display next to this Component (defaults to '').

*

Note: this config is only used when this Component is rendered by a Container which @@ -544,7 +595,11 @@ new Ext.FormPanel({ *

See {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl} also.

*/ /** - * @cfg {String} itemCls

An additional CSS class to apply to the div wrapping the form item + * @cfg {String} itemCls + *

Note: this config is only used when this Component is rendered by a Container which + * has been configured to use the {@link Ext.layout.FormLayout FormLayout} layout manager (e.g. + * {@link Ext.form.FormPanel} or specifying layout:'form').


+ *

An additional CSS class to apply to the div wrapping the form item * element of this field. If supplied, itemCls at the field level will override * the default itemCls supplied at the container level. The value specified for * itemCls will be added to the default class ('x-form-item').

@@ -554,35 +609,24 @@ new Ext.FormPanel({ * any other element within the markup for the field.

*

Note: see the note for {@link #fieldLabel}.


* Example use:

-// Apply a style to the field's label:
+// Apply a style to the field's label:
 <style>
     .required .x-form-item-label {font-weight:bold;color:red;}
 </style>
 
 new Ext.FormPanel({
-	height: 100,
-	renderTo: Ext.getBody(),
-	items: [{
-		xtype: 'textfield',
-		fieldLabel: 'Name',
-		itemCls: 'required' //this label will be styled
-	},{
-		xtype: 'textfield',
-		fieldLabel: 'Favorite Color'
-	}]
+    height: 100,
+    renderTo: Ext.getBody(),
+    items: [{
+        xtype: 'textfield',
+        fieldLabel: 'Name',
+        itemCls: 'required' //this label will be styled
+    },{
+        xtype: 'textfield',
+        fieldLabel: 'Favorite Color'
+    }]
 });
 
- */ - - // Configs below are used for all Components when rendered by AnchorLayout. - /** - * @cfg {String} anchor

Note: this config is only used when this Component is rendered - * by a Container which has been configured to use an {@link Ext.layout.AnchorLayout AnchorLayout} - * based layout manager, for example:

- *

See {@link Ext.layout.AnchorLayout}.{@link Ext.layout.AnchorLayout#anchor anchor} also.

*/ /** @@ -913,29 +957,84 @@ new Ext.Panel({ * @property el */ /** - * The component's owner {@link Ext.Container} (defaults to undefined, and is set automatically when - * the component is added to a container). Read-only. - *

Note: to access items within the container see {@link #itemId}.

+ * This Component's owner {@link Ext.Container Container} (defaults to undefined, and is set automatically when + * this Component is added to a Container). Read-only. + *

Note: to access items within the Container see {@link #itemId}.

* @type Ext.Container * @property ownerCt */ /** * True if this component is hidden. Read-only. * @type Boolean - * @property + * @property hidden */ /** * True if this component is disabled. Read-only. * @type Boolean - * @property + * @property disabled */ /** * True if this component has been rendered. Read-only. * @type Boolean - * @property + * @property rendered */ rendered : false, + /** + * @cfg {String} contentEl + *

Optional. Specify an existing HTML element, or the id of an existing HTML element to use as the content + * for this component.

+ * + */ + /** + * @cfg {String/Object} html + * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the layout element + * content (defaults to ''). The HTML content is added after the component is rendered, + * so the document will not contain this HTML at the time the {@link #render} event is fired. + * This content is inserted into the body before any configured {@link #contentEl} is appended. + */ + + /** + * @cfg {Mixed} tpl + * An {@link Ext.Template}, {@link Ext.XTemplate} + * or an array of strings to form an Ext.XTemplate. + * Used in conjunction with the {@link #data} and + * {@link #tplWriteMode} configurations. + */ + + /** + * @cfg {String} tplWriteMode The Ext.(X)Template method to use when + * updating the content area of the Component. Defaults to 'overwrite' + * (see {@link Ext.XTemplate#overwrite}). + */ + tplWriteMode : 'overwrite', + + /** + * @cfg {Mixed} data + * The initial set of data to apply to the {@link #tpl} to + * update the content area of the Component. + */ + + /** + * @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 []. + */ + bubbleEvents: [], + + // private ctype : 'Ext.Component', @@ -988,7 +1087,22 @@ Ext.Foo = Ext.extend(Ext.Bar, { } */ - initComponent : Ext.emptyFn, + initComponent : function(){ + /* + * this is double processing, however it allows people to be able to do + * Ext.apply(this, { + * listeners: { + * //here + * } + * }); + * MyClass.superclass.initComponent.call(this); + */ + if(this.listeners){ + this.on(this.listeners); + delete this.listeners; + } + this.enableBubble(this.bubbleEvents); + }, /** *

Render this Component into the passed HTML element.

@@ -1051,7 +1165,32 @@ Ext.Foo = Ext.extend(Ext.Bar, { this.el.addClassOnOver(this.overCls); } this.fireEvent('render', this); + + + // Populate content of the component with html, contentEl or + // a tpl. + var contentTarget = this.getContentTarget(); + if (this.html){ + contentTarget.update(Ext.DomHelper.markup(this.html)); + delete this.html; + } + if (this.contentEl){ + var ce = Ext.getDom(this.contentEl); + Ext.fly(ce).removeClass(['x-hidden', 'x-hide-display']); + contentTarget.appendChild(ce); + } + if (this.tpl) { + if (!this.tpl.compile) { + this.tpl = new Ext.XTemplate(this.tpl); + } + if (this.data) { + this.tpl[this.tplWriteMode](contentTarget, this.data); + delete this.data; + } + } this.afterRender(this.container); + + if(this.hidden){ // call this so we don't fire initial hide events. this.doHide(); @@ -1064,17 +1203,70 @@ Ext.Foo = Ext.extend(Ext.Bar, { if(this.stateful !== false){ this.initStateEvents(); } - this.initRef(); this.fireEvent('afterrender', this); } return this; }, - initRef : function(){ + + /** + * Update the content area of a component. + * @param {Mixed} htmlOrData + * If this component has been configured with a template via the tpl config + * then it will use this argument as data to populate the template. + * If this component was not configured with a template, the components + * content area will be updated via Ext.Element update + * @param {Boolean} loadScripts + * (optional) Only legitimate when using the html configuration. Defaults to false + * @param {Function} callback + * (optional) Only legitimate when using the html configuration. Callback to execute when scripts have finished loading + */ + update: function(htmlOrData, loadScripts, cb) { + var contentTarget = this.getContentTarget(); + if (this.tpl && typeof htmlOrData !== "string") { + this.tpl[this.tplWriteMode](contentTarget, htmlOrData || {}); + } else { + var html = Ext.isObject(htmlOrData) ? Ext.DomHelper.markup(htmlOrData) : htmlOrData; + contentTarget.update(html, loadScripts, cb); + } + }, + + + /** + * @private + * Method to manage awareness of when components are added to their + * respective Container, firing an added event. + * References are established at add time rather than at render time. + * @param {Ext.Container} container Container which holds the component + * @param {number} pos Position at which the component was added + */ + onAdded : function(container, pos) { + this.ownerCt = container; + this.initRef(); + this.fireEvent('added', this, container, pos); + }, + + /** + * @private + * Method to manage awareness of when components are removed from their + * respective Container, firing an removed event. References are properly + * cleaned up after removing a component from its owning container. + */ + onRemoved : function() { + this.removeRef(); + this.fireEvent('removed', this, this.ownerCt); + delete this.ownerCt; + }, + + /** + * @private + * Method to establish a reference to a component. + */ + initRef : function() { /** * @cfg {String} ref - *

A path specification, relative to the Component's {@link #ownerCt} specifying into which - * ancestor Container to place a named reference to this Component.

+ *

A path specification, relative to the Component's {@link #ownerCt} + * specifying into which ancestor Container to place a named reference to this Component.

*

The ancestor axis can be traversed by using '/' characters in the path. * For example, to put a reference to a Toolbar Button into the Panel which owns the Toolbar:


 var myGrid = new Ext.grid.EditorGridPanel({
@@ -1095,33 +1287,50 @@ var myGrid = new Ext.grid.EditorGridPanel({
     }
 });
 
- *

In the code above, if the ref had been 'saveButton' the reference would - * have been placed into the Toolbar. Each '/' in the ref moves up one level from the - * Component's {@link #ownerCt}.

+ *

In the code above, if the ref had been 'saveButton' + * the reference would have been placed into the Toolbar. Each '/' in the ref + * moves up one level from the Component's {@link #ownerCt}.

+ *

Also see the {@link #added} and {@link #removed} events.

*/ - if(this.ref){ - var levels = this.ref.split('/'); - var last = levels.length, i = 0; - var t = this; - while(i < last){ - if(t.ownerCt){ - t = t.ownerCt; - } - i++; + if(this.ref && !this.refOwner){ + var levels = this.ref.split('/'), + last = levels.length, + i = 0, + t = this; + + while(t && i < last){ + t = t.ownerCt; + ++i; + } + if(t){ + t[this.refName = levels[--i]] = this; + /** + * @type Ext.Container + * @property refOwner + * The ancestor Container into which the {@link #ref} reference was inserted if this Component + * is a child of a Container, and has been configured with a ref. + */ + this.refOwner = t; } - t[levels[--i]] = this; + } + }, + + removeRef : function() { + if (this.refOwner && this.refName) { + delete this.refOwner[this.refName]; + delete this.refOwner; } }, // private - initState : function(config){ + initState : function(){ if(Ext.state.Manager){ var id = this.getStateId(); if(id){ var state = Ext.state.Manager.get(id); if(state){ if(this.fireEvent('beforestaterestore', this, state) !== false){ - this.applyState(state); + this.applyState(Ext.apply({}, state)); this.fireEvent('staterestore', this, state); } } @@ -1131,7 +1340,7 @@ var myGrid = new Ext.grid.EditorGridPanel({ // private getStateId : function(){ - return this.stateId || ((this.id.indexOf('ext-comp-') == 0 || this.id.indexOf('ext-gen') == 0) ? null : this.id); + return this.stateId || ((/^(ext-comp-|ext-gen)/).test(String(this.id)) ? null : this.id); }, // private @@ -1144,7 +1353,7 @@ var myGrid = new Ext.grid.EditorGridPanel({ }, // private - applyState : function(state, config){ + applyState : function(state){ if(state){ Ext.apply(this, state); } @@ -1226,6 +1435,10 @@ var myGrid = new Ext.grid.EditorGridPanel({ this.el = Ext.get(this.el); if(this.allowDomMove !== false){ ct.dom.insertBefore(this.el.dom, position); + if (div) { + Ext.removeNode(div); + div = null; + } } } }, @@ -1251,19 +1464,37 @@ var myGrid = new Ext.grid.EditorGridPanel({ * */ destroy : function(){ - if(this.fireEvent('beforedestroy', this) !== false){ - this.beforeDestroy(); - if(this.rendered){ - this.el.removeAllListeners(); - this.el.remove(); - if(this.actionMode == 'container' || this.removeMode == 'container'){ - this.container.remove(); + if(!this.isDestroyed){ + if(this.fireEvent('beforedestroy', this) !== false){ + this.destroying = true; + this.beforeDestroy(); + if(this.ownerCt && this.ownerCt.remove){ + this.ownerCt.remove(this, false); + } + if(this.rendered){ + this.el.remove(); + if(this.actionMode == 'container' || this.removeMode == 'container'){ + this.container.remove(); + } + } + // Stop any buffered tasks + if(this.focusTask && this.focusTask.cancel){ + this.focusTask.cancel(); } + this.onDestroy(); + Ext.ComponentMgr.unregister(this); + this.fireEvent('destroy', this); + this.purgeListeners(); + this.destroying = false; + this.isDestroyed = true; } - this.onDestroy(); - Ext.ComponentMgr.unregister(this); - this.fireEvent('destroy', this); - this.purgeListeners(); + } + }, + + deleteMembers : function(){ + var args = arguments; + for(var i = 0, len = args.length; i < len; ++i){ + delete this[args[i]]; } }, @@ -1298,6 +1529,11 @@ new Ext.Panel({ return this.el; }, + // private + getContentTarget : function(){ + return this.el; + }, + /** * Returns the id of this component or automatically generates and * returns an id if an id is not defined yet:

@@ -1327,10 +1563,11 @@ new Ext.Panel({
      */
     focus : function(selectText, delay){
         if(delay){
-            this.focus.defer(Ext.isNumber(delay) ? delay : 10, this, [selectText, false]);
+            this.focusTask = new Ext.util.DelayedTask(this.focus, this, [selectText, false]);
+            this.focusTask.delay(Ext.isNumber(delay) ? delay : 10);
             return;
         }
-        if(this.rendered){
+        if(this.rendered && !this.isDestroyed){
             this.el.focus();
             if(selectText === true){
                 this.el.dom.select();
@@ -1418,7 +1655,7 @@ new Ext.Panel({
 
     // private
     onShow : function(){
-        this.getVisibiltyEl().removeClass('x-hide-' + this.hideMode);
+        this.getVisibilityEl().removeClass('x-hide-' + this.hideMode);
     },
 
     /**
@@ -1446,11 +1683,11 @@ new Ext.Panel({
 
     // private
     onHide : function(){
-        this.getVisibiltyEl().addClass('x-hide-' + this.hideMode);
+        this.getVisibilityEl().addClass('x-hide-' + this.hideMode);
     },
 
     // private
-    getVisibiltyEl : function(){
+    getVisibilityEl : function(){
         return this.hideParent ? this.container : this.getActionEl();
     },
 
@@ -1468,7 +1705,7 @@ new Ext.Panel({
      * @return {Boolean} True if this component is visible, false otherwise.
      */
     isVisible : function(){
-        return this.rendered && this.getVisibiltyEl().isVisible();
+        return this.rendered && this.getVisibilityEl().isVisible();
     },
 
     /**
@@ -1579,8 +1816,9 @@ alert(t.getXTypes());  // alerts 'component/box/field/textfield'
             });
     },
 
-    getDomPositionEl : function(){
-        return this.getPositionEl ? this.getPositionEl() : this.getEl();
+    // protected
+    getPositionEl : function(){
+        return this.positionEl || this.el;
     },
 
     // private
@@ -1599,15 +1837,38 @@ alert(t.getXTypes());  // alerts 'component/box/field/textfield'
         this.mons = [];
     },
 
-    // internal function for auto removal of assigned event handlers on destruction
-    mon : function(item, ename, fn, scope, opt){
+    // private
+    createMons: function(){
         if(!this.mons){
             this.mons = [];
             this.on('beforedestroy', this.clearMons, this, {single: true});
         }
+    },
 
+    /**
+     * 

Adds listeners to any Observable object (or Elements) which are automatically removed when this Component + * is destroyed. Usage:

+myGridPanel.mon(myGridPanel.getSelectionModel(), 'selectionchange', handleSelectionChange, null, {buffer: 50});
+
+ *

or:

+myGridPanel.mon(myGridPanel.getSelectionModel(), {
+    selectionchange: handleSelectionChange,
+    buffer: 50
+});
+
+ * @param {Observable|Element} item The item to which to add a listener/listeners. + * @param {Object|String} ename The event name, or an object containing event name properties. + * @param {Function} fn Optional. If the ename parameter was an event name, this + * is the handler function. + * @param {Object} scope Optional. If the ename parameter was an event name, this + * is the scope (this reference) in which the handler function is executed. + * @param {Object} opt Optional. If the ename parameter was an event name, this + * is the {@link Ext.util.Observable#addListener addListener} options. + */ + mon : function(item, ename, fn, scope, opt){ + this.createMons(); if(Ext.isObject(ename)){ - var propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/; + var propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/; var o = ename; for(var e in o){ @@ -1616,31 +1877,39 @@ alert(t.getXTypes()); // alerts 'component/box/field/textfield' } if(Ext.isFunction(o[e])){ // shared options - this.mons.push({ - item: item, ename: e, fn: o[e], scope: o.scope - }); - item.on(e, o[e], o.scope, o); + this.mons.push({ + item: item, ename: e, fn: o[e], scope: o.scope + }); + item.on(e, o[e], o.scope, o); }else{ // individual options - this.mons.push({ - item: item, ename: e, fn: o[e], scope: o.scope - }); - item.on(e, o[e]); + this.mons.push({ + item: item, ename: e, fn: o[e], scope: o.scope + }); + item.on(e, o[e]); } } return; } - this.mons.push({ item: item, ename: ename, fn: fn, scope: scope }); item.on(ename, fn, scope, opt); }, - // protected, opposite of mon + /** + * Removes listeners that were added by the {@link #mon} method. + * @param {Observable|Element} item The item from which to remove a listener/listeners. + * @param {Object|String} ename The event name, or an object containing event name properties. + * @param {Function} fn Optional. If the ename parameter was an event name, this + * is the handler function. + * @param {Object} scope Optional. If the ename parameter was an event name, this + * is the scope (this reference) in which the handler function is executed. + */ mun : function(item, ename, fn, scope){ var found, mon; + this.createMons(); for(var i = 0, len = this.mons.length; i < len; ++i){ mon = this.mons[i]; if(item === mon.item && ename == mon.ename && fn === mon.fn && scope === mon.scope){ @@ -1690,253 +1959,253 @@ alert(t.getXTypes()); // alerts 'component/box/field/textfield' } }); -Ext.reg('component', Ext.Component); -/** - * @class Ext.Action - *

An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it - * can be usefully shared among multiple components. Actions let you share handlers, configuration options and UI - * updates across any components that support the Action interface (primarily {@link Ext.Toolbar}, {@link Ext.Button} - * and {@link Ext.menu.Menu} components).

- *

Aside from supporting the config object interface, any component that needs to use Actions must also support - * the following method list, as these will be called as needed by the Action class: setText(string), setIconCls(string), - * setDisabled(boolean), setVisible(boolean) and setHandler(function).

- * Example usage:
- *

-// Define the shared action.  Each component below will have the same
-// display text and icon, and will display the same message on click.
-var action = new Ext.Action({
-    {@link #text}: 'Do something',
-    {@link #handler}: function(){
-        Ext.Msg.alert('Click', 'You did something.');
-    },
-    {@link #iconCls}: 'do-something',
-    {@link #itemId}: 'myAction'
-});
-
-var panel = new Ext.Panel({
-    title: 'Actions',
-    width: 500,
-    height: 300,
-    tbar: [
-        // Add the action directly to a toolbar as a menu button
-        action,
-        {
-            text: 'Action Menu',
-            // Add the action to a menu as a text item
-            menu: [action]
-        }
-    ],
-    items: [
-        // Add the action to the panel body as a standard button
-        new Ext.Button(action)
-    ],
-    renderTo: Ext.getBody()
-});
-
-// Change the text for all components using the action
-action.setText('Something else');
-
-// Reference an action through a container using the itemId
-var btn = panel.getComponent('myAction');
-var aRef = btn.baseAction;
-aRef.setText('New text');
-
- * @constructor - * @param {Object} config The configuration options - */ -Ext.Action = function(config){ - this.initialConfig = config; - this.itemId = config.itemId = (config.itemId || config.id || Ext.id()); - this.items = []; -} - -Ext.Action.prototype = { - /** - * @cfg {String} text The text to set for all components using this action (defaults to ''). - */ - /** - * @cfg {String} iconCls - * The CSS class selector that specifies a background image to be used as the header icon for - * all components using this action (defaults to ''). - *

An example of specifying a custom icon class would be something like: - *


-// specify the property in the config for the class:
-     ...
-     iconCls: 'do-something'
-
-// css class that specifies background image to be used as the icon image:
-.do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
-
- */ - /** - * @cfg {Boolean} disabled True to disable all components using this action, false to enable them (defaults to false). - */ - /** - * @cfg {Boolean} hidden True to hide all components using this action, false to show them (defaults to false). - */ - /** - * @cfg {Function} handler The function that will be invoked by each component tied to this action - * when the component's primary event is triggered (defaults to undefined). - */ - /** - * @cfg {String} itemId - * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}. - */ - /** - * @cfg {Object} scope The scope in which the {@link #handler} function will execute. - */ - - // private - isAction : true, - - /** - * Sets the text to be displayed by all components using this action. - * @param {String} text The text to display - */ - setText : function(text){ - this.initialConfig.text = text; - this.callEach('setText', [text]); - }, - - /** - * Gets the text currently displayed by all components using this action. - */ - getText : function(){ - return this.initialConfig.text; - }, - - /** - * Sets the icon CSS class for all components using this action. The class should supply - * a background image that will be used as the icon image. - * @param {String} cls The CSS class supplying the icon image - */ - setIconClass : function(cls){ - this.initialConfig.iconCls = cls; - this.callEach('setIconClass', [cls]); - }, - - /** - * Gets the icon CSS class currently used by all components using this action. - */ - getIconClass : function(){ - return this.initialConfig.iconCls; - }, - - /** - * Sets the disabled state of all components using this action. Shortcut method - * for {@link #enable} and {@link #disable}. - * @param {Boolean} disabled True to disable the component, false to enable it - */ - setDisabled : function(v){ - this.initialConfig.disabled = v; - this.callEach('setDisabled', [v]); - }, - - /** - * Enables all components using this action. - */ - enable : function(){ - this.setDisabled(false); - }, - - /** - * Disables all components using this action. - */ - disable : function(){ - this.setDisabled(true); - }, - - /** - * Returns true if the components using this action are currently disabled, else returns false. - */ - isDisabled : function(){ - return this.initialConfig.disabled; - }, - - /** - * Sets the hidden state of all components using this action. Shortcut method - * for {@link #hide} and {@link #show}. - * @param {Boolean} hidden True to hide the component, false to show it - */ - setHidden : function(v){ - this.initialConfig.hidden = v; - this.callEach('setVisible', [!v]); - }, - - /** - * Shows all components using this action. - */ - show : function(){ - this.setHidden(false); - }, - - /** - * Hides all components using this action. - */ - hide : function(){ - this.setHidden(true); - }, - - /** - * Returns true if the components using this action are currently hidden, else returns false. - */ - isHidden : function(){ - return this.initialConfig.hidden; - }, - - /** - * Sets the function that will be called by each component using this action when its primary event is triggered. - * @param {Function} fn The function that will be invoked by the action's components. The function - * will be called with no arguments. - * @param {Object} scope The scope in which the function will execute - */ - setHandler : function(fn, scope){ - this.initialConfig.handler = fn; - this.initialConfig.scope = scope; - this.callEach('setHandler', [fn, scope]); - }, - - /** - * Executes the specified function once for each component currently tied to this action. The function passed - * in should accept a single argument that will be an object that supports the basic Action config/method interface. - * @param {Function} fn The function to execute for each component - * @param {Object} scope The scope in which the function will execute - */ - each : function(fn, scope){ - Ext.each(this.items, fn, scope); - }, - - // private - callEach : function(fnName, args){ - var cs = this.items; - for(var i = 0, len = cs.length; i < len; i++){ - cs[i][fnName].apply(cs[i], args); - } - }, - - // private - addComponent : function(comp){ - this.items.push(comp); - comp.on('destroy', this.removeComponent, this); - }, - - // private - removeComponent : function(comp){ - this.items.remove(comp); - }, - - /** - * Executes this action manually using the handler function specified in the original config object - * or the handler function set with {@link #setHandler}. Any arguments passed to this - * function will be passed on to the handler function. - * @param {Mixed} arg1 (optional) Variable number of arguments passed to the handler function - * @param {Mixed} arg2 (optional) - * @param {Mixed} etc... (optional) - */ - execute : function(){ - this.initialConfig.handler.apply(this.initialConfig.scope || window, arguments); - } -}; +Ext.reg('component', Ext.Component);/** + * @class Ext.Action + *

An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it + * can be usefully shared among multiple components. Actions let you share handlers, configuration options and UI + * updates across any components that support the Action interface (primarily {@link Ext.Toolbar}, {@link Ext.Button} + * and {@link Ext.menu.Menu} components).

+ *

Aside from supporting the config object interface, any component that needs to use Actions must also support + * the following method list, as these will be called as needed by the Action class: setText(string), setIconCls(string), + * setDisabled(boolean), setVisible(boolean) and setHandler(function).

+ * Example usage:
+ *

+// Define the shared action.  Each component below will have the same
+// display text and icon, and will display the same message on click.
+var action = new Ext.Action({
+    {@link #text}: 'Do something',
+    {@link #handler}: function(){
+        Ext.Msg.alert('Click', 'You did something.');
+    },
+    {@link #iconCls}: 'do-something',
+    {@link #itemId}: 'myAction'
+});
+
+var panel = new Ext.Panel({
+    title: 'Actions',
+    width: 500,
+    height: 300,
+    tbar: [
+        // Add the action directly to a toolbar as a menu button
+        action,
+        {
+            text: 'Action Menu',
+            // Add the action to a menu as a text item
+            menu: [action]
+        }
+    ],
+    items: [
+        // Add the action to the panel body as a standard button
+        new Ext.Button(action)
+    ],
+    renderTo: Ext.getBody()
+});
+
+// Change the text for all components using the action
+action.setText('Something else');
+
+// Reference an action through a container using the itemId
+var btn = panel.getComponent('myAction');
+var aRef = btn.baseAction;
+aRef.setText('New text');
+
+ * @constructor + * @param {Object} config The configuration options + */ +Ext.Action = Ext.extend(Object, { + /** + * @cfg {String} text The text to set for all components using this action (defaults to ''). + */ + /** + * @cfg {String} iconCls + * The CSS class selector that specifies a background image to be used as the header icon for + * all components using this action (defaults to ''). + *

An example of specifying a custom icon class would be something like: + *


+// specify the property in the config for the class:
+     ...
+     iconCls: 'do-something'
+
+// css class that specifies background image to be used as the icon image:
+.do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
+
+ */ + /** + * @cfg {Boolean} disabled True to disable all components using this action, false to enable them (defaults to false). + */ + /** + * @cfg {Boolean} hidden True to hide all components using this action, false to show them (defaults to false). + */ + /** + * @cfg {Function} handler The function that will be invoked by each component tied to this action + * when the component's primary event is triggered (defaults to undefined). + */ + /** + * @cfg {String} itemId + * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}. + */ + /** + * @cfg {Object} scope The scope (this reference) in which the + * {@link #handler} is executed. Defaults to this Button. + */ + + constructor : function(config){ + this.initialConfig = config; + this.itemId = config.itemId = (config.itemId || config.id || Ext.id()); + this.items = []; + }, + + // private + isAction : true, + + /** + * Sets the text to be displayed by all components using this action. + * @param {String} text The text to display + */ + setText : function(text){ + this.initialConfig.text = text; + this.callEach('setText', [text]); + }, + + /** + * Gets the text currently displayed by all components using this action. + */ + getText : function(){ + return this.initialConfig.text; + }, + + /** + * Sets the icon CSS class for all components using this action. The class should supply + * a background image that will be used as the icon image. + * @param {String} cls The CSS class supplying the icon image + */ + setIconClass : function(cls){ + this.initialConfig.iconCls = cls; + this.callEach('setIconClass', [cls]); + }, + + /** + * Gets the icon CSS class currently used by all components using this action. + */ + getIconClass : function(){ + return this.initialConfig.iconCls; + }, + + /** + * Sets the disabled state of all components using this action. Shortcut method + * for {@link #enable} and {@link #disable}. + * @param {Boolean} disabled True to disable the component, false to enable it + */ + setDisabled : function(v){ + this.initialConfig.disabled = v; + this.callEach('setDisabled', [v]); + }, + + /** + * Enables all components using this action. + */ + enable : function(){ + this.setDisabled(false); + }, + + /** + * Disables all components using this action. + */ + disable : function(){ + this.setDisabled(true); + }, + + /** + * Returns true if the components using this action are currently disabled, else returns false. + */ + isDisabled : function(){ + return this.initialConfig.disabled; + }, + + /** + * Sets the hidden state of all components using this action. Shortcut method + * for {@link #hide} and {@link #show}. + * @param {Boolean} hidden True to hide the component, false to show it + */ + setHidden : function(v){ + this.initialConfig.hidden = v; + this.callEach('setVisible', [!v]); + }, + + /** + * Shows all components using this action. + */ + show : function(){ + this.setHidden(false); + }, + + /** + * Hides all components using this action. + */ + hide : function(){ + this.setHidden(true); + }, + + /** + * Returns true if the components using this action are currently hidden, else returns false. + */ + isHidden : function(){ + return this.initialConfig.hidden; + }, + + /** + * Sets the function that will be called by each Component using this action when its primary event is triggered. + * @param {Function} fn The function that will be invoked by the action's components. The function + * will be called with no arguments. + * @param {Object} scope The scope (this reference) in which the function is executed. Defaults to the Component firing the event. + */ + setHandler : function(fn, scope){ + this.initialConfig.handler = fn; + this.initialConfig.scope = scope; + this.callEach('setHandler', [fn, scope]); + }, + + /** + * Executes the specified function once for each Component currently tied to this action. The function passed + * in should accept a single argument that will be an object that supports the basic Action config/method interface. + * @param {Function} fn The function to execute for each component + * @param {Object} scope The scope (this reference) in which the function is executed. Defaults to the Component. + */ + each : function(fn, scope){ + Ext.each(this.items, fn, scope); + }, + + // private + callEach : function(fnName, args){ + var cs = this.items; + for(var i = 0, len = cs.length; i < len; i++){ + cs[i][fnName].apply(cs[i], args); + } + }, + + // private + addComponent : function(comp){ + this.items.push(comp); + comp.on('destroy', this.removeComponent, this); + }, + + // private + removeComponent : function(comp){ + this.items.remove(comp); + }, + + /** + * Executes this action manually using the handler function specified in the original config object + * or the handler function set with {@link #setHandler}. Any arguments passed to this + * function will be passed on to the handler function. + * @param {Mixed} arg1 (optional) Variable number of arguments passed to the handler function + * @param {Mixed} arg2 (optional) + * @param {Mixed} etc... (optional) + */ + execute : function(){ + this.initialConfig.handler.apply(this.initialConfig.scope || window, arguments); + } +}); /** * @class Ext.Layer * @extends Ext.Element @@ -2061,41 +2330,39 @@ Ext.extend(Ext.Layer, Ext.Element, { // this code can execute repeatedly in milliseconds (i.e. during a drag) so // code size was sacrificed for effeciency (e.g. no getBox/setBox, no XY calls) sync : function(doShow){ - var sw = this.shadow; - if(!this.updating && this.isVisible() && (sw || this.useShim)){ - var sh = this.getShim(); - - var w = this.getWidth(), - h = this.getHeight(); - - var l = this.getLeft(true), + var shadow = this.shadow; + if(!this.updating && this.isVisible() && (shadow || this.useShim)){ + var shim = this.getShim(), + w = this.getWidth(), + h = this.getHeight(), + l = this.getLeft(true), t = this.getTop(true); - if(sw && !this.shadowDisabled){ - if(doShow && !sw.isVisible()){ - sw.show(this); + if(shadow && !this.shadowDisabled){ + if(doShow && !shadow.isVisible()){ + shadow.show(this); }else{ - sw.realign(l, t, w, h); + shadow.realign(l, t, w, h); } - if(sh){ + if(shim){ if(doShow){ - sh.show(); + shim.show(); } // fit the shim behind the shadow, so it is shimmed too - var a = sw.adjusts, s = sh.dom.style; - s.left = (Math.min(l, l+a.l))+'px'; - s.top = (Math.min(t, t+a.t))+'px'; - s.width = (w+a.w)+'px'; - s.height = (h+a.h)+'px'; + var shadowAdj = shadow.el.getXY(), shimStyle = shim.dom.style, + shadowSize = shadow.el.getSize(); + shimStyle.left = (shadowAdj[0])+'px'; + shimStyle.top = (shadowAdj[1])+'px'; + shimStyle.width = (shadowSize.width)+'px'; + shimStyle.height = (shadowSize.height)+'px'; } - }else if(sh){ + }else if(shim){ if(doShow){ - sh.show(); + shim.show(); } - sh.setSize(w, h); - sh.setLeftTop(l, t); + shim.setSize(w, h); + shim.setLeftTop(l, t); } - } }, @@ -2107,7 +2374,7 @@ Ext.extend(Ext.Layer, Ext.Element, { } this.removeAllListeners(); Ext.removeNode(this.dom); - Ext.Element.uncache(this.id); + delete this.dom; }, remove : function(){ @@ -2179,6 +2446,10 @@ Ext.extend(Ext.Layer, Ext.Element, { } return this; }, + + getConstrainOffset : function(){ + return this.shadowOffset; + }, isVisible : function(){ return this.visible; @@ -2396,7 +2667,8 @@ Ext.extend(Ext.Layer, Ext.Element, { return this; } }); -})();/** +})(); +/** * @class Ext.Shadow * Simple class that can provide a shadow effect for any element. Note that the element MUST be absolutely positioned, * and the shadow does not provide any shimming. This should be used only in simple cases -- for more advanced @@ -2611,10 +2883,38 @@ var myImage = new Ext.BoxComponent({ */ Ext.BoxComponent = Ext.extend(Ext.Component, { - // Configs below are used for all Components when rendered by BorderLayout. + // Configs below are used for all Components when rendered by BoxLayout. /** - * @cfg {String} region

Note: this config is only used when this BoxComponent is rendered - * by a Container which has been configured to use the {@link Ext.layout.BorderLayout BorderLayout} + * @cfg {Number} flex + *

Note: this config is only used when this Component is rendered + * by a Container which has been configured to use a {@link Ext.layout.BoxLayout BoxLayout}. + * Each child Component with a flex property will be flexed either vertically (by a VBoxLayout) + * or horizontally (by an HBoxLayout) according to the item's relative flex value + * compared to the sum of all Components with flex value specified. Any child items that have + * either a flex = 0 or flex = undefined will not be 'flexed' (the initial size will not be changed). + */ + // Configs below are used for all Components when rendered by AnchorLayout. + /** + * @cfg {String} anchor

Note: this config is only used when this Component is rendered + * by a Container which has been configured to use an {@link Ext.layout.AnchorLayout AnchorLayout} (or subclass thereof). + * based layout manager, for example:

    + *
  • {@link Ext.form.FormPanel}
  • + *
  • specifying layout: 'anchor' // or 'form', or 'absolute'
  • + *

+ *

See {@link Ext.layout.AnchorLayout}.{@link Ext.layout.AnchorLayout#anchor anchor} also.

+ */ + // tabTip config is used when a BoxComponent is a child of a TabPanel + /** + * @cfg {String} tabTip + *

Note: this config is only used when this BoxComponent is a child item of a TabPanel.

+ * A string to be used as innerHTML (html tags are accepted) to show in a tooltip when mousing over + * the associated tab selector element. {@link Ext.QuickTips}.init() + * must be called in order for the tips to render. + */ + // Configs below are used for all Components when rendered by BorderLayout. + /** + * @cfg {String} region

Note: this config is only used when this BoxComponent is rendered + * by a Container which has been configured to use the {@link Ext.layout.BorderLayout BorderLayout} * layout manager (e.g. specifying layout:'border').


*

See {@link Ext.layout.BorderLayout} also.

*/ @@ -2671,6 +2971,26 @@ Ext.BoxComponent = Ext.extend(Ext.Component, { * The width of this component in pixels (defaults to auto). * Note to express this dimension as a percentage or offset see {@link Ext.Component#anchor}. */ + /** + * @cfg {Number} boxMinHeight + *

The minimum value in pixels which this BoxComponent will set its height to.

+ *

Warning: This will override any size management applied by layout managers.

+ */ + /** + * @cfg {Number} boxMinWidth + *

The minimum value in pixels which this BoxComponent will set its width to.

+ *

Warning: This will override any size management applied by layout managers.

+ */ + /** + * @cfg {Number} boxMaxHeight + *

The maximum value in pixels which this BoxComponent will set its height to.

+ *

Warning: This will override any size management applied by layout managers.

+ */ + /** + * @cfg {Number} boxMaxWidth + *

The maximum value in pixels which this BoxComponent will set its width to.

+ *

Warning: This will override any size management applied by layout managers.

+ */ /** * @cfg {Boolean} autoHeight *

True to use height:'auto', false to use fixed height (or allow it to be managed by its parent @@ -2745,6 +3065,11 @@ var myPanel = new Ext.Panel({ });

*/ + /** + * @cfg {Boolean} autoScroll + * true to use overflow:'auto' on the components layout element and show scroll bars automatically when + * necessary, false to clip any overflowing content (defaults to false). + */ /* // private internal config * {Boolean} deferHeight @@ -2800,14 +3125,27 @@ var myPanel = new Ext.Panel({ * @return {Ext.BoxComponent} this */ setSize : function(w, h){ + // support for standard size objects if(typeof w == 'object'){ h = w.height; w = w.width; } + if (Ext.isDefined(w) && Ext.isDefined(this.boxMinWidth) && (w < this.boxMinWidth)) { + w = this.boxMinWidth; + } + if (Ext.isDefined(h) && Ext.isDefined(this.boxMinHeight) && (h < this.boxMinHeight)) { + h = this.boxMinHeight; + } + if (Ext.isDefined(w) && Ext.isDefined(this.boxMaxWidth) && (w > this.boxMaxWidth)) { + w = this.boxMaxWidth; + } + if (Ext.isDefined(h) && Ext.isDefined(this.boxMaxHeight) && (h > this.boxMaxHeight)) { + h = this.boxMaxHeight; + } // not rendered if(!this.boxReady){ - this.width = w; + this.width = w; this.height = h; return this; } @@ -2817,10 +3155,12 @@ var myPanel = new Ext.Panel({ return this; } this.lastSize = {width: w, height: h}; - var adj = this.adjustSize(w, h); - var aw = adj.width, ah = adj.height; + var adj = this.adjustSize(w, h), + aw = adj.width, + ah = adj.height, + rz; if(aw !== undefined || ah !== undefined){ // this code is nasty but performs better with floaters - var rz = this.getResizeEl(); + rz = this.getResizeEl(); if(!this.deferHeight && aw !== undefined && ah !== undefined){ rz.setSize(aw, ah); }else if(!this.deferHeight && ah !== undefined){ @@ -2836,8 +3176,8 @@ var myPanel = new Ext.Panel({ /** * Sets the width of the component. This method fires the {@link #resize} event. - * @param {Number} width The new width to setThis may be one of:
*/ /** @@ -3787,12 +4135,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 @@ -3803,7 +4151,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:

*

@@ -3840,14 +4188,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
 },
@@ -3885,11 +4237,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); @@ -3938,8 +4303,6 @@ items: [ 'remove' ); - this.enableBubble('add', 'remove'); - /** * The collection of components in this container as a {@link Ext.util.MixedCollection} * @type MixedCollection @@ -3948,11 +4311,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); } }, @@ -3969,34 +4328,41 @@ items: [ if(this.layout && this.layout != layout){ this.layout.setContainer(null); } - this.initItems(); this.layout = layout; + this.initItems(); 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(){ + // Render this Container, this should be done before setLayout is called which + // will hook onResize + Ext.Container.superclass.afterRender.call(this); + 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); - } + // If a CardLayout, the active item set + if(this.activeItem !== undefined){ + var item = this.activeItem; + delete this.activeItem; + this.layout.setActiveItem(item); } + + // If we have no ownerCt, render and size all children if(!this.ownerCt){ - // force a layout if no ownerCt is set this.doLayout(false, true); } + + // This is a manually configured flag set by users in conjunction with renderTo. + // Not to be confused with the flag by the same name used in Layouts. if(this.monitorResize === true){ Ext.EventManager.onWindowResize(this.doLayout, this, [false]); } @@ -4029,10 +4395,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
    @@ -4044,32 +4410,49 @@ tb.{@link #doLayout}();             // refresh the layout
          * may not be removed or added.  See the Notes for {@link Ext.layout.BorderLayout BorderLayout}
          * for more details.
  • *
- * @param {Object/Array} component - *

Either a single component or an Array of components to add. See + * @param {...Object/Array} component + *

Either one or more Components to add or an Array of Components to add. See * {@link #items} for additional information.

- * @param {Object} (Optional) component_2 - * @param {Object} (Optional) component_n - * @return {Ext.Component} component The Component (or config object) that was added. + * @return {Ext.Component/Array} The Components that were added. */ add : function(comp){ 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 @@ -4092,20 +4475,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; @@ -4113,14 +4497,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; @@ -4148,19 +4536,35 @@ 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){ + var l = this.layout, + hasLayout = l && this.rendered; + + if(hasLayout){ + l.onRemove(c); + } + this.items.remove(c); + c.onRemoved(); + this.onRemove(c); + if(autoDestroy === true || (autoDestroy !== false && this.autoDestroy)){ + c.destroy(); + } + if(hasLayout){ + l.afterRemove(c); + } + }, + /** * Removes all components from this container. * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function. @@ -4188,9 +4592,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}. @@ -4198,14 +4602,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); @@ -4214,8 +4618,28 @@ tb.{@link #doLayout}(); // refresh the layout }, // private - createComponent : function(config){ - return Ext.create(config, this.defaultType); + createComponent : function(config, defaultType){ + if (config.render) { + return config; + } + // add in ownerCt at creation time but then immediately + // remove so that onBeforeAdd can handle it + var c = Ext.create(Ext.apply({ + ownerCt: this + }, config), defaultType || this.defaultType); + delete c.initialConfig.ownerCt; + delete c.ownerCt; + return c; + }, + + /** + * @private + * 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.getVisibilityEl(); + return el && el.dom && !el.isStyle("display", "none"); }, /** @@ -4226,13 +4650,14 @@ tb.{@link #doLayout}(); // refresh the layout * @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){ + + doLayout : function(shallow, force){ var rendered = this.rendered, - forceLayout = this.forceLayout; + forceLayout = force || this.forceLayout; - if(!this.isVisible() || this.collapsed){ + if(this.collapsed || !this.canLayout()){ this.deferLayout = this.deferLayout || !shallow; - if(!(force || forceLayout)){ + if(!forceLayout){ return; } shallow = shallow && !this.deferLayout; @@ -4247,23 +4672,55 @@ tb.{@link #doLayout}(); // refresh the layout for(var i = 0, len = cs.length; i < len; i++){ var c = cs[i]; if(c.doLayout){ - c.forceLayout = forceLayout; - c.doLayout(); + c.doLayout(false, forceLayout); } } } if(rendered){ - this.onLayout(shallow, force); + this.onLayout(shallow, forceLayout); } + // Initial layout completed + this.hasLayout = true; delete this.forceLayout; }, - //private onLayout : Ext.emptyFn, + // private + shouldBufferLayout: function(){ + /* + * Returns true if the container should buffer a layout. + * This is true only if the container has previously been laid out + * and has a parent container that is pending a layout. + */ + var hl = this.hasLayout; + if(this.ownerCt){ + // Only ever buffer if we've laid out the first time and we have one pending. + return hl ? !this.hasLayoutPending() : false; + } + // Never buffer initial layout + return hl; + }, + + // private + hasLayoutPending: function(){ + // Traverse hierarchy to see if any parent container has a pending layout. + var pending = false; + this.ownerCt.bubble(function(c){ + if(c.layoutPending){ + pending = true; + return false; + } + }); + return pending; + }, + onShow : function(){ + // removes css classes that were added to hide Ext.Container.superclass.onShow.call(this); - if(this.deferLayout !== undefined){ + // If we were sized during the time we were hidden, layout. + if(Ext.isDefined(this.deferLayout)){ + delete this.deferLayout; this.doLayout(true); } }, @@ -4275,7 +4732,7 @@ tb.{@link #doLayout}(); // refresh the layout */ getLayout : function(){ if(!this.layout){ - var layout = new Ext.layout.ContainerLayout(this.layoutConfig); + var layout = new Ext.layout.AutoLayout(this.layoutConfig); this.setLayout(layout); } return this.layout; @@ -4283,8 +4740,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); @@ -4413,20 +4873,10 @@ Ext.Container.LAYOUTS = {}; Ext.reg('container', Ext.Container); /** * @class Ext.layout.ContainerLayout - *

The ContainerLayout class is the default layout manager delegated by {@link Ext.Container} to - * render any child Components when no {@link Ext.Container#layout layout} is configured into - * a {@link Ext.Container Container}. ContainerLayout provides the basic foundation for all other layout - * classes in Ext. It simply renders all child Components into the Container, performing no sizing or - * positioning services. To utilize a layout that provides sizing and positioning of child Components, - * specify an appropriate {@link Ext.Container#layout layout}.

*

This class is intended to be extended or created via the {@link Ext.Container#layout layout} * configuration property. See {@link Ext.Container#layout} for additional details.

*/ -Ext.layout.ContainerLayout = function(config){ - Ext.apply(this, config); -}; - -Ext.layout.ContainerLayout.prototype = { +Ext.layout.ContainerLayout = Ext.extend(Object, { /** * @cfg {String} extraCls *

An optional extra CSS class that will be added to the container. This can be useful for adding @@ -4466,11 +4916,49 @@ Ext.layout.ContainerLayout.prototype = { // private activeItem : null, + constructor : function(config){ + this.id = Ext.id(null, 'ext-layout-'); + Ext.apply(this, config); + }, + + type: 'container', + + /* Workaround for how IE measures autoWidth elements. It prefers bottom-up measurements + whereas other browser prefer top-down. We will hide all target child elements before we measure and + put them back to get an accurate measurement. + */ + IEMeasureHack : function(target, viewFlag) { + var tChildren = target.dom.childNodes, tLen = tChildren.length, c, d = [], e, i, ret; + for (i = 0 ; i < tLen ; i++) { + c = tChildren[i]; + e = Ext.get(c); + if (e) { + d[i] = e.getStyle('display'); + e.setStyle({display: 'none'}); + } + } + ret = target ? target.getViewSize(viewFlag) : {}; + for (i = 0 ; i < tLen ; i++) { + c = tChildren[i]; + e = Ext.get(c); + if (e) { + e.setStyle({display: d[i]}); + } + } + return ret; + }, + + // Placeholder for the derived layouts + getLayoutTargetSize : Ext.EmptyFn, + // private layout : function(){ - var target = this.container.getLayoutTarget(); - this.onLayout(this.container, target); - this.container.fireEvent('afterlayout', this.container, this); + var ct = this.container, target = ct.getLayoutTarget(); + if(!(this.hasLayout || Ext.isEmpty(this.targetCls))){ + target.addClass(this.targetCls); + } + this.onLayout(ct, target); + ct.fireEvent('afterlayout', ct, this); }, // private @@ -4480,119 +4968,172 @@ Ext.layout.ContainerLayout.prototype = { // private isValidParent : function(c, target){ - return target && c.getDomPositionEl().dom.parentNode == (target.dom || target); + return target && c.getPositionEl().dom.parentNode == (target.dom || target); }, // private renderAll : function(ct, target){ - var items = ct.items.items; - for(var i = 0, len = items.length; i < len; i++) { - var c = items[i]; + var items = ct.items.items, i, c, len = items.length; + for(i = 0; i < len; i++) { + c = items[i]; if(c && (!c.rendered || !this.isValidParent(c, target))){ this.renderItem(c, i, target); } } }, - // private + /** + * @private + * Renders the given Component into the target Element. If the Component is already rendered, + * it is moved to the provided target instead. + * @param {Ext.Component} c The Component to render + * @param {Number} position The position within the target to render the item to + * @param {Ext.Element} target The target Element + */ renderItem : function(c, position, target){ - if(c && !c.rendered){ - c.render(target, position); - this.configureItem(c, position); - }else if(c && !this.isValidParent(c, target)){ - if(typeof position == 'number'){ - position = target.dom.childNodes[position]; + if (c) { + if (!c.rendered) { + c.render(target, position); + this.configureItem(c, position); + } else if (!this.isValidParent(c, target)) { + if (Ext.isNumber(position)) { + position = target.dom.childNodes[position]; + } + + target.dom.insertBefore(c.getPositionEl().dom, position || null); + c.container = target; + this.configureItem(c, position); } - target.dom.insertBefore(c.getDomPositionEl().dom, position || null); - c.container = target; - this.configureItem(c, position); } }, - - // private + + // private. + // Get all rendered items to lay out. + getRenderedItems: function(ct){ + var t = ct.getLayoutTarget(), cti = ct.items.items, len = cti.length, i, c, items = []; + for (i = 0; i < len; i++) { + if((c = cti[i]).rendered && this.isValidParent(c, t)){ + items.push(c); + } + }; + return items; + }, + + /** + * @private + * Applies extraCls and hides the item if renderHidden is true + */ configureItem: function(c, position){ - if(this.extraCls){ + if (this.extraCls) { var t = c.getPositionEl ? c.getPositionEl() : c; t.addClass(this.extraCls); } + + // If we are forcing a layout, do so *before* we hide so elements have height/width + if (c.doLayout && this.forceLayout) { + c.doLayout(); + } if (this.renderHidden && c != this.activeItem) { c.hide(); } - if(c.doLayout){ - c.doLayout(false, this.forceLayout); + }, + + onRemove: function(c){ + if(this.activeItem == c){ + delete this.activeItem; + } + if(c.rendered && this.extraCls){ + var t = c.getPositionEl ? c.getPositionEl() : c; + t.removeClass(this.extraCls); + } + }, + + afterRemove: function(c){ + if(c.removeRestore){ + c.removeMode = 'container'; + delete c.removeRestore; } }, // private onResize: function(){ - if(this.container.collapsed){ + var ct = this.container, + b; + if(ct.collapsed){ return; } - var b = this.container.bufferResize; - if(b){ + if(b = ct.bufferResize && ct.shouldBufferLayout()){ if(!this.resizeTask){ this.resizeTask = new Ext.util.DelayedTask(this.runLayout, this); - this.resizeBuffer = typeof b == 'number' ? b : 100; + this.resizeBuffer = Ext.isNumber(b) ? b : 50; } + ct.layoutPending = true; this.resizeTask.delay(this.resizeBuffer); }else{ this.runLayout(); } }, - - // private + runLayout: function(){ + var ct = this.container; this.layout(); - this.container.onLayout(); + ct.onLayout(); + delete ct.layoutPending; }, // private setContainer : function(ct){ + /** + * This monitorResize flag will be renamed soon as to avoid confusion + * with the Container version which hooks onWindowResize to doLayout + * + * monitorResize flag in this context attaches the resize event between + * a container and it's layout + */ if(this.monitorResize && ct != this.container){ - if(this.container){ - this.container.un('resize', this.onResize, this); - this.container.un('bodyresize', this.onResize, this); + var old = this.container; + if(old){ + old.un(old.resizeEvent, this.onResize, this); } if(ct){ - ct.on({ - scope: this, - resize: this.onResize, - bodyresize: this.onResize - }); + ct.on(ct.resizeEvent, this.onResize, this); } } this.container = ct; }, - // private + /** + * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations + * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result) + * @param {Number|String} v The encoded margins + * @return {Object} An object with margin sizes for top, right, bottom and left + */ parseMargins : function(v){ - if(typeof v == 'number'){ + if (Ext.isNumber(v)) { v = v.toString(); } - var ms = v.split(' '); - var len = ms.length; - if(len == 1){ - ms[1] = ms[0]; - ms[2] = ms[0]; - ms[3] = ms[0]; - } - if(len == 2){ + var ms = v.split(' '), + len = ms.length; + + if (len == 1) { + ms[1] = ms[2] = ms[3] = ms[0]; + } else if(len == 2) { ms[2] = ms[0]; ms[3] = ms[1]; - } - if(len == 3){ + } else if(len == 3) { ms[3] = ms[1]; } + return { - top:parseInt(ms[0], 10) || 0, - right:parseInt(ms[1], 10) || 0, + top :parseInt(ms[0], 10) || 0, + right :parseInt(ms[1], 10) || 0, bottom:parseInt(ms[2], 10) || 0, - left:parseInt(ms[3], 10) || 0 + left :parseInt(ms[3], 10) || 0 }; }, /** - * The {@link Template Ext.Template} used by Field rendering layout classes (such as + * The {@link Ext.Template Ext.Template} used by Field rendering layout classes (such as * {@link Ext.layout.FormLayout}) to create the DOM structure of a fully wrapped, * labeled and styled form Field. A default Template is supplied, but this may be * overriden to create custom field structures. The template processes values returned from @@ -4611,695 +5152,460 @@ Ext.layout.ContainerLayout.prototype = { t.disableFormats = true; return t.compile(); })(), - + /* * Destroys this layout. This is a template method that is empty by default, but should be implemented * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes. * @protected */ - destroy : Ext.emptyFn -}; -Ext.Container.LAYOUTS['auto'] = Ext.layout.ContainerLayout;/** - * @class Ext.layout.FitLayout - * @extends Ext.layout.ContainerLayout - *

This is a base class for layouts that contain a single item that automatically expands to fill the layout's - * container. This class is intended to be extended or created via the layout:'fit' {@link Ext.Container#layout} - * config, and should generally not need to be created directly via the new keyword.

- *

FitLayout does not have any direct config options (other than inherited ones). To fit a panel to a container - * using FitLayout, simply set layout:'fit' on the container and add a single panel to it. If the container has - * multiple panels, only the first one will be displayed. Example usage:

- *

-var p = new Ext.Panel({
-    title: 'Fit Layout',
-    layout:'fit',
-    items: {
-        title: 'Inner Panel',
-        html: '<p>This is the inner panel content</p>',
-        border: false
-    }
-});
-
- */ -Ext.layout.FitLayout = Ext.extend(Ext.layout.ContainerLayout, { - // private - monitorResize:true, - - // private - onLayout : function(ct, target){ - Ext.layout.FitLayout.superclass.onLayout.call(this, ct, target); - if(!this.container.collapsed){ - var sz = (Ext.isIE6 && Ext.isStrict && target.dom == document.body) ? target.getViewSize() : target.getStyleSize(); - this.setItemSize(this.activeItem || ct.items.itemAt(0), sz); - } - }, - - // private - setItemSize : function(item, size){ - if(item && size.height > 0){ // display none? - item.setSize(size); - } - } -}); -Ext.Container.LAYOUTS['fit'] = Ext.layout.FitLayout;/** - * @class Ext.layout.CardLayout - * @extends Ext.layout.FitLayout - *

This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be - * visible at any given time. This layout style is most commonly used for wizards, tab implementations, etc. - * This class is intended to be extended or created via the layout:'card' {@link Ext.Container#layout} config, - * and should generally not need to be created directly via the new keyword.

- *

The CardLayout's focal method is {@link #setActiveItem}. Since only one panel is displayed at a time, - * the only way to move from one Component to the next is by calling setActiveItem, passing the id or index of - * the next panel to display. The layout itself does not provide a user interface for handling this navigation, - * so that functionality must be provided by the developer.

- *

In the following example, a simplistic wizard setup is demonstrated. A button bar is added - * to the footer of the containing panel to provide navigation buttons. The buttons will be handled by a - * common navigation routine -- for this example, the implementation of that routine has been ommitted since - * it can be any type of custom logic. Note that other uses of a CardLayout (like a tab control) would require a - * completely different implementation. For serious implementations, a better approach would be to extend - * CardLayout to provide the custom functionality needed. Example usage:

- *

-var navHandler = function(direction){
-    // This routine could contain business logic required to manage the navigation steps.
-    // It would call setActiveItem as needed, manage navigation button state, handle any
-    // branching logic that might be required, handle alternate actions like cancellation
-    // or finalization, etc.  A complete wizard implementation could get pretty
-    // sophisticated depending on the complexity required, and should probably be
-    // done as a subclass of CardLayout in a real-world implementation.
-};
-
-var card = new Ext.Panel({
-    title: 'Example Wizard',
-    layout:'card',
-    activeItem: 0, // make sure the active item is set on the container config!
-    bodyStyle: 'padding:15px',
-    defaults: {
-        // applied to each contained panel
-        border:false
-    },
-    // just an example of one possible navigation scheme, using buttons
-    bbar: [
-        {
-            id: 'move-prev',
-            text: 'Back',
-            handler: navHandler.createDelegate(this, [-1]),
-            disabled: true
-        },
-        '->', // greedy spacer so that the buttons are aligned to each side
-        {
-            id: 'move-next',
-            text: 'Next',
-            handler: navHandler.createDelegate(this, [1])
-        }
-    ],
-    // the panels (or "cards") within the layout
-    items: [{
-        id: 'card-0',
-        html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
-    },{
-        id: 'card-1',
-        html: '<p>Step 2 of 3</p>'
-    },{
-        id: 'card-2',
-        html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
-    }]
-});
-
- */ -Ext.layout.CardLayout = Ext.extend(Ext.layout.FitLayout, { - /** - * @cfg {Boolean} deferredRender - * True to render each contained item at the time it becomes active, false to render all contained items - * as soon as the layout is rendered (defaults to false). If there is a significant amount of content or - * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to - * true might improve performance. - */ - deferredRender : false, - - /** - * @cfg {Boolean} layoutOnCardChange - * True to force a layout of the active item when the active card is changed. Defaults to false. - */ - layoutOnCardChange : false, - - /** - * @cfg {Boolean} renderHidden @hide - */ - // private - renderHidden : true, - - constructor: function(config){ - Ext.layout.CardLayout.superclass.constructor.call(this, config); - this.forceLayout = (this.deferredRender === false); - }, - - /** - * Sets the active (visible) item in the layout. - * @param {String/Number} item The string component id or numeric index of the item to activate - */ - setActiveItem : function(item){ - item = this.container.getComponent(item); - if(this.activeItem != item){ - if(this.activeItem){ - this.activeItem.hide(); - } - this.activeItem = item; - item.show(); - this.container.doLayout(); - if(this.layoutOnCardChange && item.doLayout){ - item.doLayout(); - } - } - }, - - // private - renderAll : function(ct, target){ - if(this.deferredRender){ - this.renderItem(this.activeItem, undefined, target); - }else{ - Ext.layout.CardLayout.superclass.renderAll.call(this, ct, target); - } - } -}); -Ext.Container.LAYOUTS['card'] = Ext.layout.CardLayout;/** - * @class Ext.layout.AnchorLayout - * @extends Ext.layout.ContainerLayout - *

This is a layout that enables anchoring of contained elements relative to the container's dimensions. - * If the container is resized, all anchored items are automatically rerendered according to their - * {@link #anchor} rules.

- *

This class is intended to be extended or created via the layout:'anchor' {@link Ext.Container#layout} - * config, and should generally not need to be created directly via the new keyword.

- *

AnchorLayout does not have any direct config options (other than inherited ones). By default, - * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the - * container using the AnchorLayout can supply an anchoring-specific config property of anchorSize. - * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating - * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring - * logic if necessary. For example:

- *

-var viewport = new Ext.Viewport({
-    layout:'anchor',
-    anchorSize: {width:800, height:600},
-    items:[{
-        title:'Item 1',
-        html:'Content 1',
-        width:800,
-        anchor:'right 20%'
-    },{
-        title:'Item 2',
-        html:'Content 2',
-        width:300,
-        anchor:'50% 30%'
-    },{
-        title:'Item 3',
-        html:'Content 3',
-        width:600,
-        anchor:'-100 50%'
-    }]
-});
- * 
- */ -Ext.layout.AnchorLayout = Ext.extend(Ext.layout.ContainerLayout, { - /** - * @cfg {String} anchor - *

This configuation option is to be applied to child items of a container managed by - * this layout (ie. configured with layout:'anchor').


- * - *

This value is what tells the layout how an item should be anchored to the container. items - * added to an AnchorLayout accept an anchoring-specific config property of anchor which is a string - * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%'). - * The following types of anchor values are supported:

    - * - *
  • Percentage : Any value between 1 and 100, expressed as a percentage.
    - * The first anchor is the percentage width that the item should take up within the container, and the - * second is the percentage height. For example:
    
    -// two values specified
    -anchor: '100% 50%' // render item complete width of the container and
    -                   // 1/2 height of the container
    -// one value specified
    -anchor: '100%'     // the width value; the height will default to auto
    -     * 
  • - * - *
  • Offsets : Any positive or negative integer value.
    - * This is a raw adjustment where the first anchor is the offset from the right edge of the container, - * and the second is the offset from the bottom edge. For example:
    
    -// two values specified
    -anchor: '-50 -100' // render item the complete width of the container
    -                   // minus 50 pixels and
    -                   // the complete height minus 100 pixels.
    -// one value specified
    -anchor: '-50'      // anchor value is assumed to be the right offset value
    -                   // bottom offset will default to 0
    -     * 
  • - * - *
  • Sides : Valid values are 'right' (or 'r') and 'bottom' - * (or 'b').
    - * Either the container must have a fixed size or an anchorSize config value defined at render time in - * order for these to have any effect.
  • - * - *
  • Mixed :
    - * Anchor values can also be mixed as needed. For example, to render the width offset from the container - * right edge by 50 pixels and 75% of the container's height use: - *
    
    -anchor: '-50 75%' 
    -     * 
  • - * - * - *
- */ - - // private - monitorResize:true, - - // private - getAnchorViewSize : function(ct, target){ - return target.dom == document.body ? - target.getViewSize() : target.getStyleSize(); - }, - - // private - onLayout : function(ct, target){ - Ext.layout.AnchorLayout.superclass.onLayout.call(this, ct, target); - - var size = this.getAnchorViewSize(ct, target); - - var w = size.width, h = size.height; - - if(w < 20 && h < 20){ - return; - } - - // find the container anchoring size - var aw, ah; - if(ct.anchorSize){ - if(typeof ct.anchorSize == 'number'){ - aw = ct.anchorSize; - }else{ - aw = ct.anchorSize.width; - ah = ct.anchorSize.height; - } - }else{ - aw = ct.initialConfig.width; - ah = ct.initialConfig.height; - } - - var cs = ct.items.items, len = cs.length, i, c, a, cw, ch; - for(i = 0; i < len; i++){ - c = cs[i]; - if(c.anchor){ - a = c.anchorSpec; - if(!a){ // cache all anchor values - var vs = c.anchor.split(' '); - c.anchorSpec = a = { - right: this.parseAnchor(vs[0], c.initialConfig.width, aw), - bottom: this.parseAnchor(vs[1], c.initialConfig.height, ah) - }; - } - cw = a.right ? this.adjustWidthAnchor(a.right(w), c) : undefined; - ch = a.bottom ? this.adjustHeightAnchor(a.bottom(h), c) : undefined; - - if(cw || ch){ - c.setSize(cw || undefined, ch || undefined); - } - } - } - }, - - // private - parseAnchor : function(a, start, cstart){ - if(a && a != 'none'){ - var last; - if(/^(r|right|b|bottom)$/i.test(a)){ // standard anchor - var diff = cstart - start; - return function(v){ - if(v !== last){ - last = v; - return v - diff; - } - } - }else if(a.indexOf('%') != -1){ - var ratio = parseFloat(a.replace('%', ''))*.01; // percentage - return function(v){ - if(v !== last){ - last = v; - return Math.floor(v*ratio); - } - } - }else{ - a = parseInt(a, 10); - if(!isNaN(a)){ // simple offset adjustment - return function(v){ - if(v !== last){ - last = v; - return v + a; - } - } - } - } - } - return false; - }, - - // private - adjustWidthAnchor : function(value, comp){ - return value; - }, - - // private - adjustHeightAnchor : function(value, comp){ - return value; - } - - /** - * @property activeItem - * @hide - */ -}); -Ext.Container.LAYOUTS['anchor'] = Ext.layout.AnchorLayout;/** - * @class Ext.layout.ColumnLayout - * @extends Ext.layout.ContainerLayout - *

This is the layout style of choice for creating structural layouts in a multi-column format where the width of - * each column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content. - * This class is intended to be extended or created via the layout:'column' {@link Ext.Container#layout} config, - * and should generally not need to be created directly via the new keyword.

- *

ColumnLayout does not have any direct config options (other than inherited ones), but it does support a - * specific config property of columnWidth that can be included in the config of any panel added to it. The - * layout will use the columnWidth (if present) or width of each panel during layout to determine how to size each panel. - * If width or columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).

- *

The width property is always evaluated as pixels, and must be a number greater than or equal to 1. - * The columnWidth property is always evaluated as a percentage, and must be a decimal value greater than 0 and - * less than 1 (e.g., .25).

- *

The basic rules for specifying column widths are pretty simple. The logic makes two passes through the - * set of contained panels. During the first layout pass, all panels that either have a fixed width or none - * specified (auto) are skipped, but their widths are subtracted from the overall container width. During the second - * pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages based on - * the total remaining container width. In other words, percentage width panels are designed to fill the space - * left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any number of columns - * with different percentages, the columnWidths must always add up to 1 (or 100%) when added together, otherwise your - * layout may not render as expected. Example usage:

- *

-// All columns are percentages -- they must add up to 1
-var p = new Ext.Panel({
-    title: 'Column Layout - Percentage Only',
-    layout:'column',
-    items: [{
-        title: 'Column 1',
-        columnWidth: .25 
-    },{
-        title: 'Column 2',
-        columnWidth: .6
-    },{
-        title: 'Column 3',
-        columnWidth: .15
-    }]
-});
-
-// Mix of width and columnWidth -- all columnWidth values must add up
-// to 1. The first column will take up exactly 120px, and the last two
-// columns will fill the remaining container width.
-var p = new Ext.Panel({
-    title: 'Column Layout - Mixed',
-    layout:'column',
-    items: [{
-        title: 'Column 1',
-        width: 120
-    },{
-        title: 'Column 2',
-        columnWidth: .8
-    },{
-        title: 'Column 3',
-        columnWidth: .2
-    }]
-});
-
- */ -Ext.layout.ColumnLayout = Ext.extend(Ext.layout.ContainerLayout, { - // private - monitorResize:true, - - extraCls: 'x-column', - - scrollOffset : 0, - - // private - isValidParent : function(c, target){ - return (c.getPositionEl ? c.getPositionEl() : c.getEl()).dom.parentNode == this.innerCt.dom; - }, - - // private - onLayout : function(ct, target){ - var cs = ct.items.items, len = cs.length, c, i; - - if(!this.innerCt){ - target.addClass('x-column-layout-ct'); - - // the innerCt prevents wrapping and shuffling while - // the container is resizing - this.innerCt = target.createChild({cls:'x-column-inner'}); - this.innerCt.createChild({cls:'x-clear'}); - } - this.renderAll(ct, this.innerCt); - - var size = Ext.isIE && target.dom != Ext.getBody().dom ? target.getStyleSize() : target.getViewSize(); - - if(size.width < 1 && size.height < 1){ // display none? - return; - } - - var w = size.width - target.getPadding('lr') - this.scrollOffset, - h = size.height - target.getPadding('tb'), - pw = w; - - this.innerCt.setWidth(w); - - // some columns can be percentages while others are fixed - // so we need to make 2 passes - - for(i = 0; i < len; i++){ - c = cs[i]; - if(!c.columnWidth){ - pw -= (c.getSize().width + c.getEl().getMargins('lr')); - } - } - - pw = pw < 0 ? 0 : pw; - - for(i = 0; i < len; i++){ - c = cs[i]; - if(c.columnWidth){ - c.setSize(Math.floor(c.columnWidth*pw) - c.getEl().getMargins('lr')); - } - } - } - - /** - * @property activeItem - * @hide - */ -}); - -Ext.Container.LAYOUTS['column'] = Ext.layout.ColumnLayout;/** - * @class Ext.layout.BorderLayout + destroy : function(){ + // Stop any buffered layout tasks + if(this.resizeTask && this.resizeTask.cancel){ + this.resizeTask.cancel(); + } + if(!Ext.isEmpty(this.targetCls)){ + var target = this.container.getLayoutTarget(); + if(target){ + target.removeClass(this.targetCls); + } + } + } +});/** + * @class Ext.layout.AutoLayout + *

The AutoLayout is the default layout manager delegated by {@link Ext.Container} to + * render any child Components when no {@link Ext.Container#layout layout} is configured into + * a {@link Ext.Container Container}.. AutoLayout provides only a passthrough of any layout calls + * to any child containers.

+ */ +Ext.layout.AutoLayout = Ext.extend(Ext.layout.ContainerLayout, { + type: 'auto', + + monitorResize: true, + + onLayout : function(ct, target){ + Ext.layout.AutoLayout.superclass.onLayout.call(this, ct, target); + var cs = this.getRenderedItems(ct), len = cs.length, i, c; + for(i = 0; i < len; i++){ + c = cs[i]; + if (c.doLayout){ + // Shallow layout children + c.doLayout(true); + } + } + } +}); + +Ext.Container.LAYOUTS['auto'] = Ext.layout.AutoLayout; +/** + * @class Ext.layout.FitLayout * @extends Ext.layout.ContainerLayout - *

This is a multi-pane, application-oriented UI layout style that supports multiple - * nested panels, automatic {@link Ext.layout.BorderLayout.Region#split split} bars between - * {@link Ext.layout.BorderLayout.Region#BorderLayout.Region regions} and built-in - * {@link Ext.layout.BorderLayout.Region#collapsible expanding and collapsing} of regions.

- *

This class is intended to be extended or created via the layout:'border' - * {@link Ext.Container#layout} config, and should generally not need to be created directly - * via the new keyword.

- *

BorderLayout does not have any direct config options (other than inherited ones). - * All configuration options available for customizing the BorderLayout are at the - * {@link Ext.layout.BorderLayout.Region} and {@link Ext.layout.BorderLayout.SplitRegion} - * levels.

- *

Example usage:

+ *

This is a base class for layouts that contain a single item that automatically expands to fill the layout's + * container. This class is intended to be extended or created via the layout:'fit' {@link Ext.Container#layout} + * config, and should generally not need to be created directly via the new keyword.

+ *

FitLayout does not have any direct config options (other than inherited ones). To fit a panel to a container + * using FitLayout, simply set layout:'fit' on the container and add a single panel to it. If the container has + * multiple panels, only the first one will be displayed. Example usage:

*

-var myBorderPanel = new Ext.Panel({
-    {@link Ext.Component#renderTo renderTo}: document.body,
-    {@link Ext.BoxComponent#width width}: 700,
-    {@link Ext.BoxComponent#height height}: 500,
-    {@link Ext.Panel#title title}: 'Border Layout',
-    {@link Ext.Container#layout layout}: 'border',
-    {@link Ext.Container#items items}: [{
-        {@link Ext.Panel#title title}: 'South Region is resizable',
-        {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'south',     // position for region
-        {@link Ext.BoxComponent#height height}: 100,
-        {@link Ext.layout.BorderLayout.Region#split split}: true,         // enable resizing
-        {@link Ext.SplitBar#minSize minSize}: 75,         // defaults to {@link Ext.layout.BorderLayout.Region#minHeight 50} 
-        {@link Ext.SplitBar#maxSize maxSize}: 150,
-        {@link Ext.layout.BorderLayout.Region#margins margins}: '0 5 5 5'
-    },{
-        // xtype: 'panel' implied by default
-        {@link Ext.Panel#title title}: 'West Region is collapsible',
-        {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}:'west',
-        {@link Ext.layout.BorderLayout.Region#margins margins}: '5 0 0 5',
-        {@link Ext.BoxComponent#width width}: 200,
-        {@link Ext.layout.BorderLayout.Region#collapsible collapsible}: true,   // make collapsible
-        {@link Ext.layout.BorderLayout.Region#cmargins cmargins}: '5 5 0 5', // adjust top margin when collapsed
-        {@link Ext.Component#id id}: 'west-region-container',
-        {@link Ext.Container#layout layout}: 'fit',
-        {@link Ext.Panel#unstyled unstyled}: true
-    },{
-        {@link Ext.Panel#title title}: 'Center Region',
-        {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'center',     // center region is required, no width/height specified
-        {@link Ext.Component#xtype xtype}: 'container',
-        {@link Ext.Container#layout layout}: 'fit',
-        {@link Ext.layout.BorderLayout.Region#margins margins}: '5 5 0 0'
-    }]
+var p = new Ext.Panel({
+    title: 'Fit Layout',
+    layout:'fit',
+    items: {
+        title: 'Inner Panel',
+        html: '<p>This is the inner panel content</p>',
+        border: false
+    }
 });
 
- *

Notes:

    - *
  • Any container using the BorderLayout must have a child item with region:'center'. - * The child item in the center region will always be resized to fill the remaining space not used by - * the other regions in the layout.
  • - *
  • Any child items with a region of west or east must have width defined - * (an integer representing the number of pixels that the region should take up).
  • - *
  • Any child items with a region of north or south must have height defined.
  • - *
  • The regions of a BorderLayout are fixed at render time and thereafter, its child Components may not be removed or added. To add/remove - * Components within a BorderLayout, have them wrapped by an additional Container which is directly - * managed by the BorderLayout. If the region is to be collapsible, the Container used directly - * by the BorderLayout manager should be a Panel. In the following example a Container (an Ext.Panel) - * is added to the west region: - *
    
    -wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
    -wrc.{@link Ext.Panel#removeAll removeAll}();
    -wrc.{@link Ext.Container#add add}({
    -    title: 'Added Panel',
    -    html: 'Some content'
    -});
    -wrc.{@link Ext.Container#doLayout doLayout}();
    - * 
    - *
  • - *
  • To reference a {@link Ext.layout.BorderLayout.Region Region}: - *
    
    -wr = myBorderPanel.layout.west;
    - * 
    - *
  • - *
*/ -Ext.layout.BorderLayout = Ext.extend(Ext.layout.ContainerLayout, { +Ext.layout.FitLayout = Ext.extend(Ext.layout.ContainerLayout, { // private monitorResize:true, - // private - rendered : false, + + type: 'fit', + + getLayoutTargetSize : function() { + var target = this.container.getLayoutTarget(); + if (!target) { + return {}; + } + // Style Sized (scrollbars not included) + return target.getStyleSize(); + }, // private onLayout : function(ct, target){ - var collapsed; - if(!this.rendered){ - target.addClass('x-border-layout-ct'); - var items = ct.items.items; - collapsed = []; - for(var i = 0, len = items.length; i < len; i++) { - var c = items[i]; - var pos = c.region; - if(c.collapsed){ - collapsed.push(c); - } - c.collapsed = false; - if(!c.rendered){ - c.cls = c.cls ? c.cls +' x-border-panel' : 'x-border-panel'; - c.render(target, i); - } - this[pos] = pos != 'center' && c.split ? - new Ext.layout.BorderLayout.SplitRegion(this, c.initialConfig, pos) : - new Ext.layout.BorderLayout.Region(this, c.initialConfig, pos); - this[pos].render(target, c); - } - this.rendered = true; + Ext.layout.FitLayout.superclass.onLayout.call(this, ct, target); + if(!ct.collapsed){ + this.setItemSize(this.activeItem || ct.items.itemAt(0), this.getLayoutTargetSize()); } + }, - var size = target.getViewSize(); - if(size.width < 20 || size.height < 20){ // display none? - if(collapsed){ - this.restoreCollapsed = collapsed; - } - return; - }else if(this.restoreCollapsed){ - collapsed = this.restoreCollapsed; - delete this.restoreCollapsed; + // private + setItemSize : function(item, size){ + if(item && size.height > 0){ // display none? + item.setSize(size); } + } +}); +Ext.Container.LAYOUTS['fit'] = Ext.layout.FitLayout;/** + * @class Ext.layout.CardLayout + * @extends Ext.layout.FitLayout + *

This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be + * visible at any given time. This layout style is most commonly used for wizards, tab implementations, etc. + * This class is intended to be extended or created via the layout:'card' {@link Ext.Container#layout} config, + * and should generally not need to be created directly via the new keyword.

+ *

The CardLayout's focal method is {@link #setActiveItem}. Since only one panel is displayed at a time, + * the only way to move from one Component to the next is by calling setActiveItem, passing the id or index of + * the next panel to display. The layout itself does not provide a user interface for handling this navigation, + * so that functionality must be provided by the developer.

+ *

In the following example, a simplistic wizard setup is demonstrated. A button bar is added + * to the footer of the containing panel to provide navigation buttons. The buttons will be handled by a + * common navigation routine -- for this example, the implementation of that routine has been ommitted since + * it can be any type of custom logic. Note that other uses of a CardLayout (like a tab control) would require a + * completely different implementation. For serious implementations, a better approach would be to extend + * CardLayout to provide the custom functionality needed. Example usage:

+ *

+var navHandler = function(direction){
+    // This routine could contain business logic required to manage the navigation steps.
+    // It would call setActiveItem as needed, manage navigation button state, handle any
+    // branching logic that might be required, handle alternate actions like cancellation
+    // or finalization, etc.  A complete wizard implementation could get pretty
+    // sophisticated depending on the complexity required, and should probably be
+    // done as a subclass of CardLayout in a real-world implementation.
+};
 
-        var w = size.width, h = size.height;
-        var centerW = w, centerH = h, centerY = 0, centerX = 0;
-
-        var n = this.north, s = this.south, west = this.west, e = this.east, c = this.center;
-        if(!c && Ext.layout.BorderLayout.WARN !== false){
-            throw 'No center region defined in BorderLayout ' + ct.id;
+var card = new Ext.Panel({
+    title: 'Example Wizard',
+    layout:'card',
+    activeItem: 0, // make sure the active item is set on the container config!
+    bodyStyle: 'padding:15px',
+    defaults: {
+        // applied to each contained panel
+        border:false
+    },
+    // just an example of one possible navigation scheme, using buttons
+    bbar: [
+        {
+            id: 'move-prev',
+            text: 'Back',
+            handler: navHandler.createDelegate(this, [-1]),
+            disabled: true
+        },
+        '->', // greedy spacer so that the buttons are aligned to each side
+        {
+            id: 'move-next',
+            text: 'Next',
+            handler: navHandler.createDelegate(this, [1])
         }
+    ],
+    // the panels (or "cards") within the layout
+    items: [{
+        id: 'card-0',
+        html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
+    },{
+        id: 'card-1',
+        html: '<p>Step 2 of 3</p>'
+    },{
+        id: 'card-2',
+        html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
+    }]
+});
+
+ */ +Ext.layout.CardLayout = Ext.extend(Ext.layout.FitLayout, { + /** + * @cfg {Boolean} deferredRender + * True to render each contained item at the time it becomes active, false to render all contained items + * as soon as the layout is rendered (defaults to false). If there is a significant amount of content or + * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to + * true might improve performance. + */ + deferredRender : false, - if(n && n.isVisible()){ - var b = n.getSize(); - var m = n.getMargins(); - b.width = w - (m.left+m.right); - b.x = m.left; - b.y = m.top; - centerY = b.height + b.y + m.bottom; - centerH -= centerY; - n.applyLayout(b); - } - if(s && s.isVisible()){ - var b = s.getSize(); - var m = s.getMargins(); - b.width = w - (m.left+m.right); - b.x = m.left; - var totalHeight = (b.height + m.top + m.bottom); - b.y = h - totalHeight + m.top; - centerH -= totalHeight; - s.applyLayout(b); + /** + * @cfg {Boolean} layoutOnCardChange + * True to force a layout of the active item when the active card is changed. Defaults to false. + */ + layoutOnCardChange : false, + + /** + * @cfg {Boolean} renderHidden @hide + */ + // private + renderHidden : true, + + type: 'card', + + /** + * Sets the active (visible) item in the layout. + * @param {String/Number} item The string component id or numeric index of the item to activate + */ + setActiveItem : function(item){ + var ai = this.activeItem, + ct = this.container; + item = ct.getComponent(item); + + // Is this a valid, different card? + if(item && ai != item){ + + // Changing cards, hide the current one + if(ai){ + ai.hide(); + if (ai.hidden !== true) { + return false; + } + ai.fireEvent('deactivate', ai); + } + + var layout = item.doLayout && (this.layoutOnCardChange || !item.rendered); + + // Change activeItem reference + this.activeItem = item; + + // The container is about to get a recursive layout, remove any deferLayout reference + // because it will trigger a redundant layout. + delete item.deferLayout; + + // Show the new component + item.show(); + + this.layout(); + + if(layout){ + item.doLayout(); + } + item.fireEvent('activate', item); } - if(west && west.isVisible()){ - var b = west.getSize(); - var m = west.getMargins(); - b.height = centerH - (m.top+m.bottom); - b.x = m.left; - b.y = centerY + m.top; - var totalWidth = (b.width + m.left + m.right); - centerX += totalWidth; - centerW -= totalWidth; - west.applyLayout(b); + }, + + // private + renderAll : function(ct, target){ + if(this.deferredRender){ + this.renderItem(this.activeItem, undefined, target); + }else{ + Ext.layout.CardLayout.superclass.renderAll.call(this, ct, target); } - if(e && e.isVisible()){ - var b = e.getSize(); - var m = e.getMargins(); - b.height = centerH - (m.top+m.bottom); - var totalWidth = (b.width + m.left + m.right); - b.x = w - totalWidth + m.left; - b.y = centerY + m.top; - centerW -= totalWidth; - e.applyLayout(b); + } +}); +Ext.Container.LAYOUTS['card'] = Ext.layout.CardLayout; +/** + * @class Ext.layout.AnchorLayout + * @extends Ext.layout.ContainerLayout + *

This is a layout that enables anchoring of contained elements relative to the container's dimensions. + * If the container is resized, all anchored items are automatically rerendered according to their + * {@link #anchor} rules.

+ *

This class is intended to be extended or created via the layout:'anchor' {@link Ext.Container#layout} + * config, and should generally not need to be created directly via the new keyword.

+ *

AnchorLayout does not have any direct config options (other than inherited ones). By default, + * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the + * container using the AnchorLayout can supply an anchoring-specific config property of anchorSize. + * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating + * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring + * logic if necessary. For example:

+ *

+var viewport = new Ext.Viewport({
+    layout:'anchor',
+    anchorSize: {width:800, height:600},
+    items:[{
+        title:'Item 1',
+        html:'Content 1',
+        width:800,
+        anchor:'right 20%'
+    },{
+        title:'Item 2',
+        html:'Content 2',
+        width:300,
+        anchor:'50% 30%'
+    },{
+        title:'Item 3',
+        html:'Content 3',
+        width:600,
+        anchor:'-100 50%'
+    }]
+});
+ * 
+ */ +Ext.layout.AnchorLayout = Ext.extend(Ext.layout.ContainerLayout, { + /** + * @cfg {String} anchor + *

This configuation option is to be applied to child items of a container managed by + * this layout (ie. configured with layout:'anchor').


+ * + *

This value is what tells the layout how an item should be anchored to the container. items + * added to an AnchorLayout accept an anchoring-specific config property of anchor which is a string + * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%'). + * The following types of anchor values are supported:

    + * + *
  • Percentage : Any value between 1 and 100, expressed as a percentage.
    + * The first anchor is the percentage width that the item should take up within the container, and the + * second is the percentage height. For example:
    
    +// two values specified
    +anchor: '100% 50%' // render item complete width of the container and
    +                   // 1/2 height of the container
    +// one value specified
    +anchor: '100%'     // the width value; the height will default to auto
    +     * 
  • + * + *
  • Offsets : Any positive or negative integer value.
    + * This is a raw adjustment where the first anchor is the offset from the right edge of the container, + * and the second is the offset from the bottom edge. For example:
    
    +// two values specified
    +anchor: '-50 -100' // render item the complete width of the container
    +                   // minus 50 pixels and
    +                   // the complete height minus 100 pixels.
    +// one value specified
    +anchor: '-50'      // anchor value is assumed to be the right offset value
    +                   // bottom offset will default to 0
    +     * 
  • + * + *
  • Sides : Valid values are 'right' (or 'r') and 'bottom' + * (or 'b').
    + * Either the container must have a fixed size or an anchorSize config value defined at render time in + * order for these to have any effect.
  • + * + *
  • Mixed :
    + * Anchor values can also be mixed as needed. For example, to render the width offset from the container + * right edge by 50 pixels and 75% of the container's height use: + *
    
    +anchor: '-50 75%'
    +     * 
  • + * + * + *
+ */ + + // private + monitorResize : true, + + type : 'anchor', + + /** + * @cfg {String} defaultAnchor + * + * default anchor for all child container items applied if no anchor or specific width is set on the child item. Defaults to '100%'. + * + */ + defaultAnchor : '100%', + + parseAnchorRE : /^(r|right|b|bottom)$/i, + + getLayoutTargetSize : function() { + var target = this.container.getLayoutTarget(); + if (!target) { + return {}; } - if(c){ - var m = c.getMargins(); - var centerBox = { - x: centerX + m.left, - y: centerY + m.top, - width: centerW - (m.left+m.right), - height: centerH - (m.top+m.bottom) - }; - c.applyLayout(centerBox); + // Style Sized (scrollbars not included) + return target.getStyleSize(); + }, + + // private + onLayout : function(ct, target){ + Ext.layout.AnchorLayout.superclass.onLayout.call(this, ct, target); + var size = this.getLayoutTargetSize(); + + var w = size.width, h = size.height; + + if(w < 20 && h < 20){ + return; } - if(collapsed){ - for(var i = 0, len = collapsed.length; i < len; i++){ - collapsed[i].collapse(false); + + // find the container anchoring size + var aw, ah; + if(ct.anchorSize){ + if(typeof ct.anchorSize == 'number'){ + aw = ct.anchorSize; + }else{ + aw = ct.anchorSize.width; + ah = ct.anchorSize.height; + } + }else{ + aw = ct.initialConfig.width; + ah = ct.initialConfig.height; + } + + var cs = this.getRenderedItems(ct), len = cs.length, i, c, a, cw, ch, el, vs, boxes = []; + for(i = 0; i < len; i++){ + c = cs[i]; + el = c.getPositionEl(); + + // If a child container item has no anchor and no specific width, set the child to the default anchor size + if (!c.anchor && c.items && !Ext.isNumber(c.width) && !(Ext.isIE6 && Ext.isStrict)){ + c.anchor = this.defaultAnchor; + } + + if(c.anchor){ + a = c.anchorSpec; + if(!a){ // cache all anchor values + vs = c.anchor.split(' '); + c.anchorSpec = a = { + right: this.parseAnchor(vs[0], c.initialConfig.width, aw), + bottom: this.parseAnchor(vs[1], c.initialConfig.height, ah) + }; + } + cw = a.right ? this.adjustWidthAnchor(a.right(w) - el.getMargins('lr'), c) : undefined; + ch = a.bottom ? this.adjustHeightAnchor(a.bottom(h) - el.getMargins('tb'), c) : undefined; + + if(cw || ch){ + boxes.push({ + comp: c, + width: cw || undefined, + height: ch || undefined + }); + } } } - if(Ext.isIE && Ext.isStrict){ // workaround IE strict repainting issue - target.repaint(); + for (i = 0, len = boxes.length; i < len; i++) { + c = boxes[i]; + c.comp.setSize(c.width, c.height); } }, - destroy: function() { - var r = ['north', 'south', 'east', 'west']; - for (var i = 0; i < r.length; i++) { - var region = this[r[i]]; - if(region){ - if(region.destroy){ - region.destroy(); - }else if (region.split){ - region.split.destroy(true); + // private + parseAnchor : function(a, start, cstart){ + if(a && a != 'none'){ + var last; + // standard anchor + if(this.parseAnchorRE.test(a)){ + var diff = cstart - start; + return function(v){ + if(v !== last){ + last = v; + return v - diff; + } + } + // percentage + }else if(a.indexOf('%') != -1){ + var ratio = parseFloat(a.replace('%', ''))*.01; + return function(v){ + if(v !== last){ + last = v; + return Math.floor(v*ratio); + } + } + // simple offset adjustment + }else{ + a = parseInt(a, 10); + if(!isNaN(a)){ + return function(v){ + if(v !== last){ + last = v; + return v + a; + } + } } } } - Ext.layout.BorderLayout.superclass.destroy.call(this); + return false; + }, + + // private + adjustWidthAnchor : function(value, comp){ + return value; + }, + + // private + adjustHeightAnchor : function(value, comp){ + return value; } /** @@ -5307,492 +5613,897 @@ Ext.layout.BorderLayout = Ext.extend(Ext.layout.ContainerLayout, { * @hide */ }); - +Ext.Container.LAYOUTS['anchor'] = Ext.layout.AnchorLayout; /** - * @class Ext.layout.BorderLayout.Region - *

This is a region of a {@link Ext.layout.BorderLayout BorderLayout} that acts as a subcontainer - * within the layout. Each region has its own {@link Ext.layout.ContainerLayout layout} that is - * independent of other regions and the containing BorderLayout, and can be any of the - * {@link Ext.layout.ContainerLayout valid Ext layout types}.

- *

Region size is managed automatically and cannot be changed by the user -- for - * {@link #split resizable regions}, see {@link Ext.layout.BorderLayout.SplitRegion}.

- * @constructor - * Create a new Region. - * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region. - * @param {Object} config The configuration options - * @param {String} position The region position. Valid values are: north, south, - * east, west and center. Every {@link Ext.layout.BorderLayout BorderLayout} - * must have a center region for the primary content -- all other regions are optional. + * @class Ext.layout.ColumnLayout + * @extends Ext.layout.ContainerLayout + *

This is the layout style of choice for creating structural layouts in a multi-column format where the width of + * each column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content. + * This class is intended to be extended or created via the layout:'column' {@link Ext.Container#layout} config, + * and should generally not need to be created directly via the new keyword.

+ *

ColumnLayout does not have any direct config options (other than inherited ones), but it does support a + * specific config property of columnWidth that can be included in the config of any panel added to it. The + * layout will use the columnWidth (if present) or width of each panel during layout to determine how to size each panel. + * If width or columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).

+ *

The width property is always evaluated as pixels, and must be a number greater than or equal to 1. + * The columnWidth property is always evaluated as a percentage, and must be a decimal value greater than 0 and + * less than 1 (e.g., .25).

+ *

The basic rules for specifying column widths are pretty simple. The logic makes two passes through the + * set of contained panels. During the first layout pass, all panels that either have a fixed width or none + * specified (auto) are skipped, but their widths are subtracted from the overall container width. During the second + * pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages based on + * the total remaining container width. In other words, percentage width panels are designed to fill the space + * left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any number of columns + * with different percentages, the columnWidths must always add up to 1 (or 100%) when added together, otherwise your + * layout may not render as expected. Example usage:

+ *

+// All columns are percentages -- they must add up to 1
+var p = new Ext.Panel({
+    title: 'Column Layout - Percentage Only',
+    layout:'column',
+    items: [{
+        title: 'Column 1',
+        columnWidth: .25
+    },{
+        title: 'Column 2',
+        columnWidth: .6
+    },{
+        title: 'Column 3',
+        columnWidth: .15
+    }]
+});
+
+// Mix of width and columnWidth -- all columnWidth values must add up
+// to 1. The first column will take up exactly 120px, and the last two
+// columns will fill the remaining container width.
+var p = new Ext.Panel({
+    title: 'Column Layout - Mixed',
+    layout:'column',
+    items: [{
+        title: 'Column 1',
+        width: 120
+    },{
+        title: 'Column 2',
+        columnWidth: .8
+    },{
+        title: 'Column 3',
+        columnWidth: .2
+    }]
+});
+
*/ -Ext.layout.BorderLayout.Region = function(layout, config, pos){ - Ext.apply(this, config); - this.layout = layout; - this.position = pos; - this.state = {}; - if(typeof this.margins == 'string'){ - this.margins = this.layout.parseMargins(this.margins); - } - this.margins = Ext.applyIf(this.margins || {}, this.defaultMargins); - if(this.collapsible){ - if(typeof this.cmargins == 'string'){ - this.cmargins = this.layout.parseMargins(this.cmargins); +Ext.layout.ColumnLayout = Ext.extend(Ext.layout.ContainerLayout, { + // private + monitorResize:true, + + type: 'column', + + extraCls: 'x-column', + + scrollOffset : 0, + + // private + + targetCls: 'x-column-layout-ct', + + isValidParent : function(c, target){ + return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom; + }, + + getLayoutTargetSize : function() { + var target = this.container.getLayoutTarget(), ret; + if (target) { + ret = target.getViewSize(); + + // IE in strict mode will return a width of 0 on the 1st pass of getViewSize. + // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly + // with getViewSize + if (Ext.isIE && Ext.isStrict && ret.width == 0){ + ret = target.getStyleSize(); + } + + ret.width -= target.getPadding('lr'); + ret.height -= target.getPadding('tb'); } - if(this.collapseMode == 'mini' && !this.cmargins){ - this.cmargins = {left:0,top:0,right:0,bottom:0}; - }else{ - this.cmargins = Ext.applyIf(this.cmargins || {}, - pos == 'north' || pos == 'south' ? this.defaultNSCMargins : this.defaultEWCMargins); + return ret; + }, + + renderAll : function(ct, target) { + if(!this.innerCt){ + // the innerCt prevents wrapping and shuffling while + // the container is resizing + this.innerCt = target.createChild({cls:'x-column-inner'}); + this.innerCt.createChild({cls:'x-clear'}); + } + Ext.layout.ColumnLayout.superclass.renderAll.call(this, ct, this.innerCt); + }, + + // private + onLayout : function(ct, target){ + var cs = ct.items.items, + len = cs.length, + c, + i, + m, + margins = []; + + this.renderAll(ct, target); + + var size = this.getLayoutTargetSize(); + + if(size.width < 1 && size.height < 1){ // display none? + return; + } + + var w = size.width - this.scrollOffset, + h = size.height, + pw = w; + + this.innerCt.setWidth(w); + + // some columns can be percentages while others are fixed + // so we need to make 2 passes + + for(i = 0; i < len; i++){ + c = cs[i]; + m = c.getPositionEl().getMargins('lr'); + margins[i] = m; + if(!c.columnWidth){ + pw -= (c.getWidth() + m); + } + } + + pw = pw < 0 ? 0 : pw; + + for(i = 0; i < len; i++){ + c = cs[i]; + m = margins[i]; + if(c.columnWidth){ + c.setSize(Math.floor(c.columnWidth * pw) - m); + } + } + + // Browsers differ as to when they account for scrollbars. We need to re-measure to see if the scrollbar + // spaces were accounted for properly. If not, re-layout. + if (Ext.isIE) { + if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) { + var ts = this.getLayoutTargetSize(); + if (ts.width != size.width){ + this.adjustmentPass = true; + this.onLayout(ct, target); + } + } } + delete this.adjustmentPass; } -}; -Ext.layout.BorderLayout.Region.prototype = { - /** - * @cfg {Boolean} animFloat - * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated - * panel that will close again once the user mouses out of that panel (or clicks out if - * {@link #autoHide} = false). Setting {@link #animFloat} = false will - * prevent the open and close of these floated panels from being animated (defaults to true). - */ - /** - * @cfg {Boolean} autoHide - * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated - * panel. If autoHide = true, the panel will automatically hide after the user mouses - * out of the panel. If autoHide = false, the panel will continue to display until the - * user clicks outside of the panel (defaults to true). - */ /** - * @cfg {String} collapseMode - * collapseMode supports two configuration values:
    - *
  • undefined (default)
    By default, {@link #collapsible} - * regions are collapsed by clicking the expand/collapse tool button that renders into the region's - * title bar.
  • - *
  • 'mini'
    Optionally, when collapseMode is set to - * 'mini' the region's split bar will also display a small collapse button in the center of - * the bar. In 'mini' mode the region will collapse to a thinner bar than in normal mode. - *
  • - *

- *

Note: if a collapsible region does not have a title bar, then set collapseMode = - * 'mini' and {@link #split} = true in order for the region to be {@link #collapsible} - * by the user as the expand/collapse tool button (that would go in the title bar) will not be rendered.

- *

See also {@link #cmargins}.

+ * @property activeItem + * @hide */ - /** - * @cfg {Object} margins - * An object containing margins to apply to the region when in the expanded state in the - * format:

-{
-    top: (top margin),
-    right: (right margin),
-    bottom: (bottom margin),
-    left: (left margin)
-}
- *

May also be a string containing space-separated, numeric margin values. The order of the - * sides associated with each value matches the way CSS processes margin values:

- *

    - *
  • If there is only one value, it applies to all sides.
  • - *
  • If there are two values, the top and bottom borders are set to the first value and the - * right and left are set to the second.
  • - *
  • If there are three values, the top is set to the first value, the left and right are set - * to the second, and the bottom is set to the third.
  • - *
  • If there are four values, they apply to the top, right, bottom, and left, respectively.
  • - *

- *

Defaults to:


-     * {top:0, right:0, bottom:0, left:0}
-     * 
- */ - /** - * @cfg {Object} cmargins - * An object containing margins to apply to the region when in the collapsed state in the - * format:

-{
-    top: (top margin),
-    right: (right margin),
-    bottom: (bottom margin),
-    left: (left margin)
-}
- *

May also be a string containing space-separated, numeric margin values. The order of the - * sides associated with each value matches the way CSS processes margin values.

- *

    - *
  • If there is only one value, it applies to all sides.
  • - *
  • If there are two values, the top and bottom borders are set to the first value and the - * right and left are set to the second.
  • - *
  • If there are three values, the top is set to the first value, the left and right are set - * to the second, and the bottom is set to the third.
  • - *
  • If there are four values, they apply to the top, right, bottom, and left, respectively.
  • - *

- */ - /** - * @cfg {Boolean} collapsible - *

true to allow the user to collapse this region (defaults to false). If - * true, an expand/collapse tool button will automatically be rendered into the title - * bar of the region, otherwise the button will not be shown.

- *

Note: that a title bar is required to display the collapse/expand toggle button -- if - * no title is specified for the region's panel, the region will only be collapsible if - * {@link #collapseMode} = 'mini' and {@link #split} = true. - */ - collapsible : false, - /** - * @cfg {Boolean} split - *

true to create a {@link Ext.layout.BorderLayout.SplitRegion SplitRegion} and - * display a 5px wide {@link Ext.SplitBar} between this region and its neighbor, allowing the user to - * resize the regions dynamically. Defaults to false creating a - * {@link Ext.layout.BorderLayout.Region Region}.


- *

Notes:

    - *
  • this configuration option is ignored if region='center'
  • - *
  • when split == true, it is common to specify a - * {@link Ext.SplitBar#minSize minSize} and {@link Ext.SplitBar#maxSize maxSize} - * for the {@link Ext.BoxComponent BoxComponent} representing the region. These are not native - * configs of {@link Ext.BoxComponent BoxComponent}, and are used only by this class.
  • - *
  • if {@link #collapseMode} = 'mini' requires split = true to reserve space - * for the collapse tool
  • - *
- */ - split:false, - /** - * @cfg {Boolean} floatable - * true to allow clicking a collapsed region's bar to display the region's panel floated - * above the layout, false to force the user to fully expand a collapsed region by - * clicking the expand button to see it again (defaults to true). - */ - floatable: true, - /** - * @cfg {Number} minWidth - *

The minimum allowable width in pixels for this region (defaults to 50). - * maxWidth may also be specified.


- *

Note: setting the {@link Ext.SplitBar#minSize minSize} / - * {@link Ext.SplitBar#maxSize maxSize} supersedes any specified - * minWidth / maxWidth.

- */ - minWidth:50, - /** - * @cfg {Number} minHeight - * The minimum allowable height in pixels for this region (defaults to 50) - * maxHeight may also be specified.


- *

Note: setting the {@link Ext.SplitBar#minSize minSize} / - * {@link Ext.SplitBar#maxSize maxSize} supersedes any specified - * minHeight / maxHeight.

- */ - minHeight:50, +}); +Ext.Container.LAYOUTS['column'] = Ext.layout.ColumnLayout; +/** + * @class Ext.layout.BorderLayout + * @extends Ext.layout.ContainerLayout + *

This is a multi-pane, application-oriented UI layout style that supports multiple + * nested panels, automatic {@link Ext.layout.BorderLayout.Region#split split} bars between + * {@link Ext.layout.BorderLayout.Region#BorderLayout.Region regions} and built-in + * {@link Ext.layout.BorderLayout.Region#collapsible expanding and collapsing} of regions.

+ *

This class is intended to be extended or created via the layout:'border' + * {@link Ext.Container#layout} config, and should generally not need to be created directly + * via the new keyword.

+ *

BorderLayout does not have any direct config options (other than inherited ones). + * All configuration options available for customizing the BorderLayout are at the + * {@link Ext.layout.BorderLayout.Region} and {@link Ext.layout.BorderLayout.SplitRegion} + * levels.

+ *

Example usage:

+ *

+var myBorderPanel = new Ext.Panel({
+    {@link Ext.Component#renderTo renderTo}: document.body,
+    {@link Ext.BoxComponent#width width}: 700,
+    {@link Ext.BoxComponent#height height}: 500,
+    {@link Ext.Panel#title title}: 'Border Layout',
+    {@link Ext.Container#layout layout}: 'border',
+    {@link Ext.Container#items items}: [{
+        {@link Ext.Panel#title title}: 'South Region is resizable',
+        {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'south',     // position for region
+        {@link Ext.BoxComponent#height height}: 100,
+        {@link Ext.layout.BorderLayout.Region#split split}: true,         // enable resizing
+        {@link Ext.SplitBar#minSize minSize}: 75,         // defaults to {@link Ext.layout.BorderLayout.Region#minHeight 50}
+        {@link Ext.SplitBar#maxSize maxSize}: 150,
+        {@link Ext.layout.BorderLayout.Region#margins margins}: '0 5 5 5'
+    },{
+        // xtype: 'panel' implied by default
+        {@link Ext.Panel#title title}: 'West Region is collapsible',
+        {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}:'west',
+        {@link Ext.layout.BorderLayout.Region#margins margins}: '5 0 0 5',
+        {@link Ext.BoxComponent#width width}: 200,
+        {@link Ext.layout.BorderLayout.Region#collapsible collapsible}: true,   // make collapsible
+        {@link Ext.layout.BorderLayout.Region#cmargins cmargins}: '5 5 0 5', // adjust top margin when collapsed
+        {@link Ext.Component#id id}: 'west-region-container',
+        {@link Ext.Container#layout layout}: 'fit',
+        {@link Ext.Panel#unstyled unstyled}: true
+    },{
+        {@link Ext.Panel#title title}: 'Center Region',
+        {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'center',     // center region is required, no width/height specified
+        {@link Ext.Component#xtype xtype}: 'container',
+        {@link Ext.Container#layout layout}: 'fit',
+        {@link Ext.layout.BorderLayout.Region#margins margins}: '5 5 0 0'
+    }]
+});
+
+ *

Notes:

    + *
  • Any container using the BorderLayout must have a child item with region:'center'. + * The child item in the center region will always be resized to fill the remaining space not used by + * the other regions in the layout.
  • + *
  • Any child items with a region of west or east must have width defined + * (an integer representing the number of pixels that the region should take up).
  • + *
  • Any child items with a region of north or south must have height defined.
  • + *
  • The regions of a BorderLayout are fixed at render time and thereafter, its child Components may not be removed or added. To add/remove + * Components within a BorderLayout, have them wrapped by an additional Container which is directly + * managed by the BorderLayout. If the region is to be collapsible, the Container used directly + * by the BorderLayout manager should be a Panel. In the following example a Container (an Ext.Panel) + * is added to the west region: + *
    
    +wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
    +wrc.{@link Ext.Panel#removeAll removeAll}();
    +wrc.{@link Ext.Container#add add}({
    +    title: 'Added Panel',
    +    html: 'Some content'
    +});
    +wrc.{@link Ext.Container#doLayout doLayout}();
    + * 
    + *
  • + *
  • To reference a {@link Ext.layout.BorderLayout.Region Region}: + *
    
    +wr = myBorderPanel.layout.west;
    + * 
    + *
  • + *
+ */ +Ext.layout.BorderLayout = Ext.extend(Ext.layout.ContainerLayout, { // private - defaultMargins : {left:0,top:0,right:0,bottom:0}, - // private - defaultNSCMargins : {left:5,top:5,right:5,bottom:5}, + monitorResize:true, // private - defaultEWCMargins : {left:5,top:0,right:5,bottom:0}, - floatingZIndex: 100, - - /** - * True if this region is collapsed. Read-only. - * @type Boolean - * @property - */ - isCollapsed : false, - - /** - * This region's panel. Read-only. - * @type Ext.Panel - * @property panel - */ - /** - * This region's layout. Read-only. - * @type Layout - * @property layout - */ - /** - * This region's layout position (north, south, east, west or center). Read-only. - * @type String - * @property position - */ + rendered : false, - // private - render : function(ct, p){ - this.panel = p; - p.el.enableDisplayMode(); - this.targetEl = ct; - this.el = p.el; + type: 'border', - var gs = p.getState, ps = this.position; - p.getState = function(){ - return Ext.apply(gs.call(p) || {}, this.state); - }.createDelegate(this); + targetCls: 'x-border-layout-ct', - if(ps != 'center'){ - p.allowQueuedExpand = false; - p.on({ - beforecollapse: this.beforeCollapse, - collapse: this.onCollapse, - beforeexpand: this.beforeExpand, - expand: this.onExpand, - hide: this.onHide, - show: this.onShow, - scope: this - }); - if(this.collapsible || this.floatable){ - p.collapseEl = 'el'; - p.slideAnchor = this.getSlideAnchor(); - } - if(p.tools && p.tools.toggle){ - p.tools.toggle.addClass('x-tool-collapse-'+ps); - p.tools.toggle.addClassOnOver('x-tool-collapse-'+ps+'-over'); - } - } + getLayoutTargetSize : function() { + var target = this.container.getLayoutTarget(); + return target ? target.getViewSize() : {}; }, // private - getCollapsedEl : function(){ - if(!this.collapsedEl){ - if(!this.toolTemplate){ - var tt = new Ext.Template( - '
 
' - ); - tt.disableFormats = true; - tt.compile(); - Ext.layout.BorderLayout.Region.prototype.toolTemplate = tt; + onLayout : function(ct, target){ + var collapsed, i, c, pos, items = ct.items.items, len = items.length; + if(!this.rendered){ + collapsed = []; + for(i = 0; i < len; i++) { + c = items[i]; + pos = c.region; + if(c.collapsed){ + collapsed.push(c); + } + c.collapsed = false; + if(!c.rendered){ + c.render(target, i); + c.getPositionEl().addClass('x-border-panel'); + } + this[pos] = pos != 'center' && c.split ? + new Ext.layout.BorderLayout.SplitRegion(this, c.initialConfig, pos) : + new Ext.layout.BorderLayout.Region(this, c.initialConfig, pos); + this[pos].render(target, c); } - this.collapsedEl = this.targetEl.createChild({ - cls: "x-layout-collapsed x-layout-collapsed-"+this.position, - id: this.panel.id + '-xcollapsed' - }); - this.collapsedEl.enableDisplayMode('block'); + this.rendered = true; + } - if(this.collapseMode == 'mini'){ - this.collapsedEl.addClass('x-layout-cmini-'+this.position); - this.miniCollapsedEl = this.collapsedEl.createChild({ - cls: "x-layout-mini x-layout-mini-"+this.position, html: " " - }); - this.miniCollapsedEl.addClassOnOver('x-layout-mini-over'); - this.collapsedEl.addClassOnOver("x-layout-collapsed-over"); - this.collapsedEl.on('click', this.onExpandClick, this, {stopEvent:true}); - }else { - if(this.collapsible !== false && !this.hideCollapseTool) { - var t = this.toolTemplate.append( - this.collapsedEl.dom, - {id:'expand-'+this.position}, true); - t.addClassOnOver('x-tool-expand-'+this.position+'-over'); - t.on('click', this.onExpandClick, this, {stopEvent:true}); - } - if(this.floatable !== false || this.titleCollapse){ - this.collapsedEl.addClassOnOver("x-layout-collapsed-over"); - this.collapsedEl.on("click", this[this.floatable ? 'collapseClick' : 'onExpandClick'], this); - } + var size = this.getLayoutTargetSize(); + if(size.width < 20 || size.height < 20){ // display none? + if(collapsed){ + this.restoreCollapsed = collapsed; } + return; + }else if(this.restoreCollapsed){ + collapsed = this.restoreCollapsed; + delete this.restoreCollapsed; } - return this.collapsedEl; - }, - // private - onExpandClick : function(e){ - if(this.isSlid){ - this.afterSlideIn(); - this.panel.expand(false); - }else{ - this.panel.expand(); + var w = size.width, h = size.height, + centerW = w, centerH = h, centerY = 0, centerX = 0, + n = this.north, s = this.south, west = this.west, e = this.east, c = this.center, + b, m, totalWidth, totalHeight; + if(!c && Ext.layout.BorderLayout.WARN !== false){ + throw 'No center region defined in BorderLayout ' + ct.id; } - }, - - // private - onCollapseClick : function(e){ - this.panel.collapse(); - }, - // private - beforeCollapse : function(p, animate){ - this.lastAnim = animate; - if(this.splitEl){ - this.splitEl.hide(); + if(n && n.isVisible()){ + b = n.getSize(); + m = n.getMargins(); + b.width = w - (m.left+m.right); + b.x = m.left; + b.y = m.top; + centerY = b.height + b.y + m.bottom; + centerH -= centerY; + n.applyLayout(b); } - this.getCollapsedEl().show(); - this.panel.el.setStyle('z-index', 100); - this.isCollapsed = true; - this.layout.layout(); - }, - - // private - onCollapse : function(animate){ - this.panel.el.setStyle('z-index', 1); - if(this.lastAnim === false || this.panel.animCollapse === false){ - this.getCollapsedEl().dom.style.visibility = 'visible'; - }else{ - this.getCollapsedEl().slideIn(this.panel.slideAnchor, {duration:.2}); + if(s && s.isVisible()){ + b = s.getSize(); + m = s.getMargins(); + b.width = w - (m.left+m.right); + b.x = m.left; + totalHeight = (b.height + m.top + m.bottom); + b.y = h - totalHeight + m.top; + centerH -= totalHeight; + s.applyLayout(b); } - this.state.collapsed = true; - this.panel.saveState(); - }, - - // private - beforeExpand : function(animate){ - var c = this.getCollapsedEl(); - this.el.show(); - if(this.position == 'east' || this.position == 'west'){ - this.panel.setSize(undefined, c.getHeight()); - }else{ - this.panel.setSize(c.getWidth(), undefined); + if(west && west.isVisible()){ + b = west.getSize(); + m = west.getMargins(); + b.height = centerH - (m.top+m.bottom); + b.x = m.left; + b.y = centerY + m.top; + totalWidth = (b.width + m.left + m.right); + centerX += totalWidth; + centerW -= totalWidth; + west.applyLayout(b); } - c.hide(); - c.dom.style.visibility = 'hidden'; - this.panel.el.setStyle('z-index', this.floatingZIndex); - }, - - // private - onExpand : function(){ - this.isCollapsed = false; - if(this.splitEl){ - this.splitEl.show(); + if(e && e.isVisible()){ + b = e.getSize(); + m = e.getMargins(); + b.height = centerH - (m.top+m.bottom); + totalWidth = (b.width + m.left + m.right); + b.x = w - totalWidth + m.left; + b.y = centerY + m.top; + centerW -= totalWidth; + e.applyLayout(b); } - this.layout.layout(); - this.panel.el.setStyle('z-index', 1); - this.state.collapsed = false; - this.panel.saveState(); - }, - - // private - collapseClick : function(e){ - if(this.isSlid){ - e.stopPropagation(); - this.slideIn(); - }else{ - e.stopPropagation(); - this.slideOut(); + if(c){ + m = c.getMargins(); + var centerBox = { + x: centerX + m.left, + y: centerY + m.top, + width: centerW - (m.left+m.right), + height: centerH - (m.top+m.bottom) + }; + c.applyLayout(centerBox); } - }, - - // private - onHide : function(){ - if(this.isCollapsed){ - this.getCollapsedEl().hide(); - }else if(this.splitEl){ - this.splitEl.hide(); + if(collapsed){ + for(i = 0, len = collapsed.length; i < len; i++){ + collapsed[i].collapse(false); + } + } + if(Ext.isIE && Ext.isStrict){ // workaround IE strict repainting issue + target.repaint(); + } + // Putting a border layout into an overflowed container is NOT correct and will make a second layout pass necessary. + if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) { + var ts = this.getLayoutTargetSize(); + if (ts.width != size.width || ts.height != size.height){ + this.adjustmentPass = true; + this.onLayout(ct, target); + } } + delete this.adjustmentPass; }, - // private - onShow : function(){ - if(this.isCollapsed){ - this.getCollapsedEl().show(); - }else if(this.splitEl){ - this.splitEl.show(); + destroy: function() { + var r = ['north', 'south', 'east', 'west'], i, region; + for (i = 0; i < r.length; i++) { + region = this[r[i]]; + if(region){ + if(region.destroy){ + region.destroy(); + }else if (region.split){ + region.split.destroy(true); + } + } } - }, + Ext.layout.BorderLayout.superclass.destroy.call(this); + } /** - * True if this region is currently visible, else false. - * @return {Boolean} + * @property activeItem + * @hide */ - isVisible : function(){ - return !this.panel.hidden; - }, +}); - /** - * Returns the current margins for this region. If the region is collapsed, the - * {@link #cmargins} (collapsed margins) value will be returned, otherwise the - * {@link #margins} value will be returned. - * @return {Object} An object containing the element's margins: {left: (left - * margin), top: (top margin), right: (right margin), bottom: (bottom margin)} - */ - getMargins : function(){ - return this.isCollapsed && this.cmargins ? this.cmargins : this.margins; - }, +/** + * @class Ext.layout.BorderLayout.Region + *

This is a region of a {@link Ext.layout.BorderLayout BorderLayout} that acts as a subcontainer + * within the layout. Each region has its own {@link Ext.layout.ContainerLayout layout} that is + * independent of other regions and the containing BorderLayout, and can be any of the + * {@link Ext.layout.ContainerLayout valid Ext layout types}.

+ *

Region size is managed automatically and cannot be changed by the user -- for + * {@link #split resizable regions}, see {@link Ext.layout.BorderLayout.SplitRegion}.

+ * @constructor + * Create a new Region. + * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region. + * @param {Object} config The configuration options + * @param {String} position The region position. Valid values are: north, south, + * east, west and center. Every {@link Ext.layout.BorderLayout BorderLayout} + * must have a center region for the primary content -- all other regions are optional. + */ +Ext.layout.BorderLayout.Region = function(layout, config, pos){ + Ext.apply(this, config); + this.layout = layout; + this.position = pos; + this.state = {}; + if(typeof this.margins == 'string'){ + this.margins = this.layout.parseMargins(this.margins); + } + this.margins = Ext.applyIf(this.margins || {}, this.defaultMargins); + if(this.collapsible){ + if(typeof this.cmargins == 'string'){ + this.cmargins = this.layout.parseMargins(this.cmargins); + } + if(this.collapseMode == 'mini' && !this.cmargins){ + this.cmargins = {left:0,top:0,right:0,bottom:0}; + }else{ + this.cmargins = Ext.applyIf(this.cmargins || {}, + pos == 'north' || pos == 'south' ? this.defaultNSCMargins : this.defaultEWCMargins); + } + } +}; +Ext.layout.BorderLayout.Region.prototype = { /** - * Returns the current size of this region. If the region is collapsed, the size of the - * collapsedEl will be returned, otherwise the size of the region's panel will be returned. - * @return {Object} An object containing the element's size: {width: (element width), - * height: (element height)} + * @cfg {Boolean} animFloat + * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated + * panel that will close again once the user mouses out of that panel (or clicks out if + * {@link #autoHide} = false). Setting {@link #animFloat} = false will + * prevent the open and close of these floated panels from being animated (defaults to true). */ - getSize : function(){ - return this.isCollapsed ? this.getCollapsedEl().getSize() : this.panel.getSize(); - }, - /** - * Sets the specified panel as the container element for this region. - * @param {Ext.Panel} panel The new panel + * @cfg {Boolean} autoHide + * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated + * panel. If autoHide = true, the panel will automatically hide after the user mouses + * out of the panel. If autoHide = false, the panel will continue to display until the + * user clicks outside of the panel (defaults to true). */ - setPanel : function(panel){ - this.panel = panel; - }, - /** - * Returns the minimum allowable width for this region. - * @return {Number} The minimum width + * @cfg {String} collapseMode + * collapseMode supports two configuration values:
    + *
  • undefined (default)
    By default, {@link #collapsible} + * regions are collapsed by clicking the expand/collapse tool button that renders into the region's + * title bar.
  • + *
  • 'mini'
    Optionally, when collapseMode is set to + * 'mini' the region's split bar will also display a small collapse button in the center of + * the bar. In 'mini' mode the region will collapse to a thinner bar than in normal mode. + *
  • + *

+ *

Note: if a collapsible region does not have a title bar, then set collapseMode = + * 'mini' and {@link #split} = true in order for the region to be {@link #collapsible} + * by the user as the expand/collapse tool button (that would go in the title bar) will not be rendered.

+ *

See also {@link #cmargins}.

*/ - getMinWidth: function(){ - return this.minWidth; - }, + /** + * @cfg {Object} margins + * An object containing margins to apply to the region when in the expanded state in the + * format:

+{
+    top: (top margin),
+    right: (right margin),
+    bottom: (bottom margin),
+    left: (left margin)
+}
+ *

May also be a string containing space-separated, numeric margin values. The order of the + * sides associated with each value matches the way CSS processes margin values:

+ *

    + *
  • If there is only one value, it applies to all sides.
  • + *
  • If there are two values, the top and bottom borders are set to the first value and the + * right and left are set to the second.
  • + *
  • If there are three values, the top is set to the first value, the left and right are set + * to the second, and the bottom is set to the third.
  • + *
  • If there are four values, they apply to the top, right, bottom, and left, respectively.
  • + *

+ *

Defaults to:


+     * {top:0, right:0, bottom:0, left:0}
+     * 
+ */ + /** + * @cfg {Object} cmargins + * An object containing margins to apply to the region when in the collapsed state in the + * format:

+{
+    top: (top margin),
+    right: (right margin),
+    bottom: (bottom margin),
+    left: (left margin)
+}
+ *

May also be a string containing space-separated, numeric margin values. The order of the + * sides associated with each value matches the way CSS processes margin values.

+ *

    + *
  • If there is only one value, it applies to all sides.
  • + *
  • If there are two values, the top and bottom borders are set to the first value and the + * right and left are set to the second.
  • + *
  • If there are three values, the top is set to the first value, the left and right are set + * to the second, and the bottom is set to the third.
  • + *
  • If there are four values, they apply to the top, right, bottom, and left, respectively.
  • + *

+ */ + /** + * @cfg {Boolean} collapsible + *

true to allow the user to collapse this region (defaults to false). If + * true, an expand/collapse tool button will automatically be rendered into the title + * bar of the region, otherwise the button will not be shown.

+ *

Note: that a title bar is required to display the collapse/expand toggle button -- if + * no title is specified for the region's panel, the region will only be collapsible if + * {@link #collapseMode} = 'mini' and {@link #split} = true. + */ + collapsible : false, + /** + * @cfg {Boolean} split + *

true to create a {@link Ext.layout.BorderLayout.SplitRegion SplitRegion} and + * display a 5px wide {@link Ext.SplitBar} between this region and its neighbor, allowing the user to + * resize the regions dynamically. Defaults to false creating a + * {@link Ext.layout.BorderLayout.Region Region}.


+ *

Notes:

    + *
  • this configuration option is ignored if region='center'
  • + *
  • when split == true, it is common to specify a + * {@link Ext.SplitBar#minSize minSize} and {@link Ext.SplitBar#maxSize maxSize} + * for the {@link Ext.BoxComponent BoxComponent} representing the region. These are not native + * configs of {@link Ext.BoxComponent BoxComponent}, and are used only by this class.
  • + *
  • if {@link #collapseMode} = 'mini' requires split = true to reserve space + * for the collapse tool
  • + *
+ */ + split:false, + /** + * @cfg {Boolean} floatable + * true to allow clicking a collapsed region's bar to display the region's panel floated + * above the layout, false to force the user to fully expand a collapsed region by + * clicking the expand button to see it again (defaults to true). + */ + floatable: true, + /** + * @cfg {Number} minWidth + *

The minimum allowable width in pixels for this region (defaults to 50). + * maxWidth may also be specified.


+ *

Note: setting the {@link Ext.SplitBar#minSize minSize} / + * {@link Ext.SplitBar#maxSize maxSize} supersedes any specified + * minWidth / maxWidth.

+ */ + minWidth:50, + /** + * @cfg {Number} minHeight + * The minimum allowable height in pixels for this region (defaults to 50) + * maxHeight may also be specified.


+ *

Note: setting the {@link Ext.SplitBar#minSize minSize} / + * {@link Ext.SplitBar#maxSize maxSize} supersedes any specified + * minHeight / maxHeight.

+ */ + minHeight:50, + + // private + defaultMargins : {left:0,top:0,right:0,bottom:0}, + // private + defaultNSCMargins : {left:5,top:5,right:5,bottom:5}, + // private + defaultEWCMargins : {left:5,top:0,right:5,bottom:0}, + floatingZIndex: 100, /** - * Returns the minimum allowable height for this region. - * @return {Number} The minimum height + * True if this region is collapsed. Read-only. + * @type Boolean + * @property + */ + isCollapsed : false, + + /** + * This region's panel. Read-only. + * @type Ext.Panel + * @property panel + */ + /** + * This region's layout. Read-only. + * @type Layout + * @property layout + */ + /** + * This region's layout position (north, south, east, west or center). Read-only. + * @type String + * @property position */ - getMinHeight: function(){ - return this.minHeight; - }, // private - applyLayoutCollapsed : function(box){ - var ce = this.getCollapsedEl(); - ce.setLeftTop(box.x, box.y); - ce.setSize(box.width, box.height); + render : function(ct, p){ + this.panel = p; + p.el.enableDisplayMode(); + this.targetEl = ct; + this.el = p.el; + + var gs = p.getState, ps = this.position; + p.getState = function(){ + return Ext.apply(gs.call(p) || {}, this.state); + }.createDelegate(this); + + if(ps != 'center'){ + p.allowQueuedExpand = false; + p.on({ + beforecollapse: this.beforeCollapse, + collapse: this.onCollapse, + beforeexpand: this.beforeExpand, + expand: this.onExpand, + hide: this.onHide, + show: this.onShow, + scope: this + }); + if(this.collapsible || this.floatable){ + p.collapseEl = 'el'; + p.slideAnchor = this.getSlideAnchor(); + } + if(p.tools && p.tools.toggle){ + p.tools.toggle.addClass('x-tool-collapse-'+ps); + p.tools.toggle.addClassOnOver('x-tool-collapse-'+ps+'-over'); + } + } }, // private - applyLayout : function(box){ - if(this.isCollapsed){ - this.applyLayoutCollapsed(box); - }else{ - this.panel.setPosition(box.x, box.y); - this.panel.setSize(box.width, box.height); + getCollapsedEl : function(){ + if(!this.collapsedEl){ + if(!this.toolTemplate){ + var tt = new Ext.Template( + '
 
' + ); + tt.disableFormats = true; + tt.compile(); + Ext.layout.BorderLayout.Region.prototype.toolTemplate = tt; + } + this.collapsedEl = this.targetEl.createChild({ + cls: "x-layout-collapsed x-layout-collapsed-"+this.position, + id: this.panel.id + '-xcollapsed' + }); + this.collapsedEl.enableDisplayMode('block'); + + if(this.collapseMode == 'mini'){ + this.collapsedEl.addClass('x-layout-cmini-'+this.position); + this.miniCollapsedEl = this.collapsedEl.createChild({ + cls: "x-layout-mini x-layout-mini-"+this.position, html: " " + }); + this.miniCollapsedEl.addClassOnOver('x-layout-mini-over'); + this.collapsedEl.addClassOnOver("x-layout-collapsed-over"); + this.collapsedEl.on('click', this.onExpandClick, this, {stopEvent:true}); + }else { + if(this.collapsible !== false && !this.hideCollapseTool) { + var t = this.expandToolEl = this.toolTemplate.append( + this.collapsedEl.dom, + {id:'expand-'+this.position}, true); + t.addClassOnOver('x-tool-expand-'+this.position+'-over'); + t.on('click', this.onExpandClick, this, {stopEvent:true}); + } + if(this.floatable !== false || this.titleCollapse){ + this.collapsedEl.addClassOnOver("x-layout-collapsed-over"); + this.collapsedEl.on("click", this[this.floatable ? 'collapseClick' : 'onExpandClick'], this); + } + } } + return this.collapsedEl; }, // private - beforeSlide: function(){ - this.panel.beforeEffect(); + onExpandClick : function(e){ + if(this.isSlid){ + this.panel.expand(false); + }else{ + this.panel.expand(); + } }, // private - afterSlide : function(){ - this.panel.afterEffect(); + onCollapseClick : function(e){ + this.panel.collapse(); }, // private - initAutoHide : function(){ - if(this.autoHide !== false){ - if(!this.autoHideHd){ - var st = new Ext.util.DelayedTask(this.slideIn, this); - this.autoHideHd = { - "mouseout": function(e){ - if(!e.within(this.el, true)){ - st.delay(500); - } - }, - "mouseover" : function(e){ - st.cancel(); - }, - scope : this - }; - } - this.el.on(this.autoHideHd); + beforeCollapse : function(p, animate){ + this.lastAnim = animate; + if(this.splitEl){ + this.splitEl.hide(); } + this.getCollapsedEl().show(); + var el = this.panel.getEl(); + this.originalZIndex = el.getStyle('z-index'); + el.setStyle('z-index', 100); + this.isCollapsed = true; + this.layout.layout(); }, // private - clearAutoHide : function(){ - if(this.autoHide !== false){ - this.el.un("mouseout", this.autoHideHd.mouseout); - this.el.un("mouseover", this.autoHideHd.mouseover); + onCollapse : function(animate){ + this.panel.el.setStyle('z-index', 1); + if(this.lastAnim === false || this.panel.animCollapse === false){ + this.getCollapsedEl().dom.style.visibility = 'visible'; + }else{ + this.getCollapsedEl().slideIn(this.panel.slideAnchor, {duration:.2}); } + this.state.collapsed = true; + this.panel.saveState(); }, // private - clearMonitor : function(){ - Ext.getDoc().un("click", this.slideInIf, this); - }, - - /** - * If this Region is {@link #floatable}, this method slides this Region into full visibility over the top - * of the center Region where it floats until either {@link #slideIn} is called, or other regions of the layout + beforeExpand : function(animate){ + if(this.isSlid){ + this.afterSlideIn(); + } + var c = this.getCollapsedEl(); + this.el.show(); + if(this.position == 'east' || this.position == 'west'){ + this.panel.setSize(undefined, c.getHeight()); + }else{ + this.panel.setSize(c.getWidth(), undefined); + } + c.hide(); + c.dom.style.visibility = 'hidden'; + this.panel.el.setStyle('z-index', this.floatingZIndex); + }, + + // private + onExpand : function(){ + this.isCollapsed = false; + if(this.splitEl){ + this.splitEl.show(); + } + this.layout.layout(); + this.panel.el.setStyle('z-index', this.originalZIndex); + this.state.collapsed = false; + this.panel.saveState(); + }, + + // private + collapseClick : function(e){ + if(this.isSlid){ + e.stopPropagation(); + this.slideIn(); + }else{ + e.stopPropagation(); + this.slideOut(); + } + }, + + // private + onHide : function(){ + if(this.isCollapsed){ + this.getCollapsedEl().hide(); + }else if(this.splitEl){ + this.splitEl.hide(); + } + }, + + // private + onShow : function(){ + if(this.isCollapsed){ + this.getCollapsedEl().show(); + }else if(this.splitEl){ + this.splitEl.show(); + } + }, + + /** + * True if this region is currently visible, else false. + * @return {Boolean} + */ + isVisible : function(){ + return !this.panel.hidden; + }, + + /** + * Returns the current margins for this region. If the region is collapsed, the + * {@link #cmargins} (collapsed margins) value will be returned, otherwise the + * {@link #margins} value will be returned. + * @return {Object} An object containing the element's margins: {left: (left + * margin), top: (top margin), right: (right margin), bottom: (bottom margin)} + */ + getMargins : function(){ + return this.isCollapsed && this.cmargins ? this.cmargins : this.margins; + }, + + /** + * Returns the current size of this region. If the region is collapsed, the size of the + * collapsedEl will be returned, otherwise the size of the region's panel will be returned. + * @return {Object} An object containing the element's size: {width: (element width), + * height: (element height)} + */ + getSize : function(){ + return this.isCollapsed ? this.getCollapsedEl().getSize() : this.panel.getSize(); + }, + + /** + * Sets the specified panel as the container element for this region. + * @param {Ext.Panel} panel The new panel + */ + setPanel : function(panel){ + this.panel = panel; + }, + + /** + * Returns the minimum allowable width for this region. + * @return {Number} The minimum width + */ + getMinWidth: function(){ + return this.minWidth; + }, + + /** + * Returns the minimum allowable height for this region. + * @return {Number} The minimum height + */ + getMinHeight: function(){ + return this.minHeight; + }, + + // private + applyLayoutCollapsed : function(box){ + var ce = this.getCollapsedEl(); + ce.setLeftTop(box.x, box.y); + ce.setSize(box.width, box.height); + }, + + // private + applyLayout : function(box){ + if(this.isCollapsed){ + this.applyLayoutCollapsed(box); + }else{ + this.panel.setPosition(box.x, box.y); + this.panel.setSize(box.width, box.height); + } + }, + + // private + beforeSlide: function(){ + this.panel.beforeEffect(); + }, + + // private + afterSlide : function(){ + this.panel.afterEffect(); + }, + + // private + initAutoHide : function(){ + if(this.autoHide !== false){ + if(!this.autoHideHd){ + this.autoHideSlideTask = new Ext.util.DelayedTask(this.slideIn, this); + this.autoHideHd = { + "mouseout": function(e){ + if(!e.within(this.el, true)){ + this.autoHideSlideTask.delay(500); + } + }, + "mouseover" : function(e){ + this.autoHideSlideTask.cancel(); + }, + scope : this + }; + } + this.el.on(this.autoHideHd); + this.collapsedEl.on(this.autoHideHd); + } + }, + + // private + clearAutoHide : function(){ + if(this.autoHide !== false){ + this.el.un("mouseout", this.autoHideHd.mouseout); + this.el.un("mouseover", this.autoHideHd.mouseover); + this.collapsedEl.un("mouseout", this.autoHideHd.mouseout); + this.collapsedEl.un("mouseover", this.autoHideHd.mouseover); + } + }, + + // private + clearMonitor : function(){ + Ext.getDoc().un("click", this.slideInIf, this); + }, + + /** + * If this Region is {@link #floatable}, this method slides this Region into full visibility over the top + * of the center Region where it floats until either {@link #slideIn} is called, or other regions of the layout * are clicked, or the mouse exits the Region. */ slideOut : function(){ @@ -5800,16 +6511,32 @@ Ext.layout.BorderLayout.Region.prototype = { return; } this.isSlid = true; - var ts = this.panel.tools; + var ts = this.panel.tools, dh, pc; if(ts && ts.toggle){ ts.toggle.hide(); } this.el.show(); + + // Temporarily clear the collapsed flag so we can onResize the panel on the slide + pc = this.panel.collapsed; + this.panel.collapsed = false; + if(this.position == 'east' || this.position == 'west'){ + // Temporarily clear the deferHeight flag so we can size the height on the slide + dh = this.panel.deferHeight; + this.panel.deferHeight = false; + this.panel.setSize(undefined, this.collapsedEl.getHeight()); + + // Put the deferHeight flag back after setSize + this.panel.deferHeight = dh; }else{ this.panel.setSize(this.collapsedEl.getWidth(), undefined); } + + // Put the collapsed flag back after onResize + this.panel.collapsed = pc; + this.restoreLT = [this.el.dom.style.left, this.el.dom.style.top]; this.el.alignTo(this.collapsedEl, this.getCollapseAnchor()); this.el.setStyle("z-index", this.floatingZIndex+2); @@ -5957,6 +6684,13 @@ Ext.layout.BorderLayout.Region.prototype = { return [0, cm.top+cm.bottom+c.getHeight()]; break; } + }, + + destroy : function(){ + if (this.autoHideSlideTask && this.autoHideSlideTask.cancel){ + this.autoHideSlideTask.cancel(); + } + Ext.destroyMembers(this, 'miniCollapsedEl', 'collapsedEl', 'expandToolEl'); } }; @@ -6189,15 +6923,13 @@ Ext.extend(Ext.layout.BorderLayout.SplitRegion, Ext.layout.BorderLayout.Region, // inherit docs destroy : function() { - Ext.destroy( - this.miniSplitEl, - this.split, - this.splitEl - ); + Ext.destroy(this.miniSplitEl, this.split, this.splitEl); + Ext.layout.BorderLayout.SplitRegion.superclass.destroy.call(this); } }); -Ext.Container.LAYOUTS['border'] = Ext.layout.BorderLayout;/** +Ext.Container.LAYOUTS['border'] = Ext.layout.BorderLayout; +/** * @class Ext.layout.FormLayout * @extends Ext.layout.AnchorLayout *

This layout manager is specifically designed for rendering and managing child Components of @@ -6308,6 +7040,35 @@ Ext.layout.FormLayout = Ext.extend(Ext.layout.AnchorLayout, { * @property labelStyle */ + /** + * @cfg {Boolean} trackLabels + * True to show/hide the field label when the field is hidden. Defaults to false. + */ + trackLabels: false, + + type: 'form', + + onRemove: function(c){ + Ext.layout.FormLayout.superclass.onRemove.call(this, c); + if(this.trackLabels){ + c.un('show', this.onFieldShow, this); + c.un('hide', this.onFieldHide, this); + } + // check for itemCt, since we may be removing a fieldset or something similar + var el = c.getPositionEl(), + ct = c.getItemCt && c.getItemCt(); + if (c.rendered && ct) { + if (el && el.dom) { + el.insertAfter(ct); + } + Ext.destroy(ct); + Ext.destroyMembers(c, 'label', 'itemCt'); + if (c.customItemCt) { + Ext.destroyMembers(c, 'getItemCt', 'customItemCt'); + } + } + }, + // private setContainer : function(ct){ Ext.layout.FormLayout.superclass.setContainer.call(this, ct); @@ -6316,26 +7077,50 @@ Ext.layout.FormLayout = Ext.extend(Ext.layout.AnchorLayout, { } if(ct.hideLabels){ - this.labelStyle = "display:none"; - this.elementStyle = "padding-left:0;"; - this.labelAdjust = 0; + Ext.apply(this, { + labelStyle: 'display:none', + elementStyle: 'padding-left:0;', + labelAdjust: 0 + }); }else{ - this.labelSeparator = ct.labelSeparator || this.labelSeparator; + this.labelSeparator = Ext.isDefined(ct.labelSeparator) ? ct.labelSeparator : this.labelSeparator; ct.labelWidth = ct.labelWidth || 100; - if(typeof ct.labelWidth == 'number'){ - var pad = (typeof ct.labelPad == 'number' ? ct.labelPad : 5); - this.labelAdjust = ct.labelWidth+pad; - this.labelStyle = "width:"+ct.labelWidth+"px;"; - this.elementStyle = "padding-left:"+(ct.labelWidth+pad)+'px'; + if(Ext.isNumber(ct.labelWidth)){ + var pad = Ext.isNumber(ct.labelPad) ? ct.labelPad : 5; + Ext.apply(this, { + labelAdjust: ct.labelWidth + pad, + labelStyle: 'width:' + ct.labelWidth + 'px;', + elementStyle: 'padding-left:' + (ct.labelWidth + pad) + 'px' + }); } if(ct.labelAlign == 'top'){ - this.labelStyle = "width:auto;"; - this.labelAdjust = 0; - this.elementStyle = "padding-left:0;"; + Ext.apply(this, { + labelStyle: 'width:auto;', + labelAdjust: 0, + elementStyle: 'padding-left:0;' + }); } } }, + // private + isHide: function(c){ + return c.hideLabel || this.container.hideLabels; + }, + + onFieldShow: function(c){ + c.getItemCt().removeClass('x-hide-' + c.hideMode); + + // Composite fields will need to layout after the container is made visible + if (c.isComposite) { + c.doLayout(); + } + }, + + onFieldHide: function(c){ + c.getItemCt().addClass('x-hide-' + c.hideMode); + }, + //private getLabelStyle: function(s){ var ls = '', items = [this.labelStyle, s]; @@ -6343,7 +7128,7 @@ Ext.layout.FormLayout = Ext.extend(Ext.layout.AnchorLayout, { if (items[i]){ ls += items[i]; if (ls.substr(-1, 1) != ';'){ - ls += ';' + ls += ';'; } } } @@ -6384,19 +7169,48 @@ new Ext.Template( *

Also see {@link #getTemplateArgs}

*/ - // private + /** + * @private + * + */ renderItem : function(c, position, target){ - if(c && !c.rendered && (c.isFormField || c.fieldLabel) && c.inputType != 'hidden'){ + if(c && (c.isFormField || c.fieldLabel) && c.inputType != 'hidden'){ var args = this.getTemplateArgs(c); - if(typeof position == 'number'){ + if(Ext.isNumber(position)){ position = target.dom.childNodes[position] || null; } if(position){ - this.fieldTpl.insertBefore(position, args); + c.itemCt = this.fieldTpl.insertBefore(position, args, true); }else{ - this.fieldTpl.append(target, args); + c.itemCt = this.fieldTpl.append(target, args, true); + } + if(!c.getItemCt){ + // Non form fields don't have getItemCt, apply it here + // This will get cleaned up in onRemove + Ext.apply(c, { + getItemCt: function(){ + return c.itemCt; + }, + customItemCt: true + }); + } + c.label = c.getItemCt().child('label.x-form-item-label'); + if(!c.rendered){ + c.render('x-form-el-' + c.id); + }else if(!this.isValidParent(c, target)){ + Ext.fly('x-form-el-' + c.id).appendChild(c.getPositionEl()); + } + if(this.trackLabels){ + if(c.hidden){ + this.onFieldHide(c); + } + c.on({ + scope: this, + show: this.onFieldShow, + hide: this.onFieldHide + }); } - c.render('x-form-el-'+c.id); + this.configureItem(c); }else { Ext.layout.FormLayout.superclass.renderItem.apply(this, arguments); } @@ -6416,7 +7230,7 @@ new Ext.Template( * A CSS style specification string to add to the field label for this field (defaults to '' or the * {@link #labelStyle layout's value for labelStyle}). *
  • label : String
    The text to display as the label for this - * field (defaults to '')
  • + * field (defaults to the field's configured fieldLabel property) *
  • {@link #labelSeparator} : String
    The separator to display after * the text of the label for this field (defaults to a colon ':' or the * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.
  • @@ -6424,30 +7238,42 @@ new Ext.Template( *
  • clearCls : String
    The CSS class to apply to the special clearing div * rendered directly after each form field wrapper (defaults to 'x-form-clear-left')
  • * - * @param field The {@link Field Ext.form.Field} being rendered. - * @return An object hash containing the properties required to render the Field. + * @param (Ext.form.Field} field The {@link Ext.form.Field Field} being rendered. + * @return {Object} An object hash containing the properties required to render the Field. */ getTemplateArgs: function(field) { var noLabelSep = !field.fieldLabel || field.hideLabel; + return { - id: field.id, - label: field.fieldLabel, - labelStyle: field.labelStyle||this.labelStyle||'', - elementStyle: this.elementStyle||'', - labelSeparator: noLabelSep ? '' : (typeof field.labelSeparator == 'undefined' ? this.labelSeparator : field.labelSeparator), - itemCls: (field.itemCls||this.container.itemCls||'') + (field.hideLabel ? ' x-hide-label' : ''), - clearCls: field.clearCls || 'x-form-clear-left' + id : field.id, + label : field.fieldLabel, + itemCls : (field.itemCls || this.container.itemCls || '') + (field.hideLabel ? ' x-hide-label' : ''), + clearCls : field.clearCls || 'x-form-clear-left', + labelStyle : this.getLabelStyle(field.labelStyle), + elementStyle : this.elementStyle || '', + labelSeparator: noLabelSep ? '' : (Ext.isDefined(field.labelSeparator) ? field.labelSeparator : this.labelSeparator) }; }, // private - adjustWidthAnchor : function(value, comp){ - return value - (comp.isFormField || comp.fieldLabel ? (comp.hideLabel ? 0 : this.labelAdjust) : 0); + adjustWidthAnchor: function(value, c){ + if(c.label && !this.isHide(c) && (this.container.labelAlign != 'top')){ + var adjust = Ext.isIE6 || (Ext.isIE && !Ext.isStrict); + return value - this.labelAdjust + (adjust ? -3 : 0); + } + return value; + }, + + adjustHeightAnchor : function(value, c){ + if(c.label && !this.isHide(c) && (this.container.labelAlign == 'top')){ + return value - c.label.getHeight(); + } + return value; }, // private isValidParent : function(c, target){ - return true; + return target && this.container.getEl().contains(c.getPositionEl()); } /** @@ -6456,4832 +7282,6328 @@ new Ext.Template( */ }); -Ext.Container.LAYOUTS['form'] = Ext.layout.FormLayout;/** - * @class Ext.layout.AccordionLayout - * @extends Ext.layout.FitLayout - *

    This is a layout that contains multiple panels in an expandable accordion style such that only - * one panel can be open at any given time. Each panel has built-in support for expanding and collapsing. - *

    This class is intended to be extended or created via the {@link Ext.Container#layout layout} - * configuration property. See {@link Ext.Container#layout} for additional details.

    - *

    Example usage:

    - *
    
    -var accordion = new Ext.Panel({
    -    title: 'Accordion Layout',
    -    layout:'accordion',
    -    defaults: {
    -        // applied to each contained panel
    -        bodyStyle: 'padding:15px'
    -    },
    -    layoutConfig: {
    -        // layout-specific configs go here
    -        titleCollapse: false,
    -        animate: true,
    -        activeOnTop: true
    -    },
    -    items: [{
    -        title: 'Panel 1',
    -        html: '<p>Panel content!</p>'
    -    },{
    -        title: 'Panel 2',
    -        html: '<p>Panel content!</p>'
    -    },{
    -        title: 'Panel 3',
    -        html: '<p>Panel content!</p>'
    -    }]
    -});
    -
    - */ -Ext.layout.AccordionLayout = Ext.extend(Ext.layout.FitLayout, { - /** - * @cfg {Boolean} fill - * True to adjust the active item's height to fill the available space in the container, false to use the - * item's current height, or auto height if not explicitly set (defaults to true). - */ - fill : true, - /** - * @cfg {Boolean} autoWidth - * True to set each contained item's width to 'auto', false to use the item's current width (defaults to true). - * Note that some components, in particular the {@link Ext.grid.GridPanel grid}, will not function properly within - * layouts if they have auto width, so in such cases this config should be set to false. - */ - autoWidth : true, - /** - * @cfg {Boolean} titleCollapse - * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow - * expand/collapse only when the toggle tool button is clicked (defaults to true). When set to false, - * {@link #hideCollapseTool} should be false also. - */ - titleCollapse : true, - /** - * @cfg {Boolean} hideCollapseTool - * True to hide the contained panels' collapse/expand toggle buttons, false to display them (defaults to false). - * When set to true, {@link #titleCollapse} should be true also. - */ - hideCollapseTool : false, - /** - * @cfg {Boolean} collapseFirst - * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools - * in the contained panels' title bars, false to render it last (defaults to false). - */ - collapseFirst : false, - /** - * @cfg {Boolean} animate - * True to slide the contained panels open and closed during expand/collapse using animation, false to open and - * close directly with no animation (defaults to false). Note: to defer to the specific config setting of each - * contained panel for this property, set this to undefined at the layout level. - */ - animate : false, - /** - * @cfg {Boolean} sequence - * Experimental. If animate is set to true, this will result in each animation running in sequence. - */ - sequence : false, - /** - * @cfg {Boolean} activeOnTop - * True to swap the position of each panel as it is expanded so that it becomes the first item in the container, - * false to keep the panels in the rendered order. This is NOT compatible with "animate:true" (defaults to false). - */ - activeOnTop : false, - - renderItem : function(c){ - if(this.animate === false){ - c.animCollapse = false; - } - c.collapsible = true; - if(this.autoWidth){ - c.autoWidth = true; - } - if(this.titleCollapse){ - c.titleCollapse = true; - } - if(this.hideCollapseTool){ - c.hideCollapseTool = true; - } - if(this.collapseFirst !== undefined){ - c.collapseFirst = this.collapseFirst; - } - if(!this.activeItem && !c.collapsed){ - this.activeItem = c; - }else if(this.activeItem && this.activeItem != c){ - c.collapsed = true; - } - Ext.layout.AccordionLayout.superclass.renderItem.apply(this, arguments); - c.header.addClass('x-accordion-hd'); - c.on('beforeexpand', this.beforeExpand, this); - }, - - // private - beforeExpand : function(p, anim){ - var ai = this.activeItem; - if(ai){ - if(this.sequence){ - delete this.activeItem; - if (!ai.collapsed){ - ai.collapse({callback:function(){ - p.expand(anim || true); - }, scope: this}); - return false; - } - }else{ - ai.collapse(this.animate); - } - } - this.activeItem = p; - if(this.activeOnTop){ - p.el.dom.parentNode.insertBefore(p.el.dom, p.el.dom.parentNode.firstChild); - } - this.layout(); - }, - - // private - setItemSize : function(item, size){ - if(this.fill && item){ - var hh = 0; - this.container.items.each(function(p){ - if(p != item){ - hh += p.header.getHeight(); - } - }); - size.height -= hh; - item.setSize(size); - } - }, - - /** - * Sets the active (expanded) item in the layout. - * @param {String/Number} item The string component id or numeric index of the item to activate - */ - setActiveItem : function(item){ - item = this.container.getComponent(item); - if(this.activeItem != item){ - if(item.rendered && item.collapsed){ - item.expand(); - }else{ - this.activeItem = item; - } - } - - } -}); -Ext.Container.LAYOUTS.accordion = Ext.layout.AccordionLayout; - -//backwards compat -Ext.layout.Accordion = Ext.layout.AccordionLayout;/** - * @class Ext.layout.TableLayout - * @extends Ext.layout.ContainerLayout - *

    This layout allows you to easily render content into an HTML table. The total number of columns can be - * specified, and rowspan and colspan can be used to create complex layouts within the table. - * This class is intended to be extended or created via the layout:'table' {@link Ext.Container#layout} config, - * and should generally not need to be created directly via the new keyword.

    - *

    Note that when creating a layout via config, the layout-specific config properties must be passed in via - * the {@link Ext.Container#layoutConfig} object which will then be applied internally to the layout. In the - * case of TableLayout, the only valid layout config property is {@link #columns}. However, the items added to a - * TableLayout can supply the following table-specific config properties:

    - *
      - *
    • rowspan Applied to the table cell containing the item.
    • - *
    • colspan Applied to the table cell containing the item.
    • - *
    • cellId An id applied to the table cell containing the item.
    • - *
    • cellCls A CSS class name added to the table cell containing the item.
    • - *
    - *

    The basic concept of building up a TableLayout is conceptually very similar to building up a standard - * HTML table. You simply add each panel (or "cell") that you want to include along with any span attributes - * specified as the special config properties of rowspan and colspan which work exactly like their HTML counterparts. - * Rather than explicitly creating and nesting rows and columns as you would in HTML, you simply specify the - * total column count in the layoutConfig and start adding panels in their natural order from left to right, - * top to bottom. The layout will automatically figure out, based on the column count, rowspans and colspans, - * how to position each panel within the table. Just like with HTML tables, your rowspans and colspans must add - * up correctly in your overall layout or you'll end up with missing and/or extra cells! Example usage:

    - *
    
    -// This code will generate a layout table that is 3 columns by 2 rows
    -// with some spanning included.  The basic layout will be:
    -// +--------+-----------------+
    -// |   A    |   B             |
    -// |        |--------+--------|
    -// |        |   C    |   D    |
    -// +--------+--------+--------+
    -var table = new Ext.Panel({
    -    title: 'Table Layout',
    -    layout:'table',
    -    defaults: {
    -        // applied to each contained panel
    -        bodyStyle:'padding:20px'
    -    },
    -    layoutConfig: {
    -        // The total column count must be specified here
    -        columns: 3
    -    },
    -    items: [{
    -        html: '<p>Cell A content</p>',
    -        rowspan: 2
    -    },{
    -        html: '<p>Cell B content</p>',
    -        colspan: 2
    -    },{
    -        html: '<p>Cell C content</p>',
    -        cellCls: 'highlight'
    -    },{
    -        html: '<p>Cell D content</p>'
    -    }]
    -});
    -
    - */ -Ext.layout.TableLayout = Ext.extend(Ext.layout.ContainerLayout, { - /** - * @cfg {Number} columns - * The total number of columns to create in the table for this layout. If not specified, all Components added to - * this layout will be rendered into a single row using one column per Component. - */ - - // private - monitorResize:false, - - /** - * @cfg {Object} tableAttrs - *

    An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification - * used to create the layout's <table> element. Example:

    
    -{
    -    xtype: 'panel',
    -    layout: 'table',
    -    layoutConfig: {
    -        tableAttrs: {
    -        	style: {
    -        		width: '100%'
    -        	}
    -        },
    -        columns: 3
    -    }
    -}
    - */ - tableAttrs:null, - - // private - setContainer : function(ct){ - Ext.layout.TableLayout.superclass.setContainer.call(this, ct); - - this.currentRow = 0; - this.currentColumn = 0; - this.cells = []; - }, - - // private - onLayout : function(ct, target){ - var cs = ct.items.items, len = cs.length, c, i; - - if(!this.table){ - target.addClass('x-table-layout-ct'); - - this.table = target.createChild( - Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true); - } - this.renderAll(ct, target); - }, - - // private - getRow : function(index){ - var row = this.table.tBodies[0].childNodes[index]; - if(!row){ - row = document.createElement('tr'); - this.table.tBodies[0].appendChild(row); - } - return row; - }, - - // private - getNextCell : function(c){ - var cell = this.getNextNonSpan(this.currentColumn, this.currentRow); - var curCol = this.currentColumn = cell[0], curRow = this.currentRow = cell[1]; - for(var rowIndex = curRow; rowIndex < curRow + (c.rowspan || 1); rowIndex++){ - if(!this.cells[rowIndex]){ - this.cells[rowIndex] = []; - } - for(var colIndex = curCol; colIndex < curCol + (c.colspan || 1); colIndex++){ - this.cells[rowIndex][colIndex] = true; - } - } - var td = document.createElement('td'); - if(c.cellId){ - td.id = c.cellId; - } - var cls = 'x-table-layout-cell'; - if(c.cellCls){ - cls += ' ' + c.cellCls; - } - td.className = cls; - if(c.colspan){ - td.colSpan = c.colspan; - } - if(c.rowspan){ - td.rowSpan = c.rowspan; - } - this.getRow(curRow).appendChild(td); - return td; - }, - - // private - getNextNonSpan: function(colIndex, rowIndex){ - var cols = this.columns; - while((cols && colIndex >= cols) || (this.cells[rowIndex] && this.cells[rowIndex][colIndex])) { - if(cols && colIndex >= cols){ - rowIndex++; - colIndex = 0; - }else{ - colIndex++; - } - } - return [colIndex, rowIndex]; - }, - - // private - renderItem : function(c, position, target){ - if(c && !c.rendered){ - c.render(this.getNextCell(c)); - if(this.extraCls){ - var t = c.getPositionEl ? c.getPositionEl() : c; - t.addClass(this.extraCls); - } - } - }, - - // private - isValidParent : function(c, target){ - return true; - } - - /** - * @property activeItem - * @hide - */ -}); - -Ext.Container.LAYOUTS['table'] = Ext.layout.TableLayout;/** - * @class Ext.layout.AbsoluteLayout - * @extends Ext.layout.AnchorLayout - *

    This is a layout that inherits the anchoring of {@link Ext.layout.AnchorLayout} and adds the - * ability for x/y positioning using the standard x and y component config options.

    - *

    This class is intended to be extended or created via the {@link Ext.Container#layout layout} - * configuration property. See {@link Ext.Container#layout} for additional details.

    - *

    Example usage:

    - *
    
    -var form = new Ext.form.FormPanel({
    -    title: 'Absolute Layout',
    -    layout:'absolute',
    -    layoutConfig: {
    -        // layout-specific configs go here
    -        extraCls: 'x-abs-layout-item',
    -    },
    -    baseCls: 'x-plain',
    -    url:'save-form.php',
    -    defaultType: 'textfield',
    -    items: [{
    -        x: 0,
    -        y: 5,
    -        xtype:'label',
    -        text: 'Send To:'
    -    },{
    -        x: 60,
    -        y: 0,
    -        name: 'to',
    -        anchor:'100%'  // anchor width by percentage
    -    },{
    -        x: 0,
    -        y: 35,
    -        xtype:'label',
    -        text: 'Subject:'
    -    },{
    -        x: 60,
    -        y: 30,
    -        name: 'subject',
    -        anchor: '100%'  // anchor width by percentage
    -    },{
    -        x:0,
    -        y: 60,
    -        xtype: 'textarea',
    -        name: 'msg',
    -        anchor: '100% 100%'  // anchor width and height
    -    }]
    -});
    -
    - */ -Ext.layout.AbsoluteLayout = Ext.extend(Ext.layout.AnchorLayout, { - - extraCls: 'x-abs-layout-item', - - onLayout : function(ct, target){ - target.position(); - this.paddingLeft = target.getPadding('l'); - this.paddingTop = target.getPadding('t'); - - Ext.layout.AbsoluteLayout.superclass.onLayout.call(this, ct, target); - }, - - // private - adjustWidthAnchor : function(value, comp){ - return value ? value - comp.getPosition(true)[0] + this.paddingLeft : value; - }, - - // private - adjustHeightAnchor : function(value, comp){ - return value ? value - comp.getPosition(true)[1] + this.paddingTop : value; - } - /** - * @property activeItem - * @hide - */ -}); -Ext.Container.LAYOUTS['absolute'] = Ext.layout.AbsoluteLayout; -/** - * @class Ext.layout.BoxLayout - * @extends Ext.layout.ContainerLayout - *

    Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.

    - */ -Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, { - /** - * @cfg {Object} defaultMargins - *

    If the individual contained items do not have a margins - * property specified, the default margins from this property will be - * applied to each item.

    - *

    This property may be specified as an object containing margins - * to apply in the format:

    
    -{
    -    top: (top margin),
    -    right: (right margin),
    -    bottom: (bottom margin),
    -    left: (left margin)
    -}
    - *

    This property may also be specified as a string containing - * space-separated, numeric margin values. The order of the sides associated - * with each value matches the way CSS processes margin values:

    - *
      - *
    • If there is only one value, it applies to all sides.
    • - *
    • If there are two values, the top and bottom borders are set to the - * first value and the right and left are set to the second.
    • - *
    • If there are three values, the top is set to the first value, the left - * and right are set to the second, and the bottom is set to the third.
    • - *
    • If there are four values, they apply to the top, right, bottom, and - * left, respectively.
    • - *
    - *

    Defaults to:

    
    -     * {top:0, right:0, bottom:0, left:0}
    -     * 
    - */ - defaultMargins : {left:0,top:0,right:0,bottom:0}, - /** - * @cfg {String} padding - * Defaults to '0'. Sets the padding to be applied to all child items managed by this - * container's layout. - */ - padding : '0', - // documented in subclasses - pack : 'start', - - // private - monitorResize : true, - scrollOffset : 0, - extraCls : 'x-box-item', - ctCls : 'x-box-layout-ct', - innerCls : 'x-box-inner', - - // private - isValidParent : function(c, target){ - return c.getEl().dom.parentNode == this.innerCt.dom; - }, - - // private - onLayout : function(ct, target){ - var cs = ct.items.items, len = cs.length, c, i, last = len-1, cm; - - if(!this.innerCt){ - target.addClass(this.ctCls); - - // the innerCt prevents wrapping and shuffling while - // the container is resizing - this.innerCt = target.createChild({cls:this.innerCls}); - this.padding = this.parseMargins(this.padding); - } - this.renderAll(ct, this.innerCt); - }, - - // private - renderItem : function(c){ - if(typeof c.margins == 'string'){ - c.margins = this.parseMargins(c.margins); - }else if(!c.margins){ - c.margins = this.defaultMargins; - } - Ext.layout.BoxLayout.superclass.renderItem.apply(this, arguments); - }, - - getTargetSize : function(target){ - return (Ext.isIE6 && Ext.isStrict && target.dom == document.body) ? target.getStyleSize() : target.getViewSize(); - }, - - getItems: function(ct){ - var items = []; - ct.items.each(function(c){ - if(c.isVisible()){ - items.push(c); - } - }); - return items; - } - - /** - * @property activeItem - * @hide - */ -}); - -/** - * @class Ext.layout.VBoxLayout - * @extends Ext.layout.BoxLayout - * A layout that arranges items vertically - */ -Ext.layout.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, { - /** - * @cfg {String} align - * Controls how the child items of the container are aligned. Acceptable configuration values for this - * property are: - *
      - *
    • left : Default
      child items are aligned horizontally - * at the left side of the container
    • - *
    • center :
      child items are aligned horizontally at the - * mid-width of the container
    • - *
    • stretch :
      child items are stretched horizontally to fill - * the width of the container
    • - *
    • stretchmax :
      child items are stretched horizontally to - * the size of the largest item.
    • - *
    - */ - align : 'left', // left, center, stretch, strechmax - /** - * @cfg {String} pack - * Controls how the child items of the container are packed together. Acceptable configuration values - * for this property are: - *
      - *
    • start : Default
      child items are packed together at - * top side of container
    • - *
    • center :
      child items are packed together at - * mid-height of container
    • - *
    • end :
      child items are packed together at bottom - * side of container
    • - *
    - */ - /** - * @cfg {Number} flex - * This configuation option is to be applied to child items of the container managed - * by this layout. Each child item with a flex property will be flexed vertically - * according to each item's relative flex value compared to the sum of all items with - * a flex value specified. Any child items that have either a flex = 0 or - * flex = undefined will not be 'flexed' (the initial size will not be changed). - */ - - // private - onLayout : function(ct, target){ - Ext.layout.VBoxLayout.superclass.onLayout.call(this, ct, target); - - - var cs = this.getItems(ct), cm, ch, margin, - size = this.getTargetSize(target), - w = size.width - target.getPadding('lr') - this.scrollOffset, - h = size.height - target.getPadding('tb'), - l = this.padding.left, t = this.padding.top, - isStart = this.pack == 'start', - isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1, - stretchWidth = w - (this.padding.left + this.padding.right), - extraHeight = 0, - maxWidth = 0, - totalFlex = 0, - flexHeight = 0, - usedHeight = 0; - - Ext.each(cs, function(c){ - cm = c.margins; - totalFlex += c.flex || 0; - ch = c.getHeight(); - margin = cm.top + cm.bottom; - extraHeight += ch + margin; - flexHeight += margin + (c.flex ? 0 : ch); - maxWidth = Math.max(maxWidth, c.getWidth() + cm.left + cm.right); - }); - extraHeight = h - extraHeight - this.padding.top - this.padding.bottom; - - var innerCtWidth = maxWidth + this.padding.left + this.padding.right; - switch(this.align){ - case 'stretch': - this.innerCt.setSize(w, h); - break; - case 'stretchmax': - case 'left': - this.innerCt.setSize(innerCtWidth, h); - break; - case 'center': - this.innerCt.setSize(w = Math.max(w, innerCtWidth), h); - break; - } - - var availHeight = Math.max(0, h - this.padding.top - this.padding.bottom - flexHeight), - leftOver = availHeight, - heights = [], - restore = [], - idx = 0, - availableWidth = Math.max(0, w - this.padding.left - this.padding.right); - - - Ext.each(cs, function(c){ - if(isStart && c.flex){ - ch = Math.floor(availHeight * (c.flex / totalFlex)); - leftOver -= ch; - heights.push(ch); - } - }); - - if(this.pack == 'center'){ - t += extraHeight ? extraHeight / 2 : 0; - }else if(this.pack == 'end'){ - t += extraHeight; - } - Ext.each(cs, function(c){ - cm = c.margins; - t += cm.top; - c.setPosition(l + cm.left, t); - if(isStart && c.flex){ - ch = Math.max(0, heights[idx++] + (leftOver-- > 0 ? 1 : 0)); - if(isRestore){ - restore.push(c.getWidth()); - } - c.setSize(availableWidth, ch); - }else{ - ch = c.getHeight(); - } - t += ch + cm.bottom; - }); - - idx = 0; - Ext.each(cs, function(c){ - cm = c.margins; - if(this.align == 'stretch'){ - c.setWidth((stretchWidth - (cm.left + cm.right)).constrain( - c.minWidth || 0, c.maxWidth || 1000000)); - }else if(this.align == 'stretchmax'){ - c.setWidth((maxWidth - (cm.left + cm.right)).constrain( - c.minWidth || 0, c.maxWidth || 1000000)); - }else{ - if(this.align == 'center'){ - var diff = availableWidth - (c.getWidth() + cm.left + cm.right); - if(diff > 0){ - c.setPosition(l + cm.left + (diff/2), c.y); - } - } - if(isStart && c.flex){ - c.setWidth(restore[idx++]); - } - } - }, this); - } - /** - * @property activeItem - * @hide - */ -}); - -Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout; - -/** - * @class Ext.layout.HBoxLayout - * @extends Ext.layout.BoxLayout - * A layout that arranges items horizontally - */ -Ext.layout.HBoxLayout = Ext.extend(Ext.layout.BoxLayout, { - /** - * @cfg {String} align - * Controls how the child items of the container are aligned. Acceptable configuration values for this - * property are: - *
      - *
    • top : Default
      child items are aligned vertically - * at the left side of the container
    • - *
    • middle :
      child items are aligned vertically at the - * mid-height of the container
    • - *
    • stretch :
      child items are stretched vertically to fill - * the height of the container
    • - *
    • stretchmax :
      child items are stretched vertically to - * the size of the largest item.
    • - */ - align : 'top', // top, middle, stretch, strechmax - /** - * @cfg {String} pack - * Controls how the child items of the container are packed together. Acceptable configuration values - * for this property are: - *
        - *
      • start : Default
        child items are packed together at - * left side of container
      • - *
      • center :
        child items are packed together at - * mid-width of container
      • - *
      • end :
        child items are packed together at right - * side of container
      • - *
      - */ - /** - * @cfg {Number} flex - * This configuation option is to be applied to child items of the container managed - * by this layout. Each child item with a flex property will be flexed horizontally - * according to each item's relative flex value compared to the sum of all items with - * a flex value specified. Any child items that have either a flex = 0 or - * flex = undefined will not be 'flexed' (the initial size will not be changed). - */ - - // private - onLayout : function(ct, target){ - Ext.layout.HBoxLayout.superclass.onLayout.call(this, ct, target); - - var cs = this.getItems(ct), cm, cw, margin, - size = this.getTargetSize(target), - w = size.width - target.getPadding('lr') - this.scrollOffset, - h = size.height - target.getPadding('tb'), - l = this.padding.left, t = this.padding.top, - isStart = this.pack == 'start', - isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1, - stretchHeight = h - (this.padding.top + this.padding.bottom), - extraWidth = 0, - maxHeight = 0, - totalFlex = 0, - flexWidth = 0, - usedWidth = 0; - - Ext.each(cs, function(c){ - cm = c.margins; - totalFlex += c.flex || 0; - cw = c.getWidth(); - margin = cm.left + cm.right; - extraWidth += cw + margin; - flexWidth += margin + (c.flex ? 0 : cw); - maxHeight = Math.max(maxHeight, c.getHeight() + cm.top + cm.bottom); - }); - extraWidth = w - extraWidth - this.padding.left - this.padding.right; - - var innerCtHeight = maxHeight + this.padding.top + this.padding.bottom; - switch(this.align){ - case 'stretch': - this.innerCt.setSize(w, h); - break; - case 'stretchmax': - case 'top': - this.innerCt.setSize(w, innerCtHeight); - break; - case 'middle': - this.innerCt.setSize(w, h = Math.max(h, innerCtHeight)); - break; - } - - - var availWidth = Math.max(0, w - this.padding.left - this.padding.right - flexWidth), - leftOver = availWidth, - widths = [], - restore = [], - idx = 0, - availableHeight = Math.max(0, h - this.padding.top - this.padding.bottom); - - - Ext.each(cs, function(c){ - if(isStart && c.flex){ - cw = Math.floor(availWidth * (c.flex / totalFlex)); - leftOver -= cw; - widths.push(cw); - } - }); - - if(this.pack == 'center'){ - l += extraWidth ? extraWidth / 2 : 0; - }else if(this.pack == 'end'){ - l += extraWidth; - } - Ext.each(cs, function(c){ - cm = c.margins; - l += cm.left; - c.setPosition(l, t + cm.top); - if(isStart && c.flex){ - cw = Math.max(0, widths[idx++] + (leftOver-- > 0 ? 1 : 0)); - if(isRestore){ - restore.push(c.getHeight()); - } - c.setSize(cw, availableHeight); - }else{ - cw = c.getWidth(); - } - l += cw + cm.right; - }); - - idx = 0; - Ext.each(cs, function(c){ - var cm = c.margins; - if(this.align == 'stretch'){ - c.setHeight((stretchHeight - (cm.top + cm.bottom)).constrain( - c.minHeight || 0, c.maxHeight || 1000000)); - }else if(this.align == 'stretchmax'){ - c.setHeight((maxHeight - (cm.top + cm.bottom)).constrain( - c.minHeight || 0, c.maxHeight || 1000000)); - }else{ - if(this.align == 'middle'){ - var diff = availableHeight - (c.getHeight() + cm.top + cm.bottom); - if(diff > 0){ - c.setPosition(c.x, t + cm.top + (diff/2)); - } - } - if(isStart && c.flex){ - c.setHeight(restore[idx++]); - } - } - }, this); - } - - /** - * @property activeItem - * @hide - */ -}); - -Ext.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout; -/** - * @class Ext.Viewport - * @extends Ext.Container - *

      A specialized container representing the viewable application area (the browser viewport).

      - *

      The Viewport renders itself to the document body, and automatically sizes itself to the size of - * the browser viewport and manages window resizing. There may only be one Viewport created - * in a page. Inner layouts are available by virtue of the fact that all {@link Ext.Panel Panel}s - * added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add} - * method of any of its child Panels may themselves have a layout.

      - *

      The Viewport does not provide scrolling, so child Panels within the Viewport should provide - * for scrolling if needed using the {@link #autoScroll} config.

      - *

      An example showing a classic application border layout:

      
      -new Ext.Viewport({
      -    layout: 'border',
      -    items: [{
      -        region: 'north',
      -        html: '<h1 class="x-panel-header">Page Title</h1>',
      -        autoHeight: true,
      -        border: false,
      -        margins: '0 0 5 0'
      -    }, {
      -        region: 'west',
      -        collapsible: true,
      -        title: 'Navigation',
      -        width: 200
      -        // the west region might typically utilize a {@link Ext.tree.TreePanel TreePanel} or a Panel with {@link Ext.layout.AccordionLayout Accordion layout} 
      -    }, {
      -        region: 'south',
      -        title: 'Title for Panel',
      -        collapsible: true,
      -        html: 'Information goes here',
      -        split: true,
      -        height: 100,
      -        minHeight: 100
      -    }, {
      -        region: 'east',
      -        title: 'Title for the Grid Panel',
      -        collapsible: true,
      -        split: true,
      -        width: 200,
      -        xtype: 'grid',
      -        // remaining grid configuration not shown ...
      -        // notice that the GridPanel is added directly as the region
      -        // it is not "overnested" inside another Panel
      -    }, {
      -        region: 'center',
      -        xtype: 'tabpanel', // TabPanel itself has no title
      -        items: {
      -            title: 'Default Tab',
      -            html: 'The first tab\'s content. Others may be added dynamically'
      -        }
      -    }]
      -});
      -
      - * @constructor - * Create a new Viewport - * @param {Object} config The config object - * @xtype viewport - */ -Ext.Viewport = Ext.extend(Ext.Container, { - /* - * Privatize config options which, if used, would interfere with the - * correct operation of the Viewport as the sole manager of the - * layout of the document body. - */ - /** - * @cfg {Mixed} applyTo @hide - */ - /** - * @cfg {Boolean} allowDomMove @hide - */ - /** - * @cfg {Boolean} hideParent @hide - */ - /** - * @cfg {Mixed} renderTo @hide - */ - /** - * @cfg {Boolean} hideParent @hide - */ - /** - * @cfg {Number} height @hide - */ - /** - * @cfg {Number} width @hide - */ - /** - * @cfg {Boolean} autoHeight @hide - */ - /** - * @cfg {Boolean} autoWidth @hide - */ - /** - * @cfg {Boolean} deferHeight @hide - */ - /** - * @cfg {Boolean} monitorResize @hide - */ - initComponent : function() { - Ext.Viewport.superclass.initComponent.call(this); - document.getElementsByTagName('html')[0].className += ' x-viewport'; - this.el = Ext.getBody(); - this.el.setHeight = Ext.emptyFn; - this.el.setWidth = Ext.emptyFn; - this.el.setSize = Ext.emptyFn; - this.el.dom.scroll = 'no'; - this.allowDomMove = false; - this.autoWidth = true; - this.autoHeight = true; - Ext.EventManager.onWindowResize(this.fireResize, this); - this.renderTo = this.el; - }, - - fireResize : function(w, h){ - this.fireEvent('resize', this, w, h, w, h); - } -}); -Ext.reg('viewport', Ext.Viewport);/** - * @class Ext.Panel - * @extends Ext.Container - *

      Panel is a container that has specific functionality and structural components that make - * it the perfect building block for application-oriented user interfaces.

      - *

      Panels are, by virtue of their inheritance from {@link Ext.Container}, capable - * of being configured with a {@link Ext.Container#layout layout}, and containing child Components.

      - *

      When either specifying child {@link Ext.Component#items items} of a Panel, or dynamically {@link Ext.Container#add adding} Components - * to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether - * those child elements need to be sized using one of Ext's built-in {@link Ext.Container#layout layout} schemes. By - * default, Panels use the {@link Ext.layout.ContainerLayout ContainerLayout} scheme. This simply renders - * child components, appending them one after the other inside the Container, and does not apply any sizing - * at all.

      - *

      A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate - * {@link #header}, {@link #footer} and {@link #body} sections (see {@link #frame} for additional - * information).

      - *

      Panel also provides built-in {@link #collapsible expandable and collapsible behavior}, along with - * a variety of {@link #tools prebuilt tool buttons} that can be wired up to provide other customized - * behavior. Panels can be easily dropped into any {@link Ext.Container Container} or layout, and the - * layout and rendering pipeline is {@link Ext.Container#add completely managed by the framework}.

      - * @constructor - * @param {Object} config The config object - * @xtype panel - */ -Ext.Panel = Ext.extend(Ext.Container, { - /** - * The Panel's header {@link Ext.Element Element}. Read-only. - *

      This Element is used to house the {@link #title} and {@link #tools}

      - *

      Note: see the Note for {@link Ext.Component#el el} also.

      - * @type Ext.Element - * @property header - */ - /** - * The Panel's body {@link Ext.Element Element} which may be used to contain HTML content. - * The content may be specified in the {@link #html} config, or it may be loaded using the - * {@link autoLoad} config, or through the Panel's {@link #getUpdater Updater}. Read-only. - *

      If this is used to load visible HTML elements in either way, then - * the Panel may not be used as a Layout for hosting nested Panels.

      - *

      If this Panel is intended to be used as the host of a Layout (See {@link #layout} - * then the body Element must not be loaded or changed - it is under the control - * of the Panel's Layout. - *

      Note: see the Note for {@link Ext.Component#el el} also.

      - * @type Ext.Element - * @property body - */ - /** - * The Panel's bwrap {@link Ext.Element Element} used to contain other Panel elements - * (tbar, body, bbar, footer). See {@link #bodyCfg}. Read-only. - * @type Ext.Element - * @property bwrap - */ - /** - * True if this panel is collapsed. Read-only. - * @type Boolean - * @property collapsed - */ - /** - * @cfg {Object} bodyCfg - *

      A {@link Ext.DomHelper DomHelper} element specification object may be specified for any - * Panel Element.

      - *

      By default, the Default element in the table below will be used for the html markup to - * create a child element with the commensurate Default class name (baseCls will be - * replaced by {@link #baseCls}):

      - *
      -     * Panel      Default  Default             Custom      Additional       Additional
      -     * Element    element  class               element     class            style
      -     * ========   ==========================   =========   ==============   ===========
      -     * {@link #header}     div      {@link #baseCls}+'-header'   {@link #headerCfg}   headerCssClass   headerStyle
      -     * {@link #bwrap}      div      {@link #baseCls}+'-bwrap'     {@link #bwrapCfg}    bwrapCssClass    bwrapStyle
      -     * + tbar     div      {@link #baseCls}+'-tbar'       {@link #tbarCfg}     tbarCssClass     tbarStyle
      -     * + {@link #body}     div      {@link #baseCls}+'-body'       {@link #bodyCfg}     {@link #bodyCssClass}     {@link #bodyStyle}
      -     * + bbar     div      {@link #baseCls}+'-bbar'       {@link #bbarCfg}     bbarCssClass     bbarStyle
      -     * + {@link #footer}   div      {@link #baseCls}+'-footer'   {@link #footerCfg}   footerCssClass   footerStyle
      -     * 
      - *

      Configuring a Custom element may be used, for example, to force the {@link #body} Element - * to use a different form of markup than is created by default. An example of this might be - * to {@link Ext.Element#createChild create a child} Panel containing a custom content, such as - * a header, or forcing centering of all Panel content by having the body be a <center> - * element:

      - *
      
      -new Ext.Panel({
      -    title: 'Message Title',
      -    renderTo: Ext.getBody(),
      -    width: 200, height: 130,
      -    bodyCfg: {
      -        tag: 'center',
      -        cls: 'x-panel-body',  // Default class not applied if Custom element specified
      -        html: 'Message'
      +Ext.Container.LAYOUTS['form'] = Ext.layout.FormLayout;
      +/**
      + * @class Ext.layout.AccordionLayout
      + * @extends Ext.layout.FitLayout
      + * 

      This is a layout that manages multiple Panels in an expandable accordion style such that only + * one Panel can be expanded at any given time. Each Panel has built-in support for expanding and collapsing.

      + *

      Note: Only Ext.Panels and all subclasses of Ext.Panel may be used in an accordion layout Container.

      + *

      This class is intended to be extended or created via the {@link Ext.Container#layout layout} + * configuration property. See {@link Ext.Container#layout} for additional details.

      + *

      Example usage:

      + *
      
      +var accordion = new Ext.Panel({
      +    title: 'Accordion Layout',
      +    layout:'accordion',
      +    defaults: {
      +        // applied to each contained panel
      +        bodyStyle: 'padding:15px'
           },
      -    footerCfg: {
      -        tag: 'h2',
      -        cls: 'x-panel-footer'        // same as the Default class
      -        html: 'footer html'
      +    layoutConfig: {
      +        // layout-specific configs go here
      +        titleCollapse: false,
      +        animate: true,
      +        activeOnTop: true
           },
      -    footerCssClass: 'custom-footer', // additional css class, see {@link Ext.element#addClass addClass}
      -    footerStyle:    'background-color:red' // see {@link #bodyStyle}
      +    items: [{
      +        title: 'Panel 1',
      +        html: '<p>Panel content!</p>'
      +    },{
      +        title: 'Panel 2',
      +        html: '<p>Panel content!</p>'
      +    },{
      +        title: 'Panel 3',
      +        html: '<p>Panel content!</p>'
      +    }]
       });
      -     * 
      - *

      The example above also explicitly creates a {@link #footer} with custom markup and - * styling applied.

      - */ - /** - * @cfg {Object} headerCfg - *

      A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure - * of this Panel's {@link #header} Element. See {@link #bodyCfg} also.

      - */ +
      + */ +Ext.layout.AccordionLayout = Ext.extend(Ext.layout.FitLayout, { /** - * @cfg {Object} bwrapCfg - *

      A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure - * of this Panel's {@link #bwrap} Element. See {@link #bodyCfg} also.

      + * @cfg {Boolean} fill + * True to adjust the active item's height to fill the available space in the container, false to use the + * item's current height, or auto height if not explicitly set (defaults to true). */ + fill : true, /** - * @cfg {Object} tbarCfg - *

      A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure - * of this Panel's {@link #tbar} Element. See {@link #bodyCfg} also.

      + * @cfg {Boolean} autoWidth + * True to set each contained item's width to 'auto', false to use the item's current width (defaults to true). + * Note that some components, in particular the {@link Ext.grid.GridPanel grid}, will not function properly within + * layouts if they have auto width, so in such cases this config should be set to false. */ + autoWidth : true, /** - * @cfg {Object} bbarCfg - *

      A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure - * of this Panel's {@link #bbar} Element. See {@link #bodyCfg} also.

      + * @cfg {Boolean} titleCollapse + * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow + * expand/collapse only when the toggle tool button is clicked (defaults to true). When set to false, + * {@link #hideCollapseTool} should be false also. */ + titleCollapse : true, /** - * @cfg {Object} footerCfg - *

      A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure - * of this Panel's {@link #footer} Element. See {@link #bodyCfg} also.

      + * @cfg {Boolean} hideCollapseTool + * True to hide the contained panels' collapse/expand toggle buttons, false to display them (defaults to false). + * When set to true, {@link #titleCollapse} should be true also. */ + hideCollapseTool : false, /** - * @cfg {Boolean} closable - * Panels themselves do not directly support being closed, but some Panel subclasses do (like - * {@link Ext.Window}) or a Panel Class within an {@link Ext.TabPanel}. Specify true - * to enable closing in such situations. Defaults to false. + * @cfg {Boolean} collapseFirst + * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools + * in the contained panels' title bars, false to render it last (defaults to false). */ + collapseFirst : false, /** - * The Panel's footer {@link Ext.Element Element}. Read-only. - *

      This Element is used to house the Panel's {@link #buttons} or {@link #fbar}.

      - *

      Note: see the Note for {@link Ext.Component#el el} also.

      - * @type Ext.Element - * @property footer + * @cfg {Boolean} animate + * True to slide the contained panels open and closed during expand/collapse using animation, false to open and + * close directly with no animation (defaults to false). Note: to defer to the specific config setting of each + * contained panel for this property, set this to undefined at the layout level. */ + animate : false, /** - * @cfg {Mixed} applyTo - *

      The id of the node, a DOM node or an existing Element corresponding to a DIV that is already present in - * the document that specifies some panel-specific structural markup. When applyTo is used, - * constituent parts of the panel can be specified by CSS class name within the main element, and the panel - * will automatically create those components from that markup. Any required components not specified in the - * markup will be autogenerated if necessary.

      - *

      The following class names are supported (baseCls will be replaced by {@link #baseCls}):

      - *
      • baseCls + '-header'
      • - *
      • baseCls + '-header-text'
      • - *
      • baseCls + '-bwrap'
      • - *
      • baseCls + '-tbar'
      • - *
      • baseCls + '-body'
      • - *
      • baseCls + '-bbar'
      • - *
      • baseCls + '-footer'
      - *

      Using this config, a call to render() is not required. If applyTo is specified, any value passed for - * {@link #renderTo} will be ignored and the target element's parent node will automatically be used as the - * panel's container.

      + * @cfg {Boolean} sequence + * Experimental. If animate is set to true, this will result in each animation running in sequence. */ + sequence : false, /** - * @cfg {Object/Array} tbar - *

      The top toolbar of the panel. This can be a {@link Ext.Toolbar} object, a toolbar config, or an array of - * buttons/button configs to be added to the toolbar. Note that this is not available as a property after render. - * To access the top toolbar after render, use {@link #getTopToolbar}.

      - *

      Note: Although a Toolbar may contain Field components, these will not be updated by a load - * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and - * so are not scanned to collect form items. However, the values will be submitted because form - * submission parameters are collected from the DOM tree.

      + * @cfg {Boolean} activeOnTop + * True to swap the position of each panel as it is expanded so that it becomes the first item in the container, + * false to keep the panels in the rendered order. This is NOT compatible with "animate:true" (defaults to false). */ + activeOnTop : false, + + type: 'accordion', + + renderItem : function(c){ + if(this.animate === false){ + c.animCollapse = false; + } + c.collapsible = true; + if(this.autoWidth){ + c.autoWidth = true; + } + if(this.titleCollapse){ + c.titleCollapse = true; + } + if(this.hideCollapseTool){ + c.hideCollapseTool = true; + } + if(this.collapseFirst !== undefined){ + c.collapseFirst = this.collapseFirst; + } + if(!this.activeItem && !c.collapsed){ + this.setActiveItem(c, true); + }else if(this.activeItem && this.activeItem != c){ + c.collapsed = true; + } + Ext.layout.AccordionLayout.superclass.renderItem.apply(this, arguments); + c.header.addClass('x-accordion-hd'); + c.on('beforeexpand', this.beforeExpand, this); + }, + + onRemove: function(c){ + Ext.layout.AccordionLayout.superclass.onRemove.call(this, c); + if(c.rendered){ + c.header.removeClass('x-accordion-hd'); + } + c.un('beforeexpand', this.beforeExpand, this); + }, + + // private + beforeExpand : function(p, anim){ + var ai = this.activeItem; + if(ai){ + if(this.sequence){ + delete this.activeItem; + if (!ai.collapsed){ + ai.collapse({callback:function(){ + p.expand(anim || true); + }, scope: this}); + return false; + } + }else{ + ai.collapse(this.animate); + } + } + this.setActive(p); + if(this.activeOnTop){ + p.el.dom.parentNode.insertBefore(p.el.dom, p.el.dom.parentNode.firstChild); + } + // Items have been hidden an possibly rearranged, we need to get the container size again. + this.layout(); + }, + + // private + setItemSize : function(item, size){ + if(this.fill && item){ + var hh = 0, i, ct = this.getRenderedItems(this.container), len = ct.length, p; + // Add up all the header heights + for (i = 0; i < len; i++) { + if((p = ct[i]) != item && !p.hidden){ + hh += p.header.getHeight(); + } + }; + // Subtract the header heights from the container size + size.height -= hh; + // Call setSize on the container to set the correct height. For Panels, deferedHeight + // will simply store this size for when the expansion is done. + item.setSize(size); + } + }, + /** - * @cfg {Object/Array} bbar - *

      The bottom toolbar of the panel. This can be a {@link Ext.Toolbar} object, a toolbar config, or an array of - * buttons/button configs to be added to the toolbar. Note that this is not available as a property after render. - * To access the bottom toolbar after render, use {@link #getBottomToolbar}.

      - *

      Note: Although a Toolbar may contain Field components, these will not be updated by a load - * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and - * so are not scanned to collect form items. However, the values will be submitted because form - * submission parameters are collected from the DOM tree.

      + * Sets the active (expanded) item in the layout. + * @param {String/Number} item The string component id or numeric index of the item to activate */ - /** @cfg {Object/Array} fbar - *

      A {@link Ext.Toolbar Toolbar} object, a Toolbar config, or an array of - * {@link Ext.Button Button}s/{@link Ext.Button Button} configs, describing a {@link Ext.Toolbar Toolbar} to be rendered into this Panel's footer element.

      - *

      After render, the fbar property will be an {@link Ext.Toolbar Toolbar} instance.

      - *

      If {@link #buttons} are specified, they will supersede the fbar configuration property.

      - * The Panel's {@link #buttonAlign} configuration affects the layout of these items, for example: - *
      
      -var w = new Ext.Window({
      -    height: 250,
      -    width: 500,
      -    bbar: new Ext.Toolbar({
      -        items: [{
      -            text: 'bbar Left'
      -        },'->',{
      -            text: 'bbar Right'
      -        }]
      -    }),
      -    {@link #buttonAlign}: 'left', // anything but 'center' or 'right' and you can use "-", and "->"
      -                                  // to control the alignment of fbar items
      -    fbar: [{
      -        text: 'fbar Left'
      -    },'->',{
      -        text: 'fbar Right'
      +    setActiveItem : function(item){
      +        this.setActive(item, true);
      +    },
      +
      +    // private
      +    setActive : function(item, expand){
      +        var ai = this.activeItem;
      +        item = this.container.getComponent(item);
      +        if(ai != item){
      +            if(item.rendered && item.collapsed && expand){
      +                item.expand();
      +            }else{
      +                if(ai){
      +                   ai.fireEvent('deactivate', ai);
      +                }
      +                this.activeItem = item;
      +                item.fireEvent('activate', item);
      +            }
      +        }
      +    }
      +});
      +Ext.Container.LAYOUTS.accordion = Ext.layout.AccordionLayout;
      +
      +//backwards compat
      +Ext.layout.Accordion = Ext.layout.AccordionLayout;/**
      + * @class Ext.layout.TableLayout
      + * @extends Ext.layout.ContainerLayout
      + * 

      This layout allows you to easily render content into an HTML table. The total number of columns can be + * specified, and rowspan and colspan can be used to create complex layouts within the table. + * This class is intended to be extended or created via the layout:'table' {@link Ext.Container#layout} config, + * and should generally not need to be created directly via the new keyword.

      + *

      Note that when creating a layout via config, the layout-specific config properties must be passed in via + * the {@link Ext.Container#layoutConfig} object which will then be applied internally to the layout. In the + * case of TableLayout, the only valid layout config property is {@link #columns}. However, the items added to a + * TableLayout can supply the following table-specific config properties:

      + *
        + *
      • rowspan Applied to the table cell containing the item.
      • + *
      • colspan Applied to the table cell containing the item.
      • + *
      • cellId An id applied to the table cell containing the item.
      • + *
      • cellCls A CSS class name added to the table cell containing the item.
      • + *
      + *

      The basic concept of building up a TableLayout is conceptually very similar to building up a standard + * HTML table. You simply add each panel (or "cell") that you want to include along with any span attributes + * specified as the special config properties of rowspan and colspan which work exactly like their HTML counterparts. + * Rather than explicitly creating and nesting rows and columns as you would in HTML, you simply specify the + * total column count in the layoutConfig and start adding panels in their natural order from left to right, + * top to bottom. The layout will automatically figure out, based on the column count, rowspans and colspans, + * how to position each panel within the table. Just like with HTML tables, your rowspans and colspans must add + * up correctly in your overall layout or you'll end up with missing and/or extra cells! Example usage:

      + *
      
      +// This code will generate a layout table that is 3 columns by 2 rows
      +// with some spanning included.  The basic layout will be:
      +// +--------+-----------------+
      +// |   A    |   B             |
      +// |        |--------+--------|
      +// |        |   C    |   D    |
      +// +--------+--------+--------+
      +var table = new Ext.Panel({
      +    title: 'Table Layout',
      +    layout:'table',
      +    defaults: {
      +        // applied to each contained panel
      +        bodyStyle:'padding:20px'
      +    },
      +    layoutConfig: {
      +        // The total column count must be specified here
      +        columns: 3
      +    },
      +    items: [{
      +        html: '<p>Cell A content</p>',
      +        rowspan: 2
      +    },{
      +        html: '<p>Cell B content</p>',
      +        colspan: 2
      +    },{
      +        html: '<p>Cell C content</p>',
      +        cellCls: 'highlight'
      +    },{
      +        html: '<p>Cell D content</p>'
           }]
      -}).show();
      -     * 
      - *

      Note: Although a Toolbar may contain Field components, these will not be updated by a load - * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and - * so are not scanned to collect form items. However, the values will be submitted because form - * submission parameters are collected from the DOM tree.

      - */ - /** - * @cfg {Boolean} header - * true to create the Panel's header element explicitly, false to skip creating - * it. If a {@link #title} is set the header will be created automatically, otherwise it will not. - * If a {@link #title} is set but header is explicitly set to false, the header - * will not be rendered. - */ - /** - * @cfg {Boolean} footer - * true to create the footer element explicitly, false to skip creating it. The footer - * will be created automatically if {@link #buttons} or a {@link #fbar} have - * been configured. See {@link #bodyCfg} for an example. - */ - /** - * @cfg {String} title - * The title text to be used as innerHTML (html tags are accepted) to display in the panel - * {@link #header} (defaults to ''). When a title is specified the - * {@link #header} element will automatically be created and displayed unless - * {@link #header} is explicitly set to false. If you do not want to specify a - * title at config time, but you may want one later, you must either specify a non-empty - * title (a blank space ' ' will do) or header:true so that the container - * element will get created. - */ - /** - * @cfg {Array} buttons - * buttons will be used as {@link Ext.Container#items items} for the toolbar in - * the footer ({@link #fbar}). Typically the value of this configuration property will be - * an array of {@link Ext.Button}s or {@link Ext.Button} configuration objects. - * If an item is configured with minWidth or the Panel is configured with minButtonWidth, - * that width will be applied to the item. - */ +}); +
      + */ +Ext.layout.TableLayout = Ext.extend(Ext.layout.ContainerLayout, { /** - * @cfg {Object/String/Function} autoLoad - * A valid url spec according to the Updater {@link Ext.Updater#update} method. - * If autoLoad is not null, the panel will attempt to load its contents - * immediately upon render.

      - * The URL will become the default URL for this panel's {@link #body} element, - * so it may be {@link Ext.Element#refresh refresh}ed at any time.

      + * @cfg {Number} columns + * The total number of columns to create in the table for this layout. If not specified, all Components added to + * this layout will be rendered into a single row using one column per Component. */ - /** - * @cfg {Boolean} frame - * false by default to render with plain 1px square borders. true to render with - * 9 elements, complete with custom rounded corners (also see {@link Ext.Element#boxWrap}). - *

      The template generated for each condition is depicted below:

      
      -     *
      -// frame = false
      -<div id="developer-specified-id-goes-here" class="x-panel">
      -
      -    <div class="x-panel-header"><span class="x-panel-header-text">Title: (frame:false)</span></div>
       
      -    <div class="x-panel-bwrap">
      -        <div class="x-panel-body"><p>html value goes here</p></div>
      -    </div>
      -</div>
      +    // private
      +    monitorResize:false,
       
      -// frame = true (create 9 elements)
      -<div id="developer-specified-id-goes-here" class="x-panel">
      -    <div class="x-panel-tl"><div class="x-panel-tr"><div class="x-panel-tc">
      -        <div class="x-panel-header"><span class="x-panel-header-text">Title: (frame:true)</span></div>
      -    </div></div></div>
      +    type: 'table',
       
      -    <div class="x-panel-bwrap">
      -        <div class="x-panel-ml"><div class="x-panel-mr"><div class="x-panel-mc">
      -            <div class="x-panel-body"><p>html value goes here</p></div>
      -        </div></div></div>
      +    targetCls: 'x-table-layout-ct',
       
      -        <div class="x-panel-bl"><div class="x-panel-br"><div class="x-panel-bc"/>
      -        </div></div></div>
      -</div>
      -     * 
      - */ /** - * @cfg {Boolean} border - * True to display the borders of the panel's body element, false to hide them (defaults to true). By default, - * the border is a 2px wide inset border, but this can be further altered by setting {@link #bodyBorder} to false. - */ - /** - * @cfg {Boolean} bodyBorder - * True to display an interior border on the body element of the panel, false to hide it (defaults to true). - * This only applies when {@link #border} == true. If border == true and bodyBorder == false, the border will display - * as a 1px wide inset border, giving the entire body element an inset appearance. - */ - /** - * @cfg {String/Object/Function} bodyCssClass - * Additional css class selector to be applied to the {@link #body} element in the format expected by - * {@link Ext.Element#addClass} (defaults to null). See {@link #bodyCfg}. + * @cfg {Object} tableAttrs + *

      An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification + * used to create the layout's <table> element. Example:

      
      +{
      +    xtype: 'panel',
      +    layout: 'table',
      +    layoutConfig: {
      +        tableAttrs: {
      +            style: {
      +                width: '100%'
      +            }
      +        },
      +        columns: 3
      +    }
      +}
      */ + tableAttrs:null, + + // private + setContainer : function(ct){ + Ext.layout.TableLayout.superclass.setContainer.call(this, ct); + + this.currentRow = 0; + this.currentColumn = 0; + this.cells = []; + }, + + // private + onLayout : function(ct, target){ + var cs = ct.items.items, len = cs.length, c, i; + + if(!this.table){ + target.addClass('x-table-layout-ct'); + + this.table = target.createChild( + Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true); + } + this.renderAll(ct, target); + }, + + // private + getRow : function(index){ + var row = this.table.tBodies[0].childNodes[index]; + if(!row){ + row = document.createElement('tr'); + this.table.tBodies[0].appendChild(row); + } + return row; + }, + + // private + getNextCell : function(c){ + var cell = this.getNextNonSpan(this.currentColumn, this.currentRow); + var curCol = this.currentColumn = cell[0], curRow = this.currentRow = cell[1]; + for(var rowIndex = curRow; rowIndex < curRow + (c.rowspan || 1); rowIndex++){ + if(!this.cells[rowIndex]){ + this.cells[rowIndex] = []; + } + for(var colIndex = curCol; colIndex < curCol + (c.colspan || 1); colIndex++){ + this.cells[rowIndex][colIndex] = true; + } + } + var td = document.createElement('td'); + if(c.cellId){ + td.id = c.cellId; + } + var cls = 'x-table-layout-cell'; + if(c.cellCls){ + cls += ' ' + c.cellCls; + } + td.className = cls; + if(c.colspan){ + td.colSpan = c.colspan; + } + if(c.rowspan){ + td.rowSpan = c.rowspan; + } + this.getRow(curRow).appendChild(td); + return td; + }, + + // private + getNextNonSpan: function(colIndex, rowIndex){ + var cols = this.columns; + while((cols && colIndex >= cols) || (this.cells[rowIndex] && this.cells[rowIndex][colIndex])) { + if(cols && colIndex >= cols){ + rowIndex++; + colIndex = 0; + }else{ + colIndex++; + } + } + return [colIndex, rowIndex]; + }, + + // private + renderItem : function(c, position, target){ + // Ensure we have our inner table to get cells to render into. + if(!this.table){ + this.table = target.createChild( + Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true); + } + if(c && !c.rendered){ + c.render(this.getNextCell(c)); + this.configureItem(c, position); + }else if(c && !this.isValidParent(c, target)){ + var container = this.getNextCell(c); + container.insertBefore(c.getPositionEl().dom, null); + c.container = Ext.get(container); + this.configureItem(c, position); + } + }, + + // private + isValidParent : function(c, target){ + return c.getPositionEl().up('table', 5).dom.parentNode === (target.dom || target); + }, + + destroy: function(){ + delete this.table; + Ext.layout.TableLayout.superclass.destroy.call(this); + } + /** - * @cfg {String/Object/Function} bodyStyle - * Custom CSS styles to be applied to the {@link #body} element in the format expected by - * {@link Ext.Element#applyStyles} (defaults to null). See {@link #bodyCfg}. + * @property activeItem + * @hide */ - /** - * @cfg {String} iconCls - * The CSS class selector that specifies a background image to be used as the header icon (defaults to ''). - *

      An example of specifying a custom icon class would be something like: - *

      
      -// specify the property in the config for the class:
      -     ...
      -     iconCls: 'my-icon'
      +});
       
      -// css class that specifies background image to be used as the icon image:
      -.my-icon { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
      +Ext.Container.LAYOUTS['table'] = Ext.layout.TableLayout;/**
      + * @class Ext.layout.AbsoluteLayout
      + * @extends Ext.layout.AnchorLayout
      + * 

      This is a layout that inherits the anchoring of {@link Ext.layout.AnchorLayout} and adds the + * ability for x/y positioning using the standard x and y component config options.

      + *

      This class is intended to be extended or created via the {@link Ext.Container#layout layout} + * configuration property. See {@link Ext.Container#layout} for additional details.

      + *

      Example usage:

      + *
      
      +var form = new Ext.form.FormPanel({
      +    title: 'Absolute Layout',
      +    layout:'absolute',
      +    layoutConfig: {
      +        // layout-specific configs go here
      +        extraCls: 'x-abs-layout-item',
      +    },
      +    baseCls: 'x-plain',
      +    url:'save-form.php',
      +    defaultType: 'textfield',
      +    items: [{
      +        x: 0,
      +        y: 5,
      +        xtype:'label',
      +        text: 'Send To:'
      +    },{
      +        x: 60,
      +        y: 0,
      +        name: 'to',
      +        anchor:'100%'  // anchor width by percentage
      +    },{
      +        x: 0,
      +        y: 35,
      +        xtype:'label',
      +        text: 'Subject:'
      +    },{
      +        x: 60,
      +        y: 30,
      +        name: 'subject',
      +        anchor: '100%'  // anchor width by percentage
      +    },{
      +        x:0,
      +        y: 60,
      +        xtype: 'textarea',
      +        name: 'msg',
      +        anchor: '100% 100%'  // anchor width and height
      +    }]
      +});
       
      - */ + */ +Ext.layout.AbsoluteLayout = Ext.extend(Ext.layout.AnchorLayout, { + + extraCls: 'x-abs-layout-item', + + type: 'absolute', + + onLayout : function(ct, target){ + target.position(); + this.paddingLeft = target.getPadding('l'); + this.paddingTop = target.getPadding('t'); + Ext.layout.AbsoluteLayout.superclass.onLayout.call(this, ct, target); + }, + + // private + adjustWidthAnchor : function(value, comp){ + return value ? value - comp.getPosition(true)[0] + this.paddingLeft : value; + }, + + // private + adjustHeightAnchor : function(value, comp){ + return value ? value - comp.getPosition(true)[1] + this.paddingTop : value; + } /** - * @cfg {Boolean} collapsible - * True to make the panel collapsible and have the expand/collapse toggle button automatically rendered into - * the header tool button area, false to keep the panel statically sized with no button (defaults to false). + * @property activeItem + * @hide */ - /** - * @cfg {Array} tools - * An array of tool button configs to be added to the header tool area. When rendered, each tool is - * stored as an {@link Ext.Element Element} referenced by a public property called tools.<tool-type> - *

      Each tool config may contain the following properties: +}); +Ext.Container.LAYOUTS['absolute'] = Ext.layout.AbsoluteLayout; +/** + * @class Ext.layout.BoxLayout + * @extends Ext.layout.ContainerLayout + *

      Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.

      + */ +Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, { + /** + * @cfg {Object} defaultMargins + *

      If the individual contained items do not have a margins + * property specified, the default margins from this property will be + * applied to each item.

      + *

      This property may be specified as an object containing margins + * to apply in the format:

      
      +{
      +    top: (top margin),
      +    right: (right margin),
      +    bottom: (bottom margin),
      +    left: (left margin)
      +}
      + *

      This property may also be specified as a string containing + * space-separated, numeric margin values. The order of the sides associated + * with each value matches the way CSS processes margin values:

      *
        - *
      • id : String
        Required. The type - * of tool to create. By default, this assigns a CSS class of the form x-tool-<tool-type> to the - * resulting tool Element. Ext provides CSS rules, and an icon sprite containing images for the tool types listed below. - * The developer may implement custom tools by supplying alternate CSS rules and background images: - *
          - *
          toggle (Created by default when {@link #collapsible} is true)
          - *
          close
          - *
          minimize
          - *
          maximize
          - *
          restore
          - *
          gear
          - *
          pin
          - *
          unpin
          - *
          right
          - *
          left
          - *
          up
          - *
          down
          - *
          refresh
          - *
          minus
          - *
          plus
          - *
          help
          - *
          search
          - *
          save
          - *
          print
          - *
      • - *
      • handler : Function
        Required. The function to - * call when clicked. Arguments passed are:
          - *
        • event : Ext.EventObject
          The click event.
        • - *
        • toolEl : Ext.Element
          The tool Element.
        • - *
        • panel : Ext.Panel
          The host Panel
        • - *
        • tc : Ext.Panel
          The tool configuration object
        • - *
      • - *
      • stopEvent : Boolean
        Defaults to true. Specify as false to allow click event to propagate.
      • - *
      • scope : Object
        The scope in which to call the handler.
      • - *
      • qtip : String/Object
        A tip string, or - * a config argument to {@link Ext.QuickTip#register}
      • - *
      • hidden : Boolean
        True to initially render hidden.
      • - *
      • on : Object
        A listener config object specifiying - * event listeners in the format of an argument to {@link #addListener}
      • + *
      • If there is only one value, it applies to all sides.
      • + *
      • If there are two values, the top and bottom borders are set to the + * first value and the right and left are set to the second.
      • + *
      • If there are three values, the top is set to the first value, the left + * and right are set to the second, and the bottom is set to the third.
      • + *
      • If there are four values, they apply to the top, right, bottom, and + * left, respectively.
      • *
      - *

      Note that, apart from the toggle tool which is provided when a panel is collapsible, these - * tools only provide the visual button. Any required functionality must be provided by adding - * handlers that implement the necessary behavior.

      - *

      Example usage:

      - *
      
      -tools:[{
      -    id:'refresh',
      -    qtip: 'Refresh form Data',
      -    // hidden:true,
      -    handler: function(event, toolEl, panel){
      -        // refresh logic
      -    }
      -},
      -{
      -    id:'help',
      -    qtip: 'Get Help',
      -    handler: function(event, toolEl, panel){
      -        // whatever
      -    }
      -}]
      -
      - *

      For the custom id of 'help' define two relevant css classes with a link to - * a 15x15 image:

      - *
      
      -.x-tool-help {background-image: url(images/help.png);}
      -.x-tool-help-over {background-image: url(images/help_over.png);}
      -// if using an image sprite:
      -.x-tool-help {background-image: url(images/help.png) no-repeat 0 0;}
      -.x-tool-help-over {background-position:-15px 0;}
      -
      + *

      Defaults to:

      
      +     * {top:0, right:0, bottom:0, left:0}
      +     * 
      */ + defaultMargins : {left:0,top:0,right:0,bottom:0}, /** - * @cfg {Ext.Template/Ext.XTemplate} toolTemplate - *

      A Template used to create {@link #tools} in the {@link #header} Element. Defaults to:

      
      -new Ext.Template('<div class="x-tool x-tool-{id}">&#160;</div>')
      - *

      This may may be overridden to provide a custom DOM structure for tools based upon a more - * complex XTemplate. The template's data is a single tool configuration object (Not the entire Array) - * as specified in {@link #tools}. In the following example an <a> tag is used to provide a - * visual indication when hovering over the tool:

      
      -var win = new Ext.Window({
      -    tools: [{
      -        id: 'download',
      -        href: '/MyPdfDoc.pdf'
      -    }],
      -    toolTemplate: new Ext.XTemplate(
      -        '<tpl if="id==\'download\'">',
      -            '<a class="x-tool x-tool-pdf" href="{href}"></a>',
      -        '</tpl>',
      -        '<tpl if="id!=\'download\'">',
      -            '<div class="x-tool x-tool-{id}">&#160;</div>',
      -        '</tpl>'
      -    ),
      -    width:500,
      -    height:300,
      -    closeAction:'hide'
      -});
      - *

      Note that the CSS class "x-tool-pdf" should have an associated style rule which provides an - * appropriate background image, something like:

      -
      
      -    a.x-tool-pdf {background-image: url(../shared/extjs/images/pdf.gif)!important;}
      -    
      + * @cfg {String} padding + *

      Sets the padding to be applied to all child items managed by this layout.

      + *

      This property must be specified as a string containing + * space-separated, numeric padding values. The order of the sides associated + * with each value matches the way CSS processes padding values:

      + *
        + *
      • If there is only one value, it applies to all sides.
      • + *
      • If there are two values, the top and bottom borders are set to the + * first value and the right and left are set to the second.
      • + *
      • If there are three values, the top is set to the first value, the left + * and right are set to the second, and the bottom is set to the third.
      • + *
      • If there are four values, they apply to the top, right, bottom, and + * left, respectively.
      • + *
      + *

      Defaults to: "0"

      */ + padding : '0', + // documented in subclasses + pack : 'start', + + // private + monitorResize : true, + type: 'box', + scrollOffset : 0, + extraCls : 'x-box-item', + targetCls : 'x-box-layout-ct', + innerCls : 'x-box-inner', + + constructor : function(config){ + Ext.layout.BoxLayout.superclass.constructor.call(this, config); + + if (Ext.isString(this.defaultMargins)) { + this.defaultMargins = this.parseMargins(this.defaultMargins); + } + }, + /** - * @cfg {Boolean} hideCollapseTool - * true to hide the expand/collapse toggle button when {@link #collapsible} == true, - * false to display it (defaults to false). + * @private + * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values + * when laying out */ + onLayout: function(container, target) { + Ext.layout.BoxLayout.superclass.onLayout.call(this, container, target); + + var items = this.getVisibleItems(container), + tSize = this.getLayoutTargetSize(); + + /** + * @private + * @property layoutTargetLastSize + * @type Object + * Private cache of the last measured size of the layout target. This should never be used except by + * BoxLayout subclasses during their onLayout run. + */ + this.layoutTargetLastSize = tSize; + + /** + * @private + * @property childBoxCache + * @type Array + * Array of the last calculated height, width, top and left positions of each visible rendered component + * within the Box layout. + */ + this.childBoxCache = this.calculateChildBoxes(items, tSize); + + this.updateInnerCtSize(tSize, this.childBoxCache); + this.updateChildBoxes(this.childBoxCache.boxes); + + // Putting a box layout into an overflowed container is NOT correct and will make a second layout pass necessary. + this.handleTargetOverflow(tSize, container, target); + }, + /** - * @cfg {Boolean} titleCollapse - * true to allow expanding and collapsing the panel (when {@link #collapsible} = true) - * by clicking anywhere in the header bar, false) to allow it only by clicking to tool button - * (defaults to false)). If this panel is a child item of a border layout also see the - * {@link Ext.layout.BorderLayout.Region BorderLayout.Region} - * {@link Ext.layout.BorderLayout.Region#floatable floatable} config option. + * Resizes and repositions each child component + * @param {Array} boxes The box measurements */ + updateChildBoxes: function(boxes) { + for (var i = 0, length = boxes.length; i < length; i++) { + var box = boxes[i], + comp = box.component; + + if (box.dirtySize) { + comp.setSize(box.width, box.height); + } + // Don't set positions to NaN + if (isNaN(box.left) || isNaN(box.top)) { + continue; + } + comp.setPosition(box.left, box.top); + } + }, + /** - * @cfg {Boolean} autoScroll - * true to use overflow:'auto' on the panel's body element and show scroll bars automatically when - * necessary, false to clip any overflowing content (defaults to false). + * @private + * Called by onRender just before the child components are sized and positioned. This resizes the innerCt + * to make sure all child items fit within it. We call this before sizing the children because if our child + * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them + * again immediately afterwards, giving a performance hit. + * Subclasses should provide an implementation. + * @param {Object} currentSize The current height and width of the innerCt + * @param {Array} calculations The new box calculations of all items to be laid out */ + updateInnerCtSize: Ext.emptyFn, + /** - * @cfg {Mixed} floating - *

      This property is used to configure the underlying {@link Ext.Layer}. Acceptable values for this - * configuration property are:

        - *
      • false : Default.
        Display the panel inline where it is - * rendered.
      • - *
      • true :
        Float the panel (absolute position it with automatic - * shimming and shadow).
          - *
          Setting floating to true will create an Ext.Layer for this panel and display the - * panel at negative offsets so that it is hidden.
          - *
          Since the panel will be absolute positioned, the position must be set explicitly - * after render (e.g., myPanel.setPosition(100,100);).
          - *
          Note: when floating a panel you should always assign a fixed width, - * otherwise it will be auto width and will expand to fill to the right edge of the viewport.
          - *
      • - *
      • {@link Ext.Layer object} :
        The specified object will be used - * as the configuration object for the {@link Ext.Layer} that will be created.
      • + * @private + * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden', + * we need to lay out a second time because the scrollbars may have modified the height and width of the layout + * target. Having a Box layout inside such a target is therefore not recommended. + * @param {Object} previousTargetSize The size and height of the layout target before we just laid out + * @param {Ext.Container} container The container + * @param {Ext.Element} target The target element + */ + handleTargetOverflow: function(previousTargetSize, container, target) { + var overflow = target.getStyle('overflow'); + + if (overflow && overflow != 'hidden' &&!this.adjustmentPass) { + var newTargetSize = this.getLayoutTargetSize(); + if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height){ + this.adjustmentPass = true; + this.onLayout(container, target); + } + } + + delete this.adjustmentPass; + }, + + // private + isValidParent : function(c, target){ + return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom; + }, + + /** + * @private + * Returns all items that are both rendered and visible + * @return {Array} All matching items + */ + getVisibleItems: function(ct) { + var ct = ct || this.container, + t = ct.getLayoutTarget(), + cti = ct.items.items, + len = cti.length, + + i, c, items = []; + + for (i = 0; i < len; i++) { + if((c = cti[i]).rendered && this.isValidParent(c, t) && c.hidden !== true && c.collapsed !== true){ + items.push(c); + } + } + + return items; + }, + + // private + renderAll : function(ct, target){ + if(!this.innerCt){ + // the innerCt prevents wrapping and shuffling while + // the container is resizing + this.innerCt = target.createChild({cls:this.innerCls}); + this.padding = this.parseMargins(this.padding); + } + Ext.layout.BoxLayout.superclass.renderAll.call(this, ct, this.innerCt); + }, + + getLayoutTargetSize : function(){ + var target = this.container.getLayoutTarget(), ret; + if (target) { + ret = target.getViewSize(); + + // IE in strict mode will return a width of 0 on the 1st pass of getViewSize. + // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly + // with getViewSize + if (Ext.isIE && Ext.isStrict && ret.width == 0){ + ret = target.getStyleSize(); + } + + ret.width -= target.getPadding('lr'); + ret.height -= target.getPadding('tb'); + } + return ret; + }, + + // private + renderItem : function(c){ + if(Ext.isString(c.margins)){ + c.margins = this.parseMargins(c.margins); + }else if(!c.margins){ + c.margins = this.defaultMargins; + } + Ext.layout.BoxLayout.superclass.renderItem.apply(this, arguments); + } +}); + +/** + * @class Ext.layout.VBoxLayout + * @extends Ext.layout.BoxLayout + *

        A layout that arranges items vertically down a Container. This layout optionally divides available vertical + * space between child items containing a numeric flex configuration.

        + * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option. + */ +Ext.layout.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, { + /** + * @cfg {String} align + * Controls how the child items of the container are aligned. Acceptable configuration values for this + * property are: + *
          + *
        • left : Default
          child items are aligned horizontally + * at the left side of the container
        • + *
        • center :
          child items are aligned horizontally at the + * mid-width of the container
        • + *
        • stretch :
          child items are stretched horizontally to fill + * the width of the container
        • + *
        • stretchmax :
          child items are stretched horizontally to + * the size of the largest item.
        • + *
        + */ + align : 'left', // left, center, stretch, strechmax + type: 'vbox', + + /** + * @cfg {String} pack + * Controls how the child items of the container are packed together. Acceptable configuration values + * for this property are: + *
          + *
        • start : Default
          child items are packed together at + * top side of container
        • + *
        • center :
          child items are packed together at + * mid-height of container
        • + *
        • end :
          child items are packed together at bottom + * side of container
        • + *
        + */ + + /** + * @cfg {Number} flex + * This configuation option is to be applied to child items of the container managed + * by this layout. Each child item with a flex property will be flexed vertically + * according to each item's relative flex value compared to the sum of all items with + * a flex value specified. Any child items that have either a flex = 0 or + * flex = undefined will not be 'flexed' (the initial size will not be changed). + */ + + /** + * @private + * See parent documentation + */ + updateInnerCtSize: function(tSize, calcs) { + var innerCtHeight = tSize.height, + innerCtWidth = calcs.meta.maxWidth + this.padding.left + this.padding.right; + + if (this.align == 'stretch') { + innerCtWidth = tSize.width; + } else if (this.align == 'center') { + innerCtWidth = Math.max(tSize.width, innerCtWidth); + } + + //we set the innerCt size first because if our child items are larger than the previous innerCt size + //the browser will insert scrollbars and then remove them again immediately afterwards + this.innerCt.setSize(innerCtWidth || undefined, innerCtHeight || undefined); + }, + + /** + * @private + * Calculates the size and positioning of each item in the VBox. This iterates over all of the rendered, + * visible items and returns a height, width, top and left for each, as well as a reference to each. Also + * returns meta data such as maxHeight which are useful when resizing layout wrappers such as this.innerCt. + * @param {Array} visibleItems The array of all rendered, visible items to be calculated for + * @param {Object} targetSize Object containing target size and height + * @return {Object} Object containing box measurements for each child, plus meta data + */ + calculateChildBoxes: function(visibleItems, targetSize) { + var visibleCount = visibleItems.length, + + padding = this.padding, + topOffset = padding.top, + leftOffset = padding.left, + paddingVert = topOffset + padding.bottom, + paddingHoriz = leftOffset + padding.right, + + width = targetSize.width - this.scrollOffset, + height = targetSize.height, + availWidth = Math.max(0, width - paddingHoriz), + + isStart = this.pack == 'start', + isCenter = this.pack == 'center', + isEnd = this.pack == 'end', + + nonFlexHeight= 0, + maxWidth = 0, + totalFlex = 0, + + //used to cache the calculated size and position values for each child item + boxes = [], + + //used in the for loops below, just declared here for brevity + child, childWidth, childHeight, childSize, childMargins, canLayout, i, calcs, flexedHeight, horizMargins, stretchWidth; + + //gather the total flex of all flexed items and the width taken up by fixed width items + for (i = 0; i < visibleCount; i++) { + child = visibleItems[i]; + childHeight = child.height; + childWidth = child.width; + canLayout = !child.hasLayout && Ext.isFunction(child.doLayout); + + + // Static height (numeric) requires no calcs + if (!Ext.isNumber(childHeight)) { + + // flex and not 'auto' height + if (child.flex && !childHeight) { + totalFlex += child.flex; + + // Not flexed or 'auto' height or undefined height + } else { + //Render and layout sub-containers without a flex or width defined, as otherwise we + //don't know how wide the sub-container should be and cannot calculate flexed widths + if (!childHeight && canLayout) { + child.doLayout(); + } + + childSize = child.getSize(); + childWidth = childSize.width; + childHeight = childSize.height; + } + } + + childMargins = child.margins; + + nonFlexHeight += (childHeight || 0) + childMargins.top + childMargins.bottom; + + // Max width for align - force layout of non-layed out subcontainers without a numeric width + if (!Ext.isNumber(childWidth)) { + if (canLayout) { + child.doLayout(); + } + childWidth = child.getWidth(); + } + + maxWidth = Math.max(maxWidth, childWidth + childMargins.left + childMargins.right); + + //cache the size of each child component + boxes.push({ + component: child, + height : childHeight || undefined, + width : childWidth || undefined + }); + } + + //the height available to the flexed items + var availableHeight = Math.max(0, (height - nonFlexHeight - paddingVert)); + + if (isCenter) { + topOffset += availableHeight / 2; + } else if (isEnd) { + topOffset += availableHeight; + } + + //temporary variables used in the flex height calculations below + var remainingHeight = availableHeight, + remainingFlex = totalFlex; + + //calculate the height of each flexed item, and the left + top positions of every item + for (i = 0; i < visibleCount; i++) { + child = visibleItems[i]; + calcs = boxes[i]; + + childMargins = child.margins; + horizMargins = childMargins.left + childMargins.right; + + topOffset += childMargins.top; + + if (isStart && child.flex && !child.height) { + flexedHeight = Math.ceil((child.flex / remainingFlex) * remainingHeight); + remainingHeight -= flexedHeight; + remainingFlex -= child.flex; + + calcs.height = flexedHeight; + calcs.dirtySize = true; + } + + calcs.left = leftOffset + childMargins.left; + calcs.top = topOffset; + + switch (this.align) { + case 'stretch': + stretchWidth = availWidth - horizMargins; + calcs.width = stretchWidth.constrain(child.minWidth || 0, child.maxWidth || 1000000); + calcs.dirtySize = true; + break; + case 'stretchmax': + stretchWidth = maxWidth - horizMargins; + calcs.width = stretchWidth.constrain(child.minWidth || 0, child.maxWidth || 1000000); + calcs.dirtySize = true; + break; + case 'center': + var diff = availWidth - calcs.width - horizMargins; + if (diff > 0) { + calcs.left = leftOffset + horizMargins + (diff / 2); + } + } + + topOffset += calcs.height + childMargins.bottom; + } + + return { + boxes: boxes, + meta : { + maxWidth: maxWidth + } + }; + } +}); + +Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout; + +/** + * @class Ext.layout.HBoxLayout + * @extends Ext.layout.BoxLayout + *

        A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal + * space between child items containing a numeric flex configuration.

        + * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option. + */ +Ext.layout.HBoxLayout = Ext.extend(Ext.layout.BoxLayout, { + /** + * @cfg {String} align + * Controls how the child items of the container are aligned. Acceptable configuration values for this + * property are: + *
          + *
        • top : Default
          child items are aligned vertically + * at the top of the container
        • + *
        • middle :
          child items are aligned vertically in the + * middle of the container
        • + *
        • stretch :
          child items are stretched vertically to fill + * the height of the container
        • + *
        • stretchmax :
          child items are stretched vertically to + * the height of the largest item.
        • + */ + align: 'top', // top, middle, stretch, strechmax + + type : 'hbox', + + /** + * @private + * See parent documentation + */ + updateInnerCtSize: function(tSize, calcs) { + var innerCtWidth = tSize.width, + innerCtHeight = calcs.meta.maxHeight + this.padding.top + this.padding.bottom; + + if (this.align == 'stretch') { + innerCtHeight = tSize.height; + } else if (this.align == 'middle') { + innerCtHeight = Math.max(tSize.height, innerCtHeight); + } + + this.innerCt.setSize(innerCtWidth || undefined, innerCtHeight || undefined); + }, + + /** + * @cfg {String} pack + * Controls how the child items of the container are packed together. Acceptable configuration values + * for this property are: + *
            + *
          • start : Default
            child items are packed together at + * left side of container
          • + *
          • center :
            child items are packed together at + * mid-width of container
          • + *
          • end :
            child items are packed together at right + * side of container
          • *
          */ /** - * @cfg {Boolean/String} shadow - * true (or a valid Ext.Shadow {@link Ext.Shadow#mode} value) to display a shadow behind the - * panel, false to display no shadow (defaults to 'sides'). Note that this option - * only applies when {@link #floating} = true. + * @cfg {Number} flex + * This configuation option is to be applied to child items of the container managed + * by this layout. Each child item with a flex property will be flexed horizontally + * according to each item's relative flex value compared to the sum of all items with + * a flex value specified. Any child items that have either a flex = 0 or + * flex = undefined will not be 'flexed' (the initial size will not be changed). + */ + + /** + * @private + * Calculates the size and positioning of each item in the HBox. This iterates over all of the rendered, + * visible items and returns a height, width, top and left for each, as well as a reference to each. Also + * returns meta data such as maxHeight which are useful when resizing layout wrappers such as this.innerCt. + * @param {Array} visibleItems The array of all rendered, visible items to be calculated for + * @param {Object} targetSize Object containing target size and height + * @return {Object} Object containing box measurements for each child, plus meta data + */ + calculateChildBoxes: function(visibleItems, targetSize) { + var visibleCount = visibleItems.length, + + padding = this.padding, + topOffset = padding.top, + leftOffset = padding.left, + paddingVert = topOffset + padding.bottom, + paddingHoriz = leftOffset + padding.right, + + width = targetSize.width - this.scrollOffset, + height = targetSize.height, + availHeight = Math.max(0, height - paddingVert), + + isStart = this.pack == 'start', + isCenter = this.pack == 'center', + isEnd = this.pack == 'end', + // isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1, + + nonFlexWidth = 0, + maxHeight = 0, + totalFlex = 0, + + //used to cache the calculated size and position values for each child item + boxes = [], + + //used in the for loops below, just declared here for brevity + child, childWidth, childHeight, childSize, childMargins, canLayout, i, calcs, flexedWidth, vertMargins, stretchHeight; + + //gather the total flex of all flexed items and the width taken up by fixed width items + for (i = 0; i < visibleCount; i++) { + child = visibleItems[i]; + childHeight = child.height; + childWidth = child.width; + canLayout = !child.hasLayout && Ext.isFunction(child.doLayout); + + // Static width (numeric) requires no calcs + if (!Ext.isNumber(childWidth)) { + + // flex and not 'auto' width + if (child.flex && !childWidth) { + totalFlex += child.flex; + + // Not flexed or 'auto' width or undefined width + } else { + //Render and layout sub-containers without a flex or width defined, as otherwise we + //don't know how wide the sub-container should be and cannot calculate flexed widths + if (!childWidth && canLayout) { + child.doLayout(); + } + + childSize = child.getSize(); + childWidth = childSize.width; + childHeight = childSize.height; + } + } + + childMargins = child.margins; + + nonFlexWidth += (childWidth || 0) + childMargins.left + childMargins.right; + + // Max height for align - force layout of non-layed out subcontainers without a numeric height + if (!Ext.isNumber(childHeight)) { + if (canLayout) { + child.doLayout(); + } + childHeight = child.getHeight(); + } + + maxHeight = Math.max(maxHeight, childHeight + childMargins.top + childMargins.bottom); + + //cache the size of each child component + boxes.push({ + component: child, + height : childHeight || undefined, + width : childWidth || undefined + }); + } + + //the width available to the flexed items + var availableWidth = Math.max(0, (width - nonFlexWidth - paddingHoriz)); + + if (isCenter) { + leftOffset += availableWidth / 2; + } else if (isEnd) { + leftOffset += availableWidth; + } + + //temporary variables used in the flex width calculations below + var remainingWidth = availableWidth, + remainingFlex = totalFlex; + + //calculate the widths of each flexed item, and the left + top positions of every item + for (i = 0; i < visibleCount; i++) { + child = visibleItems[i]; + calcs = boxes[i]; + + childMargins = child.margins; + vertMargins = childMargins.top + childMargins.bottom; + + leftOffset += childMargins.left; + + if (isStart && child.flex && !child.width) { + flexedWidth = Math.ceil((child.flex / remainingFlex) * remainingWidth); + remainingWidth -= flexedWidth; + remainingFlex -= child.flex; + + calcs.width = flexedWidth; + calcs.dirtySize = true; + } + + calcs.left = leftOffset; + calcs.top = topOffset + childMargins.top; + + switch (this.align) { + case 'stretch': + stretchHeight = availHeight - vertMargins; + calcs.height = stretchHeight.constrain(child.minHeight || 0, child.maxHeight || 1000000); + calcs.dirtySize = true; + break; + case 'stretchmax': + stretchHeight = maxHeight - vertMargins; + calcs.height = stretchHeight.constrain(child.minHeight || 0, child.maxHeight || 1000000); + calcs.dirtySize = true; + break; + case 'middle': + var diff = availHeight - calcs.height - vertMargins; + if (diff > 0) { + calcs.top = topOffset + vertMargins + (diff / 2); + } + } + leftOffset += calcs.width + childMargins.right; + } + + return { + boxes: boxes, + meta : { + maxHeight: maxHeight + } + }; + } +}); + +Ext.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout; +/** + * @class Ext.layout.ToolbarLayout + * @extends Ext.layout.ContainerLayout + * Layout manager used by Ext.Toolbar. This is highly specialised for use by Toolbars and would not + * usually be used by any other class. + */ +Ext.layout.ToolbarLayout = Ext.extend(Ext.layout.ContainerLayout, { + monitorResize : true, + + type: 'toolbar', + + /** + * @property triggerWidth + * @type Number + * The width allocated for the menu trigger at the extreme right end of the Toolbar + */ + triggerWidth: 18, + + /** + * @property noItemsMenuText + * @type String + * HTML fragment to render into the toolbar overflow menu if there are no items to display + */ + noItemsMenuText : '
          (None)
          ', + + /** + * @private + * @property lastOverflow + * @type Boolean + * Used internally to record whether the last layout caused an overflow or not + */ + lastOverflow: false, + + /** + * @private + * @property tableHTML + * @type String + * String used to build the HTML injected to support the Toolbar's layout. The align property is + * injected into this string inside the td.x-toolbar-left element during onLayout. + */ + tableHTML: [ + '', + '', + '', + '', + '', + '', + '', + '
          ', + '', + '', + '', + '', + '
          ', + '
          ', + '', + '', + '', + '', + '', + '', + '', + '
          ', + '', + '', + '', + '', + '
          ', + '
          ', + '', + '', + '', + '', + '
          ', + '
          ', + '
          ' + ].join(""), + + /** + * @private + * Create the wrapping Toolbar HTML and render/move all the items into the correct places + */ + onLayout : function(ct, target) { + //render the Toolbar HTML if it's not already present + if (!this.leftTr) { + var align = ct.buttonAlign == 'center' ? 'center' : 'left'; + + target.addClass('x-toolbar-layout-ct'); + target.insertHtml('beforeEnd', String.format(this.tableHTML, align)); + + this.leftTr = target.child('tr.x-toolbar-left-row', true); + this.rightTr = target.child('tr.x-toolbar-right-row', true); + this.extrasTr = target.child('tr.x-toolbar-extras-row', true); + + if (this.hiddenItem == undefined) { + /** + * @property hiddenItems + * @type Array + * Holds all items that are currently hidden due to there not being enough space to render them + * These items will appear on the expand menu. + */ + this.hiddenItems = []; + } + } + + var side = ct.buttonAlign == 'right' ? this.rightTr : this.leftTr, + items = ct.items.items, + position = 0; + + //render each item if not already rendered, place it into the correct (left or right) target + for (var i = 0, len = items.length, c; i < len; i++, position++) { + c = items[i]; + + if (c.isFill) { + side = this.rightTr; + position = -1; + } else if (!c.rendered) { + c.render(this.insertCell(c, side, position)); + } else { + if (!c.xtbHidden && !this.isValidParent(c, side.childNodes[position])) { + var td = this.insertCell(c, side, position); + td.appendChild(c.getPositionEl().dom); + c.container = Ext.get(td); + } + } + } + + //strip extra empty cells + this.cleanup(this.leftTr); + this.cleanup(this.rightTr); + this.cleanup(this.extrasTr); + this.fitToSize(target); + }, + + /** + * @private + * Removes any empty nodes from the given element + * @param {Ext.Element} el The element to clean up + */ + cleanup : function(el) { + var cn = el.childNodes, i, c; + + for (i = cn.length-1; i >= 0 && (c = cn[i]); i--) { + if (!c.firstChild) { + el.removeChild(c); + } + } + }, + + /** + * @private + * Inserts the given Toolbar item into the given element + * @param {Ext.Component} c The component to add + * @param {Ext.Element} target The target to add the component to + * @param {Number} position The position to add the component at + */ + insertCell : function(c, target, position) { + var td = document.createElement('td'); + td.className = 'x-toolbar-cell'; + + target.insertBefore(td, target.childNodes[position] || null); + + return td; + }, + + /** + * @private + * Hides an item because it will not fit in the available width. The item will be unhidden again + * if the Toolbar is resized to be large enough to show it + * @param {Ext.Component} item The item to hide + */ + hideItem : function(item) { + this.hiddenItems.push(item); + + item.xtbHidden = true; + item.xtbWidth = item.getPositionEl().dom.parentNode.offsetWidth; + item.hide(); + }, + + /** + * @private + * Unhides an item that was previously hidden due to there not being enough space left on the Toolbar + * @param {Ext.Component} item The item to show + */ + unhideItem : function(item) { + item.show(); + item.xtbHidden = false; + this.hiddenItems.remove(item); + }, + + /** + * @private + * Returns the width of the given toolbar item. If the item is currently hidden because there + * is not enough room to render it, its previous width is returned + * @param {Ext.Component} c The component to measure + * @return {Number} The width of the item + */ + getItemWidth : function(c) { + return c.hidden ? (c.xtbWidth || 0) : c.getPositionEl().dom.parentNode.offsetWidth; + }, + + /** + * @private + * Called at the end of onLayout. At this point the Toolbar has already been resized, so we need + * to fit the items into the available width. We add up the width required by all of the items in + * the toolbar - if we don't have enough space we hide the extra items and render the expand menu + * trigger. + * @param {Ext.Element} target The Element the Toolbar is currently laid out within + */ + fitToSize : function(target) { + if (this.container.enableOverflow === false) { + return; + } + + var width = target.dom.clientWidth, + tableWidth = target.dom.firstChild.offsetWidth, + clipWidth = width - this.triggerWidth, + lastWidth = this.lastWidth || 0, + + hiddenItems = this.hiddenItems, + hasHiddens = hiddenItems.length != 0, + isLarger = width >= lastWidth; + + this.lastWidth = width; + + if (tableWidth > width || (hasHiddens && isLarger)) { + var items = this.container.items.items, + len = items.length, + loopWidth = 0, + item; + + for (var i = 0; i < len; i++) { + item = items[i]; + + if (!item.isFill) { + loopWidth += this.getItemWidth(item); + if (loopWidth > clipWidth) { + if (!(item.hidden || item.xtbHidden)) { + this.hideItem(item); + } + } else if (item.xtbHidden) { + this.unhideItem(item); + } + } + } + } + + //test for number of hidden items again here because they may have changed above + hasHiddens = hiddenItems.length != 0; + + if (hasHiddens) { + this.initMore(); + + if (!this.lastOverflow) { + this.container.fireEvent('overflowchange', this.container, true); + this.lastOverflow = true; + } + } else if (this.more) { + this.clearMenu(); + this.more.destroy(); + delete this.more; + + if (this.lastOverflow) { + this.container.fireEvent('overflowchange', this.container, false); + this.lastOverflow = false; + } + } + }, + + /** + * @private + * Returns a menu config for a given component. This config is used to create a menu item + * to be added to the expander menu + * @param {Ext.Component} component The component to create the config for + * @param {Boolean} hideOnClick Passed through to the menu item + */ + createMenuConfig : function(component, hideOnClick){ + var config = Ext.apply({}, component.initialConfig), + group = component.toggleGroup; + + Ext.copyTo(config, component, [ + 'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu' + ]); + + Ext.apply(config, { + text : component.overflowText || component.text, + hideOnClick: hideOnClick + }); + + if (group || component.enableToggle) { + Ext.apply(config, { + group : group, + checked: component.pressed, + listeners: { + checkchange: function(item, checked){ + component.toggle(checked); + } + } + }); + } + + delete config.ownerCt; + delete config.xtype; + delete config.id; + + return config; + }, + + /** + * @private + * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually. + * @param {Ext.menu.Menu} menu The menu to add to + * @param {Ext.Component} component The component to add + */ + addComponentToMenu : function(menu, component) { + if (component instanceof Ext.Toolbar.Separator) { + menu.add('-'); + + } else if (Ext.isFunction(component.isXType)) { + if (component.isXType('splitbutton')) { + menu.add(this.createMenuConfig(component, true)); + + } else if (component.isXType('button')) { + menu.add(this.createMenuConfig(component, !component.menu)); + + } else if (component.isXType('buttongroup')) { + component.items.each(function(item){ + this.addComponentToMenu(menu, item); + }, this); + } + } + }, + + /** + * @private + * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as + * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item + */ + clearMenu : function(){ + var menu = this.moreMenu; + if (menu && menu.items) { + menu.items.each(function(item){ + delete item.menu; + }); + } + }, + + /** + * @private + * Called before the expand menu is shown, this rebuilds the menu since it was last shown because + * it is possible that the items hidden due to space limitations on the Toolbar have changed since. + * @param {Ext.menu.Menu} m The menu + */ + beforeMoreShow : function(menu) { + var items = this.container.items.items, + len = items.length, + item, + prev; + + var needsSep = function(group, item){ + return group.isXType('buttongroup') && !(item instanceof Ext.Toolbar.Separator); + }; + + this.clearMenu(); + menu.removeAll(); + for (var i = 0; i < len; i++) { + item = items[i]; + if (item.xtbHidden) { + if (prev && (needsSep(item, prev) || needsSep(prev, item))) { + menu.add('-'); + } + this.addComponentToMenu(menu, item); + prev = item; + } + } + + // put something so the menu isn't empty if no compatible items found + if (menu.items.length < 1) { + menu.add(this.noItemsMenuText); + } + }, + + /** + * @private + * Creates the expand trigger and menu, adding them to the at the extreme right of the + * Toolbar table + */ + initMore : function(){ + if (!this.more) { + /** + * @private + * @property moreMenu + * @type Ext.menu.Menu + * The expand menu - holds items for every Toolbar item that cannot be shown + * because the Toolbar is currently not wide enough. + */ + this.moreMenu = new Ext.menu.Menu({ + ownerCt : this.container, + listeners: { + beforeshow: this.beforeMoreShow, + scope: this + } + }); + + /** + * @private + * @property more + * @type Ext.Button + * The expand button which triggers the overflow menu to be shown + */ + this.more = new Ext.Button({ + iconCls: 'x-toolbar-more-icon', + cls : 'x-toolbar-more', + menu : this.moreMenu, + ownerCt: this.container + }); + + var td = this.insertCell(this.more, this.extrasTr, 100); + this.more.render(td); + } + }, + + destroy : function(){ + Ext.destroy(this.more, this.moreMenu); + delete this.leftTr; + delete this.rightTr; + delete this.extrasTr; + Ext.layout.ToolbarLayout.superclass.destroy.call(this); + } +}); + +Ext.Container.LAYOUTS.toolbar = Ext.layout.ToolbarLayout; +/** + * @class Ext.layout.MenuLayout + * @extends Ext.layout.ContainerLayout + *

          Layout manager used by {@link Ext.menu.Menu}. Generally this class should not need to be used directly.

          + */ + Ext.layout.MenuLayout = Ext.extend(Ext.layout.ContainerLayout, { + monitorResize : true, + + type: 'menu', + + setContainer : function(ct){ + this.monitorResize = !ct.floating; + // This event is only fired by the menu in IE, used so we don't couple + // the menu with the layout. + ct.on('autosize', this.doAutoSize, this); + Ext.layout.MenuLayout.superclass.setContainer.call(this, ct); + }, + + renderItem : function(c, position, target){ + if (!this.itemTpl) { + this.itemTpl = Ext.layout.MenuLayout.prototype.itemTpl = new Ext.XTemplate( + '
        • ', + '', + '', + '', + '
        • ' + ); + } + + if(c && !c.rendered){ + if(Ext.isNumber(position)){ + position = target.dom.childNodes[position]; + } + var a = this.getItemArgs(c); + +// The Component's positionEl is the
        • it is rendered into + c.render(c.positionEl = position ? + this.itemTpl.insertBefore(position, a, true) : + this.itemTpl.append(target, a, true)); + +// Link the containing
        • to the item. + c.positionEl.menuItemId = c.getItemId(); + +// If rendering a regular Component, and it needs an icon, +// move the Component rightwards. + if (!a.isMenuItem && a.needsIcon) { + c.positionEl.addClass('x-menu-list-item-indent'); + } + this.configureItem(c, position); + }else if(c && !this.isValidParent(c, target)){ + if(Ext.isNumber(position)){ + position = target.dom.childNodes[position]; + } + target.dom.insertBefore(c.getActionEl().dom, position || null); + } + }, + + getItemArgs : function(c) { + var isMenuItem = c instanceof Ext.menu.Item; + return { + isMenuItem: isMenuItem, + needsIcon: !isMenuItem && (c.icon || c.iconCls), + icon: c.icon || Ext.BLANK_IMAGE_URL, + iconCls: 'x-menu-item-icon ' + (c.iconCls || ''), + itemId: 'x-menu-el-' + c.id, + itemCls: 'x-menu-list-item ' + }; + }, + + // Valid if the Component is in a
        • which is part of our target
            + isValidParent : function(c, target) { + return c.el.up('li.x-menu-list-item', 5).dom.parentNode === (target.dom || target); + }, + + onLayout : function(ct, target){ + Ext.layout.MenuLayout.superclass.onLayout.call(this, ct, target); + this.doAutoSize(); + }, + + doAutoSize : function(){ + var ct = this.container, w = ct.width; + if(ct.floating){ + if(w){ + ct.setWidth(w); + }else if(Ext.isIE){ + ct.setWidth(Ext.isStrict && (Ext.isIE7 || Ext.isIE8) ? 'auto' : ct.minWidth); + var el = ct.getEl(), t = el.dom.offsetWidth; // force recalc + ct.setWidth(ct.getLayoutTarget().getWidth() + el.getFrameWidth('lr')); + } + } + } +}); +Ext.Container.LAYOUTS['menu'] = Ext.layout.MenuLayout; +/** + * @class Ext.Viewport + * @extends Ext.Container + *

            A specialized container representing the viewable application area (the browser viewport).

            + *

            The Viewport renders itself to the document body, and automatically sizes itself to the size of + * the browser viewport and manages window resizing. There may only be one Viewport created + * in a page. Inner layouts are available by virtue of the fact that all {@link Ext.Panel Panel}s + * added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add} + * method of any of its child Panels may themselves have a layout.

            + *

            The Viewport does not provide scrolling, so child Panels within the Viewport should provide + * for scrolling if needed using the {@link #autoScroll} config.

            + *

            An example showing a classic application border layout:

            
            +new Ext.Viewport({
            +    layout: 'border',
            +    items: [{
            +        region: 'north',
            +        html: '<h1 class="x-panel-header">Page Title</h1>',
            +        autoHeight: true,
            +        border: false,
            +        margins: '0 0 5 0'
            +    }, {
            +        region: 'west',
            +        collapsible: true,
            +        title: 'Navigation',
            +        width: 200
            +        // the west region might typically utilize a {@link Ext.tree.TreePanel TreePanel} or a Panel with {@link Ext.layout.AccordionLayout Accordion layout}
            +    }, {
            +        region: 'south',
            +        title: 'Title for Panel',
            +        collapsible: true,
            +        html: 'Information goes here',
            +        split: true,
            +        height: 100,
            +        minHeight: 100
            +    }, {
            +        region: 'east',
            +        title: 'Title for the Grid Panel',
            +        collapsible: true,
            +        split: true,
            +        width: 200,
            +        xtype: 'grid',
            +        // remaining grid configuration not shown ...
            +        // notice that the GridPanel is added directly as the region
            +        // it is not "overnested" inside another Panel
            +    }, {
            +        region: 'center',
            +        xtype: 'tabpanel', // TabPanel itself has no title
            +        items: {
            +            title: 'Default Tab',
            +            html: 'The first tab\'s content. Others may be added dynamically'
            +        }
            +    }]
            +});
            +
            + * @constructor + * Create a new Viewport + * @param {Object} config The config object + * @xtype viewport + */ +Ext.Viewport = Ext.extend(Ext.Container, { + /* + * Privatize config options which, if used, would interfere with the + * correct operation of the Viewport as the sole manager of the + * layout of the document body. + */ + /** + * @cfg {Mixed} applyTo @hide + */ + /** + * @cfg {Boolean} allowDomMove @hide + */ + /** + * @cfg {Boolean} hideParent @hide + */ + /** + * @cfg {Mixed} renderTo @hide + */ + /** + * @cfg {Boolean} hideParent @hide + */ + /** + * @cfg {Number} height @hide + */ + /** + * @cfg {Number} width @hide + */ + /** + * @cfg {Boolean} autoHeight @hide + */ + /** + * @cfg {Boolean} autoWidth @hide + */ + /** + * @cfg {Boolean} deferHeight @hide + */ + /** + * @cfg {Boolean} monitorResize @hide + */ + + initComponent : function() { + Ext.Viewport.superclass.initComponent.call(this); + document.getElementsByTagName('html')[0].className += ' x-viewport'; + this.el = Ext.getBody(); + this.el.setHeight = Ext.emptyFn; + this.el.setWidth = Ext.emptyFn; + this.el.setSize = Ext.emptyFn; + this.el.dom.scroll = 'no'; + this.allowDomMove = false; + this.autoWidth = true; + this.autoHeight = true; + Ext.EventManager.onWindowResize(this.fireResize, this); + this.renderTo = this.el; + }, + + fireResize : function(w, h){ + this.fireEvent('resize', this, w, h, w, h); + } +}); +Ext.reg('viewport', Ext.Viewport); +/** + * @class Ext.Panel + * @extends Ext.Container + *

            Panel is a container that has specific functionality and structural components that make + * it the perfect building block for application-oriented user interfaces.

            + *

            Panels are, by virtue of their inheritance from {@link Ext.Container}, capable + * of being configured with a {@link Ext.Container#layout layout}, and containing child Components.

            + *

            When either specifying child {@link Ext.Component#items items} of a Panel, or dynamically {@link Ext.Container#add adding} Components + * to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether + * those child elements need to be sized using one of Ext's built-in {@link Ext.Container#layout layout} schemes. By + * default, Panels use the {@link Ext.layout.ContainerLayout ContainerLayout} scheme. This simply renders + * child components, appending them one after the other inside the Container, and does not apply any sizing + * at all.

            + *

            A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate + * {@link #header}, {@link #footer} and {@link #body} sections (see {@link #frame} for additional + * information).

            + *

            Panel also provides built-in {@link #collapsible expandable and collapsible behavior}, along with + * a variety of {@link #tools prebuilt tool buttons} that can be wired up to provide other customized + * behavior. Panels can be easily dropped into any {@link Ext.Container Container} or layout, and the + * layout and rendering pipeline is {@link Ext.Container#add completely managed by the framework}.

            + * @constructor + * @param {Object} config The config object + * @xtype panel + */ +Ext.Panel = Ext.extend(Ext.Container, { + /** + * The Panel's header {@link Ext.Element Element}. Read-only. + *

            This Element is used to house the {@link #title} and {@link #tools}

            + *

            Note: see the Note for {@link Ext.Component#el el} also.

            + * @type Ext.Element + * @property header + */ + /** + * The Panel's body {@link Ext.Element Element} which may be used to contain HTML content. + * The content may be specified in the {@link #html} config, or it may be loaded using the + * {@link autoLoad} config, or through the Panel's {@link #getUpdater Updater}. Read-only. + *

            If this is used to load visible HTML elements in either way, then + * the Panel may not be used as a Layout for hosting nested Panels.

            + *

            If this Panel is intended to be used as the host of a Layout (See {@link #layout} + * then the body Element must not be loaded or changed - it is under the control + * of the Panel's Layout. + *

            Note: see the Note for {@link Ext.Component#el el} also.

            + * @type Ext.Element + * @property body + */ + /** + * The Panel's bwrap {@link Ext.Element Element} used to contain other Panel elements + * (tbar, body, bbar, footer). See {@link #bodyCfg}. Read-only. + * @type Ext.Element + * @property bwrap + */ + /** + * True if this panel is collapsed. Read-only. + * @type Boolean + * @property collapsed + */ + /** + * @cfg {Object} bodyCfg + *

            A {@link Ext.DomHelper DomHelper} element specification object may be specified for any + * Panel Element.

            + *

            By default, the Default element in the table below will be used for the html markup to + * create a child element with the commensurate Default class name (baseCls will be + * replaced by {@link #baseCls}):

            + *
            +     * Panel      Default  Default             Custom      Additional       Additional
            +     * Element    element  class               element     class            style
            +     * ========   ==========================   =========   ==============   ===========
            +     * {@link #header}     div      {@link #baseCls}+'-header'   {@link #headerCfg}   headerCssClass   headerStyle
            +     * {@link #bwrap}      div      {@link #baseCls}+'-bwrap'     {@link #bwrapCfg}    bwrapCssClass    bwrapStyle
            +     * + tbar     div      {@link #baseCls}+'-tbar'       {@link #tbarCfg}     tbarCssClass     tbarStyle
            +     * + {@link #body}     div      {@link #baseCls}+'-body'       {@link #bodyCfg}     {@link #bodyCssClass}     {@link #bodyStyle}
            +     * + bbar     div      {@link #baseCls}+'-bbar'       {@link #bbarCfg}     bbarCssClass     bbarStyle
            +     * + {@link #footer}   div      {@link #baseCls}+'-footer'   {@link #footerCfg}   footerCssClass   footerStyle
            +     * 
            + *

            Configuring a Custom element may be used, for example, to force the {@link #body} Element + * to use a different form of markup than is created by default. An example of this might be + * to {@link Ext.Element#createChild create a child} Panel containing a custom content, such as + * a header, or forcing centering of all Panel content by having the body be a <center> + * element:

            + *
            
            +new Ext.Panel({
            +    title: 'Message Title',
            +    renderTo: Ext.getBody(),
            +    width: 200, height: 130,
            +    bodyCfg: {
            +        tag: 'center',
            +        cls: 'x-panel-body',  // Default class not applied if Custom element specified
            +        html: 'Message'
            +    },
            +    footerCfg: {
            +        tag: 'h2',
            +        cls: 'x-panel-footer',        // same as the Default class
            +        html: 'footer html'
            +    },
            +    footerCssClass: 'custom-footer', // additional css class, see {@link Ext.element#addClass addClass}
            +    footerStyle:    'background-color:red' // see {@link #bodyStyle}
            +});
            +     * 
            + *

            The example above also explicitly creates a {@link #footer} with custom markup and + * styling applied.

            + */ + /** + * @cfg {Object} headerCfg + *

            A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure + * of this Panel's {@link #header} Element. See {@link #bodyCfg} also.

            + */ + /** + * @cfg {Object} bwrapCfg + *

            A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure + * of this Panel's {@link #bwrap} Element. See {@link #bodyCfg} also.

            + */ + /** + * @cfg {Object} tbarCfg + *

            A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure + * of this Panel's {@link #tbar} Element. See {@link #bodyCfg} also.

            + */ + /** + * @cfg {Object} bbarCfg + *

            A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure + * of this Panel's {@link #bbar} Element. See {@link #bodyCfg} also.

            + */ + /** + * @cfg {Object} footerCfg + *

            A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure + * of this Panel's {@link #footer} Element. See {@link #bodyCfg} also.

            + */ + /** + * @cfg {Boolean} closable + * Panels themselves do not directly support being closed, but some Panel subclasses do (like + * {@link Ext.Window}) or a Panel Class within an {@link Ext.TabPanel}. Specify true + * to enable closing in such situations. Defaults to false. + */ + /** + * The Panel's footer {@link Ext.Element Element}. Read-only. + *

            This Element is used to house the Panel's {@link #buttons} or {@link #fbar}.

            + *

            Note: see the Note for {@link Ext.Component#el el} also.

            + * @type Ext.Element + * @property footer + */ + /** + * @cfg {Mixed} applyTo + *

            The id of the node, a DOM node or an existing Element corresponding to a DIV that is already present in + * the document that specifies some panel-specific structural markup. When applyTo is used, + * constituent parts of the panel can be specified by CSS class name within the main element, and the panel + * will automatically create those components from that markup. Any required components not specified in the + * markup will be autogenerated if necessary.

            + *

            The following class names are supported (baseCls will be replaced by {@link #baseCls}):

            + *
            • baseCls + '-header'
            • + *
            • baseCls + '-header-text'
            • + *
            • baseCls + '-bwrap'
            • + *
            • baseCls + '-tbar'
            • + *
            • baseCls + '-body'
            • + *
            • baseCls + '-bbar'
            • + *
            • baseCls + '-footer'
            + *

            Using this config, a call to render() is not required. If applyTo is specified, any value passed for + * {@link #renderTo} will be ignored and the target element's parent node will automatically be used as the + * panel's container.

            + */ + /** + * @cfg {Object/Array} tbar + *

            The top toolbar of the panel. This can be a {@link Ext.Toolbar} object, a toolbar config, or an array of + * buttons/button configs to be added to the toolbar. Note that this is not available as a property after render. + * To access the top toolbar after render, use {@link #getTopToolbar}.

            + *

            Note: Although a Toolbar may contain Field components, these will not be updated by a load + * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and + * so are not scanned to collect form items. However, the values will be submitted because form + * submission parameters are collected from the DOM tree.

            + */ + /** + * @cfg {Object/Array} bbar + *

            The bottom toolbar of the panel. This can be a {@link Ext.Toolbar} object, a toolbar config, or an array of + * buttons/button configs to be added to the toolbar. Note that this is not available as a property after render. + * To access the bottom toolbar after render, use {@link #getBottomToolbar}.

            + *

            Note: Although a Toolbar may contain Field components, these will not be updated by a load + * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and + * so are not scanned to collect form items. However, the values will be submitted because form + * submission parameters are collected from the DOM tree.

            + */ + /** @cfg {Object/Array} fbar + *

            A {@link Ext.Toolbar Toolbar} object, a Toolbar config, or an array of + * {@link Ext.Button Button}s/{@link Ext.Button Button} configs, describing a {@link Ext.Toolbar Toolbar} to be rendered into this Panel's footer element.

            + *

            After render, the fbar property will be an {@link Ext.Toolbar Toolbar} instance.

            + *

            If {@link #buttons} are specified, they will supersede the fbar configuration property.

            + * The Panel's {@link #buttonAlign} configuration affects the layout of these items, for example: + *
            
            +var w = new Ext.Window({
            +    height: 250,
            +    width: 500,
            +    bbar: new Ext.Toolbar({
            +        items: [{
            +            text: 'bbar Left'
            +        },'->',{
            +            text: 'bbar Right'
            +        }]
            +    }),
            +    {@link #buttonAlign}: 'left', // anything but 'center' or 'right' and you can use '-', and '->'
            +                                  // to control the alignment of fbar items
            +    fbar: [{
            +        text: 'fbar Left'
            +    },'->',{
            +        text: 'fbar Right'
            +    }]
            +}).show();
            +     * 
            + *

            Note: Although a Toolbar may contain Field components, these will not be updated by a load + * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and + * so are not scanned to collect form items. However, the values will be submitted because form + * submission parameters are collected from the DOM tree.

            + */ + /** + * @cfg {Boolean} header + * true to create the Panel's header element explicitly, false to skip creating + * it. If a {@link #title} is set the header will be created automatically, otherwise it will not. + * If a {@link #title} is set but header is explicitly set to false, the header + * will not be rendered. + */ + /** + * @cfg {Boolean} footer + * true to create the footer element explicitly, false to skip creating it. The footer + * will be created automatically if {@link #buttons} or a {@link #fbar} have + * been configured. See {@link #bodyCfg} for an example. + */ + /** + * @cfg {String} title + * The title text to be used as innerHTML (html tags are accepted) to display in the panel + * {@link #header} (defaults to ''). When a title is specified the + * {@link #header} element will automatically be created and displayed unless + * {@link #header} is explicitly set to false. If you do not want to specify a + * title at config time, but you may want one later, you must either specify a non-empty + * title (a blank space ' ' will do) or header:true so that the container + * element will get created. + */ + /** + * @cfg {Array} buttons + * buttons will be used as {@link Ext.Container#items items} for the toolbar in + * the footer ({@link #fbar}). Typically the value of this configuration property will be + * an array of {@link Ext.Button}s or {@link Ext.Button} configuration objects. + * If an item is configured with minWidth or the Panel is configured with minButtonWidth, + * that width will be applied to the item. + */ + /** + * @cfg {Object/String/Function} autoLoad + * A valid url spec according to the Updater {@link Ext.Updater#update} method. + * If autoLoad is not null, the panel will attempt to load its contents + * immediately upon render.

            + * The URL will become the default URL for this panel's {@link #body} element, + * so it may be {@link Ext.Element#refresh refresh}ed at any time.

            + */ + /** + * @cfg {Boolean} frame + * false by default to render with plain 1px square borders. true to render with + * 9 elements, complete with custom rounded corners (also see {@link Ext.Element#boxWrap}). + *

            The template generated for each condition is depicted below:

            
            +     *
            +// frame = false
            +<div id="developer-specified-id-goes-here" class="x-panel">
            +
            +    <div class="x-panel-header"><span class="x-panel-header-text">Title: (frame:false)</span></div>
            +
            +    <div class="x-panel-bwrap">
            +        <div class="x-panel-body"><p>html value goes here</p></div>
            +    </div>
            +</div>
            +
            +// frame = true (create 9 elements)
            +<div id="developer-specified-id-goes-here" class="x-panel">
            +    <div class="x-panel-tl"><div class="x-panel-tr"><div class="x-panel-tc">
            +        <div class="x-panel-header"><span class="x-panel-header-text">Title: (frame:true)</span></div>
            +    </div></div></div>
            +
            +    <div class="x-panel-bwrap">
            +        <div class="x-panel-ml"><div class="x-panel-mr"><div class="x-panel-mc">
            +            <div class="x-panel-body"><p>html value goes here</p></div>
            +        </div></div></div>
            +
            +        <div class="x-panel-bl"><div class="x-panel-br"><div class="x-panel-bc"/>
            +        </div></div></div>
            +</div>
            +     * 
            + */ + /** + * @cfg {Boolean} border + * True to display the borders of the panel's body element, false to hide them (defaults to true). By default, + * the border is a 2px wide inset border, but this can be further altered by setting {@link #bodyBorder} to false. + */ + /** + * @cfg {Boolean} bodyBorder + * True to display an interior border on the body element of the panel, false to hide it (defaults to true). + * This only applies when {@link #border} == true. If border == true and bodyBorder == false, the border will display + * as a 1px wide inset border, giving the entire body element an inset appearance. + */ + /** + * @cfg {String/Object/Function} bodyCssClass + * Additional css class selector to be applied to the {@link #body} element in the format expected by + * {@link Ext.Element#addClass} (defaults to null). See {@link #bodyCfg}. + */ + /** + * @cfg {String/Object/Function} bodyStyle + * Custom CSS styles to be applied to the {@link #body} element in the format expected by + * {@link Ext.Element#applyStyles} (defaults to null). See {@link #bodyCfg}. + */ + /** + * @cfg {String} iconCls + * The CSS class selector that specifies a background image to be used as the header icon (defaults to ''). + *

            An example of specifying a custom icon class would be something like: + *

            
            +// specify the property in the config for the class:
            +     ...
            +     iconCls: 'my-icon'
            +
            +// css class that specifies background image to be used as the icon image:
            +.my-icon { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
            +
            + */ + /** + * @cfg {Boolean} collapsible + * True to make the panel collapsible and have the expand/collapse toggle button automatically rendered into + * the header tool button area, false to keep the panel statically sized with no button (defaults to false). + */ + /** + * @cfg {Array} tools + * An array of tool button configs to be added to the header tool area. When rendered, each tool is + * stored as an {@link Ext.Element Element} referenced by a public property called tools.<tool-type> + *

            Each tool config may contain the following properties: + *

              + *
            • id : String
              Required. The type + * of tool to create. By default, this assigns a CSS class of the form x-tool-<tool-type> to the + * resulting tool Element. Ext provides CSS rules, and an icon sprite containing images for the tool types listed below. + * The developer may implement custom tools by supplying alternate CSS rules and background images: + *
                + *
                toggle (Created by default when {@link #collapsible} is true)
                + *
                close
                + *
                minimize
                + *
                maximize
                + *
                restore
                + *
                gear
                + *
                pin
                + *
                unpin
                + *
                right
                + *
                left
                + *
                up
                + *
                down
                + *
                refresh
                + *
                minus
                + *
                plus
                + *
                help
                + *
                search
                + *
                save
                + *
                print
                + *
            • + *
            • handler : Function
              Required. The function to + * call when clicked. Arguments passed are:
                + *
              • event : Ext.EventObject
                The click event.
              • + *
              • toolEl : Ext.Element
                The tool Element.
              • + *
              • panel : Ext.Panel
                The host Panel
              • + *
              • tc : Object
                The tool configuration object
              • + *
            • + *
            • stopEvent : Boolean
              Defaults to true. Specify as false to allow click event to propagate.
            • + *
            • scope : Object
              The scope in which to call the handler.
            • + *
            • qtip : String/Object
              A tip string, or + * a config argument to {@link Ext.QuickTip#register}
            • + *
            • hidden : Boolean
              True to initially render hidden.
            • + *
            • on : Object
              A listener config object specifiying + * event listeners in the format of an argument to {@link #addListener}
            • + *
            + *

            Note that, apart from the toggle tool which is provided when a panel is collapsible, these + * tools only provide the visual button. Any required functionality must be provided by adding + * handlers that implement the necessary behavior.

            + *

            Example usage:

            + *
            
            +tools:[{
            +    id:'refresh',
            +    qtip: 'Refresh form Data',
            +    // hidden:true,
            +    handler: function(event, toolEl, panel){
            +        // refresh logic
            +    }
            +},
            +{
            +    id:'help',
            +    qtip: 'Get Help',
            +    handler: function(event, toolEl, panel){
            +        // whatever
            +    }
            +}]
            +
            + *

            For the custom id of 'help' define two relevant css classes with a link to + * a 15x15 image:

            + *
            
            +.x-tool-help {background-image: url(images/help.png);}
            +.x-tool-help-over {background-image: url(images/help_over.png);}
            +// if using an image sprite:
            +.x-tool-help {background-image: url(images/help.png) no-repeat 0 0;}
            +.x-tool-help-over {background-position:-15px 0;}
            +
            + */ + /** + * @cfg {Ext.Template/Ext.XTemplate} toolTemplate + *

            A Template used to create {@link #tools} in the {@link #header} Element. Defaults to:

            
            +new Ext.Template('<div class="x-tool x-tool-{id}">&#160;</div>')
            + *

            This may may be overridden to provide a custom DOM structure for tools based upon a more + * complex XTemplate. The template's data is a single tool configuration object (Not the entire Array) + * as specified in {@link #tools}. In the following example an <a> tag is used to provide a + * visual indication when hovering over the tool:

            
            +var win = new Ext.Window({
            +    tools: [{
            +        id: 'download',
            +        href: '/MyPdfDoc.pdf'
            +    }],
            +    toolTemplate: new Ext.XTemplate(
            +        '<tpl if="id==\'download\'">',
            +            '<a class="x-tool x-tool-pdf" href="{href}"></a>',
            +        '</tpl>',
            +        '<tpl if="id!=\'download\'">',
            +            '<div class="x-tool x-tool-{id}">&#160;</div>',
            +        '</tpl>'
            +    ),
            +    width:500,
            +    height:300,
            +    closeAction:'hide'
            +});
            + *

            Note that the CSS class 'x-tool-pdf' should have an associated style rule which provides an + * appropriate background image, something like:

            +
            
            +    a.x-tool-pdf {background-image: url(../shared/extjs/images/pdf.gif)!important;}
            +    
            + */ + /** + * @cfg {Boolean} hideCollapseTool + * true to hide the expand/collapse toggle button when {@link #collapsible} == true, + * false to display it (defaults to false). + */ + /** + * @cfg {Boolean} titleCollapse + * true to allow expanding and collapsing the panel (when {@link #collapsible} = true) + * by clicking anywhere in the header bar, false) to allow it only by clicking to tool button + * (defaults to false)). If this panel is a child item of a border layout also see the + * {@link Ext.layout.BorderLayout.Region BorderLayout.Region} + * {@link Ext.layout.BorderLayout.Region#floatable floatable} config option. + */ + + /** + * @cfg {Mixed} floating + *

            This property is used to configure the underlying {@link Ext.Layer}. Acceptable values for this + * configuration property are:

              + *
            • false : Default.
              Display the panel inline where it is + * rendered.
            • + *
            • true :
              Float the panel (absolute position it with automatic + * shimming and shadow).
                + *
                Setting floating to true will create an Ext.Layer for this panel and display the + * panel at negative offsets so that it is hidden.
                + *
                Since the panel will be absolute positioned, the position must be set explicitly + * after render (e.g., myPanel.setPosition(100,100);).
                + *
                Note: when floating a panel you should always assign a fixed width, + * otherwise it will be auto width and will expand to fill to the right edge of the viewport.
                + *
            • + *
            • {@link Ext.Layer object} :
              The specified object will be used + * as the configuration object for the {@link Ext.Layer} that will be created.
            • + *
            + */ + /** + * @cfg {Boolean/String} shadow + * true (or a valid Ext.Shadow {@link Ext.Shadow#mode} value) to display a shadow behind the + * panel, false to display no shadow (defaults to 'sides'). Note that this option + * only applies when {@link #floating} = true. + */ + /** + * @cfg {Number} shadowOffset + * The number of pixels to offset the shadow if displayed (defaults to 4). Note that this + * option only applies when {@link #floating} = true. + */ + /** + * @cfg {Boolean} shim + * false to disable the iframe shim in browsers which need one (defaults to true). + * Note that this option only applies when {@link #floating} = true. + */ + /** + * @cfg {Object/Array} keys + * A {@link Ext.KeyMap} config object (in the format expected by {@link Ext.KeyMap#addBinding} + * used to assign custom key handling to this panel (defaults to null). + */ + /** + * @cfg {Boolean/Object} draggable + *

            true to enable dragging of this Panel (defaults to false).

            + *

            For custom drag/drop implementations, an Ext.Panel.DD config could also be passed + * in this config instead of true. Ext.Panel.DD is an internal, undocumented class which + * moves a proxy Element around in place of the Panel's element, but provides no other behaviour + * during dragging or on drop. It is a subclass of {@link Ext.dd.DragSource}, so behaviour may be + * added by implementing the interface methods of {@link Ext.dd.DragDrop} e.g.: + *

            
            +new Ext.Panel({
            +    title: 'Drag me',
            +    x: 100,
            +    y: 100,
            +    renderTo: Ext.getBody(),
            +    floating: true,
            +    frame: true,
            +    width: 400,
            +    height: 200,
            +    draggable: {
            +//      Config option of Ext.Panel.DD class.
            +//      It's a floating Panel, so do not show a placeholder proxy in the original position.
            +        insertProxy: false,
            +
            +//      Called for each mousemove event while dragging the DD object.
            +        onDrag : function(e){
            +//          Record the x,y position of the drag proxy so that we can
            +//          position the Panel at end of drag.
            +            var pel = this.proxy.getEl();
            +            this.x = pel.getLeft(true);
            +            this.y = pel.getTop(true);
            +
            +//          Keep the Shadow aligned if there is one.
            +            var s = this.panel.getEl().shadow;
            +            if (s) {
            +                s.realign(this.x, this.y, pel.getWidth(), pel.getHeight());
            +            }
            +        },
            +
            +//      Called on the mouseup event.
            +        endDrag : function(e){
            +            this.panel.setPosition(this.x, this.y);
            +        }
            +    }
            +}).show();
            +
            + */ + /** + * @cfg {Boolean} disabled + * Render this panel disabled (default is false). An important note when using the disabled + * config on panels is that IE will often fail to initialize the disabled mask element correectly if + * the panel's layout has not yet completed by the time the Panel is disabled during the render process. + * If you experience this issue, you may need to instead use the {@link #afterlayout} event to initialize + * the disabled state: + *
            
            +new Ext.Panel({
            +    ...
            +    listeners: {
            +        'afterlayout': {
            +            fn: function(p){
            +                p.disable();
            +            },
            +            single: true // important, as many layouts can occur
            +        }
            +    }
            +});
            +
            + */ + /** + * @cfg {Boolean} autoHeight + * true to use height:'auto', false to use fixed height (defaults to false). + * Note: Setting autoHeight: true means that the browser will manage the panel's height + * based on its contents, and that Ext will not manage it at all. If the panel is within a layout that + * manages dimensions (fit, border, etc.) then setting autoHeight: true + * can cause issues with scrolling and will not generally work as expected since the panel will take + * on the height of its contents rather than the height required by the Ext layout. + */ + + + /** + * @cfg {String} baseCls + * The base CSS class to apply to this panel's element (defaults to 'x-panel'). + *

            Another option available by default is to specify 'x-plain' which strips all styling + * except for required attributes for Ext layouts to function (e.g. overflow:hidden). + * See {@link #unstyled} also.

            + */ + baseCls : 'x-panel', + /** + * @cfg {String} collapsedCls + * A CSS class to add to the panel's element after it has been collapsed (defaults to + * 'x-panel-collapsed'). + */ + collapsedCls : 'x-panel-collapsed', + /** + * @cfg {Boolean} maskDisabled + * true to mask the panel when it is {@link #disabled}, false to not mask it (defaults + * to true). Either way, the panel will always tell its contained elements to disable themselves + * when it is disabled, but masking the panel can provide an additional visual cue that the panel is + * disabled. + */ + maskDisabled : true, + /** + * @cfg {Boolean} animCollapse + * true to animate the transition when the panel is collapsed, false to skip the + * animation (defaults to true if the {@link Ext.Fx} class is available, otherwise false). + */ + animCollapse : Ext.enableFx, + /** + * @cfg {Boolean} headerAsText + * true to display the panel {@link #title} in the {@link #header}, + * false to hide it (defaults to true). + */ + headerAsText : true, + /** + * @cfg {String} buttonAlign + * The alignment of any {@link #buttons} added to this panel. Valid values are 'right', + * 'left' and 'center' (defaults to 'right'). + */ + buttonAlign : 'right', + /** + * @cfg {Boolean} collapsed + * true to render the panel collapsed, false to render it expanded (defaults to + * false). + */ + collapsed : false, + /** + * @cfg {Boolean} collapseFirst + * true to make sure the collapse/expand toggle button always renders first (to the left of) + * any other tools in the panel's title bar, false to render it last (defaults to true). + */ + collapseFirst : true, + /** + * @cfg {Number} minButtonWidth + * Minimum width in pixels of all {@link #buttons} in this panel (defaults to 75) + */ + minButtonWidth : 75, + /** + * @cfg {Boolean} unstyled + * Overrides the {@link #baseCls} setting to {@link #baseCls} = 'x-plain' which renders + * the panel unstyled except for required attributes for Ext layouts to function (e.g. overflow:hidden). + */ + /** + * @cfg {String} elements + * A comma-delimited list of panel elements to initialize when the panel is rendered. Normally, this list will be + * generated automatically based on the items added to the panel at config time, but sometimes it might be useful to + * make sure a structural element is rendered even if not specified at config time (for example, you may want + * to add a button or toolbar dynamically after the panel has been rendered). Adding those elements to this + * list will allocate the required placeholders in the panel when it is rendered. Valid values are
              + *
            • header
            • + *
            • tbar (top bar)
            • + *
            • body
            • + *
            • bbar (bottom bar)
            • + *
            • footer
            • + *
            + * Defaults to 'body'. + */ + elements : 'body', + /** + * @cfg {Boolean} preventBodyReset + * Defaults to false. When set to true, an extra css class 'x-panel-normal' + * will be added to the panel's element, effectively applying css styles suggested by the W3C + * (see http://www.w3.org/TR/CSS21/sample.html) to the Panel's body element (not the header, + * footer, etc.). + */ + preventBodyReset : false, + + /** + * @cfg {Number/String} padding + * A shortcut for setting a padding style on the body element. The value can either be + * a number to be applied to all sides, or a normal css string describing padding. + * Defaults to undefined. + * + */ + padding: undefined, + + /** @cfg {String} resizeEvent + * The event to listen to for resizing in layouts. Defaults to 'bodyresize'. + */ + resizeEvent: 'bodyresize', + + // protected - these could be used to customize the behavior of the window, + // but changing them would not be useful without further mofifications and + // could lead to unexpected or undesirable results. + toolTarget : 'header', + collapseEl : 'bwrap', + slideAnchor : 't', + disabledClass : '', + + // private, notify box this class will handle heights + deferHeight : true, + // private + expandDefaults: { + duration : 0.25 + }, + // private + collapseDefaults : { + duration : 0.25 + }, + + // private + initComponent : function(){ + Ext.Panel.superclass.initComponent.call(this); + + this.addEvents( + /** + * @event bodyresize + * Fires after the Panel has been resized. + * @param {Ext.Panel} p the Panel which has been resized. + * @param {Number} width The Panel body's new width. + * @param {Number} height The Panel body's new height. + */ + 'bodyresize', + /** + * @event titlechange + * Fires after the Panel title has been {@link #title set} or {@link #setTitle changed}. + * @param {Ext.Panel} p the Panel which has had its title changed. + * @param {String} The new title. + */ + 'titlechange', + /** + * @event iconchange + * Fires after the Panel icon class has been {@link #iconCls set} or {@link #setIconClass changed}. + * @param {Ext.Panel} p the Panel which has had its {@link #iconCls icon class} changed. + * @param {String} The new icon class. + * @param {String} The old icon class. + */ + 'iconchange', + /** + * @event collapse + * Fires after the Panel has been collapsed. + * @param {Ext.Panel} p the Panel that has been collapsed. + */ + 'collapse', + /** + * @event expand + * Fires after the Panel has been expanded. + * @param {Ext.Panel} p The Panel that has been expanded. + */ + 'expand', + /** + * @event beforecollapse + * Fires before the Panel is collapsed. A handler can return false to cancel the collapse. + * @param {Ext.Panel} p the Panel being collapsed. + * @param {Boolean} animate True if the collapse is animated, else false. + */ + 'beforecollapse', + /** + * @event beforeexpand + * Fires before the Panel is expanded. A handler can return false to cancel the expand. + * @param {Ext.Panel} p The Panel being expanded. + * @param {Boolean} animate True if the expand is animated, else false. + */ + 'beforeexpand', + /** + * @event beforeclose + * Fires before the Panel is closed. Note that Panels do not directly support being closed, but some + * Panel subclasses do (like {@link Ext.Window}) or a Panel within a Ext.TabPanel. This event only + * applies to such subclasses. + * A handler can return false to cancel the close. + * @param {Ext.Panel} p The Panel being closed. + */ + 'beforeclose', + /** + * @event close + * Fires after the Panel is closed. Note that Panels do not directly support being closed, but some + * Panel subclasses do (like {@link Ext.Window}) or a Panel within a Ext.TabPanel. + * @param {Ext.Panel} p The Panel that has been closed. + */ + 'close', + /** + * @event activate + * Fires after the Panel has been visually activated. + * Note that Panels do not directly support being activated, but some Panel subclasses + * do (like {@link Ext.Window}). Panels which are child Components of a TabPanel fire the + * activate and deactivate events under the control of the TabPanel. + * @param {Ext.Panel} p The Panel that has been activated. + */ + 'activate', + /** + * @event deactivate + * Fires after the Panel has been visually deactivated. + * Note that Panels do not directly support being deactivated, but some Panel subclasses + * do (like {@link Ext.Window}). Panels which are child Components of a TabPanel fire the + * activate and deactivate events under the control of the TabPanel. + * @param {Ext.Panel} p The Panel that has been deactivated. + */ + 'deactivate' + ); + + if(this.unstyled){ + this.baseCls = 'x-plain'; + } + + + this.toolbars = []; + // shortcuts + if(this.tbar){ + this.elements += ',tbar'; + this.topToolbar = this.createToolbar(this.tbar); + this.tbar = null; + + } + if(this.bbar){ + this.elements += ',bbar'; + this.bottomToolbar = this.createToolbar(this.bbar); + this.bbar = null; + } + + if(this.header === true){ + this.elements += ',header'; + this.header = null; + }else if(this.headerCfg || (this.title && this.header !== false)){ + this.elements += ',header'; + } + + if(this.footerCfg || this.footer === true){ + this.elements += ',footer'; + this.footer = null; + } + + if(this.buttons){ + this.fbar = this.buttons; + this.buttons = null; + } + if(this.fbar){ + this.createFbar(this.fbar); + } + if(this.autoLoad){ + this.on('render', this.doAutoLoad, this, {delay:10}); + } + }, + + // private + createFbar : function(fbar){ + var min = this.minButtonWidth; + this.elements += ',footer'; + this.fbar = this.createToolbar(fbar, { + buttonAlign: this.buttonAlign, + toolbarCls: 'x-panel-fbar', + enableOverflow: false, + defaults: function(c){ + return { + minWidth: c.minWidth || min + }; + } + }); + // @compat addButton and buttons could possibly be removed + // @target 4.0 + /** + * This Panel's Array of buttons as created from the {@link #buttons} + * config property. Read only. + * @type Array + * @property buttons + */ + this.fbar.items.each(function(c){ + c.minWidth = c.minWidth || this.minButtonWidth; + }, this); + this.buttons = this.fbar.items.items; + }, + + // private + createToolbar: function(tb, options){ + var result; + // Convert array to proper toolbar config + if(Ext.isArray(tb)){ + tb = { + items: tb + }; + } + result = tb.events ? Ext.apply(tb, options) : this.createComponent(Ext.apply({}, tb, options), 'toolbar'); + this.toolbars.push(result); + return result; + }, + + // private + createElement : function(name, pnode){ + if(this[name]){ + pnode.appendChild(this[name].dom); + return; + } + + if(name === 'bwrap' || this.elements.indexOf(name) != -1){ + if(this[name+'Cfg']){ + this[name] = Ext.fly(pnode).createChild(this[name+'Cfg']); + }else{ + var el = document.createElement('div'); + el.className = this[name+'Cls']; + this[name] = Ext.get(pnode.appendChild(el)); + } + if(this[name+'CssClass']){ + this[name].addClass(this[name+'CssClass']); + } + if(this[name+'Style']){ + this[name].applyStyles(this[name+'Style']); + } + } + }, + + // private + onRender : function(ct, position){ + Ext.Panel.superclass.onRender.call(this, ct, position); + this.createClasses(); + + var el = this.el, + d = el.dom, + bw, + ts; + + + if(this.collapsible && !this.hideCollapseTool){ + this.tools = this.tools ? this.tools.slice(0) : []; + this.tools[this.collapseFirst?'unshift':'push']({ + id: 'toggle', + handler : this.toggleCollapse, + scope: this + }); + } + + if(this.tools){ + ts = this.tools; + this.elements += (this.header !== false) ? ',header' : ''; + } + this.tools = {}; + + el.addClass(this.baseCls); + if(d.firstChild){ // existing markup + this.header = el.down('.'+this.headerCls); + this.bwrap = el.down('.'+this.bwrapCls); + var cp = this.bwrap ? this.bwrap : el; + this.tbar = cp.down('.'+this.tbarCls); + this.body = cp.down('.'+this.bodyCls); + this.bbar = cp.down('.'+this.bbarCls); + this.footer = cp.down('.'+this.footerCls); + this.fromMarkup = true; + } + if (this.preventBodyReset === true) { + el.addClass('x-panel-reset'); + } + if(this.cls){ + el.addClass(this.cls); + } + + if(this.buttons){ + this.elements += ',footer'; + } + + // This block allows for maximum flexibility and performance when using existing markup + + // framing requires special markup + if(this.frame){ + el.insertHtml('afterBegin', String.format(Ext.Element.boxMarkup, this.baseCls)); + + this.createElement('header', d.firstChild.firstChild.firstChild); + this.createElement('bwrap', d); + + // append the mid and bottom frame to the bwrap + bw = this.bwrap.dom; + var ml = d.childNodes[1], bl = d.childNodes[2]; + bw.appendChild(ml); + bw.appendChild(bl); + + var mc = bw.firstChild.firstChild.firstChild; + this.createElement('tbar', mc); + this.createElement('body', mc); + this.createElement('bbar', mc); + this.createElement('footer', bw.lastChild.firstChild.firstChild); + + if(!this.footer){ + this.bwrap.dom.lastChild.className += ' x-panel-nofooter'; + } + /* + * Store a reference to this element so: + * a) We aren't looking it up all the time + * b) The last element is reported incorrectly when using a loadmask + */ + this.ft = Ext.get(this.bwrap.dom.lastChild); + this.mc = Ext.get(mc); + }else{ + this.createElement('header', d); + this.createElement('bwrap', d); + + // append the mid and bottom frame to the bwrap + bw = this.bwrap.dom; + this.createElement('tbar', bw); + this.createElement('body', bw); + this.createElement('bbar', bw); + this.createElement('footer', bw); + + if(!this.header){ + this.body.addClass(this.bodyCls + '-noheader'); + if(this.tbar){ + this.tbar.addClass(this.tbarCls + '-noheader'); + } + } + } + + if(Ext.isDefined(this.padding)){ + this.body.setStyle('padding', this.body.addUnits(this.padding)); + } + + if(this.border === false){ + this.el.addClass(this.baseCls + '-noborder'); + this.body.addClass(this.bodyCls + '-noborder'); + if(this.header){ + this.header.addClass(this.headerCls + '-noborder'); + } + if(this.footer){ + this.footer.addClass(this.footerCls + '-noborder'); + } + if(this.tbar){ + this.tbar.addClass(this.tbarCls + '-noborder'); + } + if(this.bbar){ + this.bbar.addClass(this.bbarCls + '-noborder'); + } + } + + if(this.bodyBorder === false){ + this.body.addClass(this.bodyCls + '-noborder'); + } + + this.bwrap.enableDisplayMode('block'); + + if(this.header){ + this.header.unselectable(); + + // for tools, we need to wrap any existing header markup + if(this.headerAsText){ + this.header.dom.innerHTML = + ''+this.header.dom.innerHTML+''; + + if(this.iconCls){ + this.setIconClass(this.iconCls); + } + } + } + + if(this.floating){ + this.makeFloating(this.floating); + } + + if(this.collapsible && this.titleCollapse && this.header){ + this.mon(this.header, 'click', this.toggleCollapse, this); + this.header.setStyle('cursor', 'pointer'); + } + if(ts){ + this.addTool.apply(this, ts); + } + + // Render Toolbars. + if(this.fbar){ + this.footer.addClass('x-panel-btns'); + this.fbar.ownerCt = this; + this.fbar.render(this.footer); + this.footer.createChild({cls:'x-clear'}); + } + if(this.tbar && this.topToolbar){ + this.topToolbar.ownerCt = this; + this.topToolbar.render(this.tbar); + } + if(this.bbar && this.bottomToolbar){ + this.bottomToolbar.ownerCt = this; + this.bottomToolbar.render(this.bbar); + } + }, + + /** + * Sets the CSS class that provides the icon image for this panel. This method will replace any existing + * icon class if one has already been set and fire the {@link #iconchange} event after completion. + * @param {String} cls The new CSS class name + */ + setIconClass : function(cls){ + var old = this.iconCls; + this.iconCls = cls; + if(this.rendered && this.header){ + if(this.frame){ + this.header.addClass('x-panel-icon'); + this.header.replaceClass(old, this.iconCls); + }else{ + var hd = this.header, + img = hd.child('img.x-panel-inline-icon'); + if(img){ + Ext.fly(img).replaceClass(old, this.iconCls); + }else{ + var hdspan = hd.child('span.' + this.headerTextCls); + if (hdspan) { + Ext.DomHelper.insertBefore(hdspan.dom, { + tag:'img', src: Ext.BLANK_IMAGE_URL, cls:'x-panel-inline-icon '+this.iconCls + }); + } + } + } + } + this.fireEvent('iconchange', this, cls, old); + }, + + // private + makeFloating : function(cfg){ + this.floating = true; + this.el = new Ext.Layer(Ext.apply({}, cfg, { + shadow: Ext.isDefined(this.shadow) ? this.shadow : 'sides', + shadowOffset: this.shadowOffset, + constrain:false, + shim: this.shim === false ? false : undefined + }), this.el); + }, + + /** + * Returns the {@link Ext.Toolbar toolbar} from the top ({@link #tbar}) section of the panel. + * @return {Ext.Toolbar} The toolbar + */ + getTopToolbar : function(){ + return this.topToolbar; + }, + + /** + * Returns the {@link Ext.Toolbar toolbar} from the bottom ({@link #bbar}) section of the panel. + * @return {Ext.Toolbar} The toolbar + */ + getBottomToolbar : function(){ + return this.bottomToolbar; + }, + + /** + * Returns the {@link Ext.Toolbar toolbar} from the footer ({@link #fbar}) section of the panel. + * @return {Ext.Toolbar} The toolbar + */ + getFooterToolbar : function() { + return this.fbar; + }, + + /** + * Adds a button to this panel. Note that this method must be called prior to rendering. The preferred + * approach is to add buttons via the {@link #buttons} config. + * @param {String/Object} config A valid {@link Ext.Button} config. A string will become the text for a default + * button config, an object will be treated as a button config object. + * @param {Function} handler The function to be called on button {@link Ext.Button#click} + * @param {Object} scope The scope (this reference) in which the button handler function is executed. Defaults to the Button. + * @return {Ext.Button} The button that was added + */ + addButton : function(config, handler, scope){ + if(!this.fbar){ + this.createFbar([]); + } + if(handler){ + if(Ext.isString(config)){ + config = {text: config}; + } + config = Ext.apply({ + handler: handler, + scope: scope + }, config); + } + return this.fbar.add(config); + }, + + // private + addTool : function(){ + if(!this.rendered){ + if(!this.tools){ + this.tools = []; + } + Ext.each(arguments, function(arg){ + this.tools.push(arg); + }, this); + return; + } + // nowhere to render tools! + if(!this[this.toolTarget]){ + return; + } + if(!this.toolTemplate){ + // initialize the global tool template on first use + var tt = new Ext.Template( + '
             
            ' + ); + tt.disableFormats = true; + tt.compile(); + Ext.Panel.prototype.toolTemplate = tt; + } + for(var i = 0, a = arguments, len = a.length; i < len; i++) { + var tc = a[i]; + if(!this.tools[tc.id]){ + var overCls = 'x-tool-'+tc.id+'-over'; + var t = this.toolTemplate.insertFirst(this[this.toolTarget], tc, true); + this.tools[tc.id] = t; + t.enableDisplayMode('block'); + this.mon(t, 'click', this.createToolHandler(t, tc, overCls, this)); + if(tc.on){ + this.mon(t, tc.on); + } + if(tc.hidden){ + t.hide(); + } + if(tc.qtip){ + if(Ext.isObject(tc.qtip)){ + Ext.QuickTips.register(Ext.apply({ + target: t.id + }, tc.qtip)); + } else { + t.dom.qtip = tc.qtip; + } + } + t.addClassOnOver(overCls); + } + } + }, + + onLayout : function(shallow, force){ + Ext.Panel.superclass.onLayout.apply(this, arguments); + if(this.hasLayout && this.toolbars.length > 0){ + Ext.each(this.toolbars, function(tb){ + tb.doLayout(undefined, force); + }); + this.syncHeight(); + } + }, + + syncHeight : function(){ + var h = this.toolbarHeight, + bd = this.body, + lsh = this.lastSize.height, + sz; + + if(this.autoHeight || !Ext.isDefined(lsh) || lsh == 'auto'){ + return; + } + + + if(h != this.getToolbarHeight()){ + h = Math.max(0, lsh - this.getFrameHeight()); + bd.setHeight(h); + sz = bd.getSize(); + this.toolbarHeight = this.getToolbarHeight(); + this.onBodyResize(sz.width, sz.height); + } + }, + + // private + onShow : function(){ + if(this.floating){ + return this.el.show(); + } + Ext.Panel.superclass.onShow.call(this); + }, + + // private + onHide : function(){ + if(this.floating){ + return this.el.hide(); + } + Ext.Panel.superclass.onHide.call(this); + }, + + // private + createToolHandler : function(t, tc, overCls, panel){ + return function(e){ + t.removeClass(overCls); + if(tc.stopEvent !== false){ + e.stopEvent(); + } + if(tc.handler){ + tc.handler.call(tc.scope || t, e, t, panel, tc); + } + }; + }, + + // private + afterRender : function(){ + if(this.floating && !this.hidden){ + this.el.show(); + } + if(this.title){ + this.setTitle(this.title); + } + Ext.Panel.superclass.afterRender.call(this); // do sizing calcs last + if (this.collapsed) { + this.collapsed = false; + this.collapse(false); + } + this.initEvents(); + }, + + // private + getKeyMap : function(){ + if(!this.keyMap){ + this.keyMap = new Ext.KeyMap(this.el, this.keys); + } + return this.keyMap; + }, + + // private + initEvents : function(){ + if(this.keys){ + this.getKeyMap(); + } + if(this.draggable){ + this.initDraggable(); + } + if(this.toolbars.length > 0){ + Ext.each(this.toolbars, function(tb){ + tb.doLayout(); + tb.on({ + scope: this, + afterlayout: this.syncHeight, + remove: this.syncHeight + }); + }, this); + this.syncHeight(); + } + + }, + + // private + initDraggable : function(){ + /** + *

            If this Panel is configured {@link #draggable}, this property will contain + * an instance of {@link Ext.dd.DragSource} which handles dragging the Panel.

            + * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource} + * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}. + * @type Ext.dd.DragSource. + * @property dd + */ + this.dd = new Ext.Panel.DD(this, Ext.isBoolean(this.draggable) ? null : this.draggable); + }, + + // private + beforeEffect : function(anim){ + if(this.floating){ + this.el.beforeAction(); + } + if(anim !== false){ + this.el.addClass('x-panel-animated'); + } + }, + + // private + afterEffect : function(anim){ + this.syncShadow(); + this.el.removeClass('x-panel-animated'); + }, + + // private - wraps up an animation param with internal callbacks + createEffect : function(a, cb, scope){ + var o = { + scope:scope, + block:true + }; + if(a === true){ + o.callback = cb; + return o; + }else if(!a.callback){ + o.callback = cb; + }else { // wrap it up + o.callback = function(){ + cb.call(scope); + Ext.callback(a.callback, a.scope); + }; + } + return Ext.applyIf(o, a); + }, + + /** + * Collapses the panel body so that it becomes hidden. Fires the {@link #beforecollapse} event which will + * cancel the collapse action if it returns false. + * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the + * {@link #animCollapse} panel config) + * @return {Ext.Panel} this + */ + collapse : function(animate){ + if(this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforecollapse', this, animate) === false){ + return; + } + var doAnim = animate === true || (animate !== false && this.animCollapse); + this.beforeEffect(doAnim); + this.onCollapse(doAnim, animate); + return this; + }, + + // private + onCollapse : function(doAnim, animArg){ + if(doAnim){ + this[this.collapseEl].slideOut(this.slideAnchor, + Ext.apply(this.createEffect(animArg||true, this.afterCollapse, this), + this.collapseDefaults)); + }else{ + this[this.collapseEl].hide(this.hideMode); + this.afterCollapse(false); + } + }, + + // private + afterCollapse : function(anim){ + this.collapsed = true; + this.el.addClass(this.collapsedCls); + if(anim !== false){ + this[this.collapseEl].hide(this.hideMode); + } + this.afterEffect(anim); + + // Reset lastSize of all sub-components so they KNOW they are in a collapsed container + this.cascade(function(c) { + if (c.lastSize) { + c.lastSize = { width: undefined, height: undefined }; + } + }); + this.fireEvent('collapse', this); + }, + + /** + * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will + * cancel the expand action if it returns false. + * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the + * {@link #animCollapse} panel config) + * @return {Ext.Panel} this + */ + expand : function(animate){ + if(!this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforeexpand', this, animate) === false){ + return; + } + var doAnim = animate === true || (animate !== false && this.animCollapse); + this.el.removeClass(this.collapsedCls); + this.beforeEffect(doAnim); + this.onExpand(doAnim, animate); + return this; + }, + + // private + onExpand : function(doAnim, animArg){ + if(doAnim){ + this[this.collapseEl].slideIn(this.slideAnchor, + Ext.apply(this.createEffect(animArg||true, this.afterExpand, this), + this.expandDefaults)); + }else{ + this[this.collapseEl].show(this.hideMode); + this.afterExpand(false); + } + }, + + // private + afterExpand : function(anim){ + this.collapsed = false; + if(anim !== false){ + this[this.collapseEl].show(this.hideMode); + } + this.afterEffect(anim); + if (this.deferLayout) { + delete this.deferLayout; + this.doLayout(true); + } + this.fireEvent('expand', this); + }, + + /** + * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel. + * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the + * {@link #animCollapse} panel config) + * @return {Ext.Panel} this + */ + toggleCollapse : function(animate){ + this[this.collapsed ? 'expand' : 'collapse'](animate); + return this; + }, + + // private + onDisable : function(){ + if(this.rendered && this.maskDisabled){ + this.el.mask(); + } + Ext.Panel.superclass.onDisable.call(this); + }, + + // private + onEnable : function(){ + if(this.rendered && this.maskDisabled){ + this.el.unmask(); + } + Ext.Panel.superclass.onEnable.call(this); + }, + + // private + onResize : function(adjWidth, adjHeight, rawWidth, rawHeight){ + var w = adjWidth, + h = adjHeight; + + if(Ext.isDefined(w) || Ext.isDefined(h)){ + if(!this.collapsed){ + // First, set the the Panel's body width. + // If we have auto-widthed it, get the resulting full offset width so we can size the Toolbars to match + // The Toolbars must not buffer this resize operation because we need to know their heights. + + if(Ext.isNumber(w)){ + this.body.setWidth(w = this.adjustBodyWidth(w - this.getFrameWidth())); + } else if (w == 'auto') { + w = this.body.setWidth('auto').dom.offsetWidth; + } else { + w = this.body.dom.offsetWidth; + } + + if(this.tbar){ + this.tbar.setWidth(w); + if(this.topToolbar){ + this.topToolbar.setSize(w); + } + } + if(this.bbar){ + this.bbar.setWidth(w); + if(this.bottomToolbar){ + this.bottomToolbar.setSize(w); + // The bbar does not move on resize without this. + if (Ext.isIE) { + this.bbar.setStyle('position', 'static'); + this.bbar.setStyle('position', ''); + } + } + } + if(this.footer){ + this.footer.setWidth(w); + if(this.fbar){ + this.fbar.setSize(Ext.isIE ? (w - this.footer.getFrameWidth('lr')) : 'auto'); + } + } + + // At this point, the Toolbars must be layed out for getFrameHeight to find a result. + if(Ext.isNumber(h)){ + h = Math.max(0, h - this.getFrameHeight()); + //h = Math.max(0, h - (this.getHeight() - this.body.getHeight())); + this.body.setHeight(h); + }else if(h == 'auto'){ + this.body.setHeight(h); + } + + if(this.disabled && this.el._mask){ + this.el._mask.setSize(this.el.dom.clientWidth, this.el.getHeight()); + } + }else{ + // Adds an event to set the correct height afterExpand. This accounts for the deferHeight flag in panel + this.queuedBodySize = {width: w, height: h}; + if(!this.queuedExpand && this.allowQueuedExpand !== false){ + this.queuedExpand = true; + this.on('expand', function(){ + delete this.queuedExpand; + this.onResize(this.queuedBodySize.width, this.queuedBodySize.height); + }, this, {single:true}); + } + } + this.onBodyResize(w, h); + } + this.syncShadow(); + Ext.Panel.superclass.onResize.call(this, adjWidth, adjHeight, rawWidth, rawHeight); + + }, + + // private + onBodyResize: function(w, h){ + this.fireEvent('bodyresize', this, w, h); + }, + + // private + getToolbarHeight: function(){ + var h = 0; + if(this.rendered){ + Ext.each(this.toolbars, function(tb){ + h += tb.getHeight(); + }, this); + } + return h; + }, + + // deprecate + adjustBodyHeight : function(h){ + return h; + }, + + // private + adjustBodyWidth : function(w){ + return w; + }, + + // private + onPosition : function(){ + this.syncShadow(); + }, + + /** + * Returns the width in pixels of the framing elements of this panel (not including the body width). To + * retrieve the body width see {@link #getInnerWidth}. + * @return {Number} The frame width */ + getFrameWidth : function(){ + var w = this.el.getFrameWidth('lr') + this.bwrap.getFrameWidth('lr'); + + if(this.frame){ + var l = this.bwrap.dom.firstChild; + w += (Ext.fly(l).getFrameWidth('l') + Ext.fly(l.firstChild).getFrameWidth('r')); + w += this.mc.getFrameWidth('lr'); + } + return w; + }, + /** - * @cfg {Number} shadowOffset - * The number of pixels to offset the shadow if displayed (defaults to 4). Note that this - * option only applies when {@link #floating} = true. + * Returns the height in pixels of the framing elements of this panel (including any top and bottom bars and + * header and footer elements, but not including the body height). To retrieve the body height see {@link #getInnerHeight}. + * @return {Number} The frame height */ + getFrameHeight : function() { + var h = Math.max(0, this.getHeight() - this.body.getHeight()); + + if (isNaN(h)) { + h = 0; + } + return h; + + /* Deprecate + var h = this.el.getFrameWidth('tb') + this.bwrap.getFrameWidth('tb'); + h += (this.tbar ? this.tbar.getHeight() : 0) + + (this.bbar ? this.bbar.getHeight() : 0); + + if(this.frame){ + h += this.el.dom.firstChild.offsetHeight + this.ft.dom.offsetHeight + this.mc.getFrameWidth('tb'); + }else{ + h += (this.header ? this.header.getHeight() : 0) + + (this.footer ? this.footer.getHeight() : 0); + } + return h; + */ + }, + /** - * @cfg {Boolean} shim - * false to disable the iframe shim in browsers which need one (defaults to true). - * Note that this option only applies when {@link #floating} = true. + * Returns the width in pixels of the body element (not including the width of any framing elements). + * For the frame width see {@link #getFrameWidth}. + * @return {Number} The body width */ + getInnerWidth : function(){ + return this.getSize().width - this.getFrameWidth(); + }, + /** - * @cfg {String/Object} html - * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the panel's body - * content (defaults to ''). The HTML content is added by the Panel's {@link #afterRender} method, - * and so the document will not contain this HTML at the time the {@link #render} event is fired. - * This content is inserted into the body before any configured {@link #contentEl} is appended. + * Returns the height in pixels of the body element (not including the height of any framing elements). + * For the frame height see {@link #getFrameHeight}. + * @return {Number} The body height */ + getInnerHeight : function(){ + return this.body.getHeight(); + /* Deprecate + return this.getSize().height - this.getFrameHeight(); + */ + }, + + // private + syncShadow : function(){ + if(this.floating){ + this.el.sync(true); + } + }, + + // private + getLayoutTarget : function(){ + return this.body; + }, + + // private + getContentTarget : function(){ + return this.body; + }, + /** - * @cfg {String} contentEl - *

            Specify the id of an existing HTML node to use as the panel's body content - * (defaults to '').

              - *
            • Description :
                - *
                This config option is used to take an existing HTML element and place it in the body - * of a new panel (it simply moves the specified DOM element into the body element of the Panel - * when the Panel is rendered to use as the content (it is not going to be the - * actual panel itself).
                - *
            • - *
            • Notes :
                - *
                The specified HTML Element is appended to the Panel's {@link #body} Element by the - * Panel's {@link #afterRender} method after any configured {@link #html HTML} has - * been inserted, and so the document will not contain this HTML at the time the - * {@link #render} event is fired.
                - *
                The specified HTML element used will not participate in any layout scheme that the - * Panel may use. It's just HTML. Layouts operate on child items.
                - *
                Add either the x-hidden or the x-hide-display CSS class to - * prevent a brief flicker of the content before it is rendered to the panel.
                - *
            • - *
            + *

            Sets the title text for the panel and optionally the {@link #iconCls icon class}.

            + *

            In order to be able to set the title, a header element must have been created + * for the Panel. This is triggered either by configuring the Panel with a non-blank {@link #title}, + * or configuring it with {@link #header}: true.

            + * @param {String} title The title text to set + * @param {String} iconCls (optional) {@link #iconCls iconCls} A user-defined CSS class that provides the icon image for this panel */ + setTitle : function(title, iconCls){ + this.title = title; + if(this.header && this.headerAsText){ + this.header.child('span').update(title); + } + if(iconCls){ + this.setIconClass(iconCls); + } + this.fireEvent('titlechange', this, title); + return this; + }, + /** - * @cfg {Object/Array} keys - * A {@link Ext.KeyMap} config object (in the format expected by {@link Ext.KeyMap#addBinding} - * used to assign custom key handling to this panel (defaults to null). + * Get the {@link Ext.Updater} for this panel. Enables you to perform Ajax updates of this panel's body. + * @return {Ext.Updater} The Updater */ - /** - * @cfg {Boolean/Object} draggable - *

            true to enable dragging of this Panel (defaults to false).

            - *

            For custom drag/drop implementations, an Ext.Panel.DD config could also be passed - * in this config instead of true. Ext.Panel.DD is an internal, undocumented class which - * moves a proxy Element around in place of the Panel's element, but provides no other behaviour - * during dragging or on drop. It is a subclass of {@link Ext.dd.DragSource}, so behaviour may be - * added by implementing the interface methods of {@link Ext.dd.DragDrop} e.g.: - *

            
            -new Ext.Panel({
            -    title: 'Drag me',
            -    x: 100,
            -    y: 100,
            -    renderTo: Ext.getBody(),
            -    floating: true,
            -    frame: true,
            -    width: 400,
            -    height: 200,
            -    draggable: {
            -//      Config option of Ext.Panel.DD class.
            -//      It's a floating Panel, so do not show a placeholder proxy in the original position.
            -        insertProxy: false,
            +    getUpdater : function(){
            +        return this.body.getUpdater();
            +    },
             
            -//      Called for each mousemove event while dragging the DD object.
            -        onDrag : function(e){
            -//          Record the x,y position of the drag proxy so that we can
            -//          position the Panel at end of drag.
            -            var pel = this.proxy.getEl();
            -            this.x = pel.getLeft(true);
            -            this.y = pel.getTop(true);
            +     /**
            +     * Loads this content panel immediately with content returned from an XHR call.
            +     * @param {Object/String/Function} config A config object containing any of the following options:
            +
            
            +panel.load({
            +    url: 'your-url.php',
            +    params: {param1: 'foo', param2: 'bar'}, // or a URL encoded string
            +    callback: yourFunction,
            +    scope: yourObject, // optional scope for the callback
            +    discardUrl: false,
            +    nocache: false,
            +    text: 'Loading...',
            +    timeout: 30,
            +    scripts: false
            +});
            +
            + * The only required property is url. The optional properties nocache, text and scripts + * are shorthand for disableCaching, indicatorText and loadScripts and are used to set their + * associated property on this panel Updater instance. + * @return {Ext.Panel} this + */ + load : function(){ + var um = this.body.getUpdater(); + um.update.apply(um, arguments); + return this; + }, -// Keep the Shadow aligned if there is one. - var s = this.panel.getEl().shadow; - if (s) { - s.realign(this.x, this.y, pel.getWidth(), pel.getHeight()); + // private + beforeDestroy : function(){ + Ext.Panel.superclass.beforeDestroy.call(this); + if(this.header){ + this.header.removeAllListeners(); + } + if(this.tools){ + for(var k in this.tools){ + Ext.destroy(this.tools[k]); } - }, + } + if(this.toolbars.length > 0){ + Ext.each(this.toolbars, function(tb){ + tb.un('afterlayout', this.syncHeight, this); + tb.un('remove', this.syncHeight, this); + }, this); + } + if(Ext.isArray(this.buttons)){ + while(this.buttons.length) { + Ext.destroy(this.buttons[0]); + } + } + if(this.rendered){ + Ext.destroy( + this.ft, + this.header, + this.footer, + this.tbar, + this.bbar, + this.body, + this.mc, + this.bwrap, + this.dd + ); + if (this.fbar) { + Ext.destroy( + this.fbar, + this.fbar.el + ); + } + } + Ext.destroy(this.toolbars); + }, + + // private + createClasses : function(){ + this.headerCls = this.baseCls + '-header'; + this.headerTextCls = this.baseCls + '-header-text'; + this.bwrapCls = this.baseCls + '-bwrap'; + this.tbarCls = this.baseCls + '-tbar'; + this.bodyCls = this.baseCls + '-body'; + this.bbarCls = this.baseCls + '-bbar'; + this.footerCls = this.baseCls + '-footer'; + }, + + // private + createGhost : function(cls, useShim, appendTo){ + var el = document.createElement('div'); + el.className = 'x-panel-ghost ' + (cls ? cls : ''); + if(this.header){ + el.appendChild(this.el.dom.firstChild.cloneNode(true)); + } + Ext.fly(el.appendChild(document.createElement('ul'))).setHeight(this.bwrap.getHeight()); + el.style.width = this.el.dom.offsetWidth + 'px';; + if(!appendTo){ + this.container.dom.appendChild(el); + }else{ + Ext.getDom(appendTo).appendChild(el); + } + if(useShim !== false && this.el.useShim !== false){ + var layer = new Ext.Layer({shadow:false, useDisplay:true, constrain:false}, el); + layer.show(); + return layer; + }else{ + return new Ext.Element(el); + } + }, -// Called on the mouseup event. - endDrag : function(e){ - this.panel.setPosition(this.x, this.y); + // private + doAutoLoad : function(){ + var u = this.body.getUpdater(); + if(this.renderer){ + u.setRenderer(this.renderer); } - } -}).show(); -
            - */ + u.update(Ext.isObject(this.autoLoad) ? this.autoLoad : {url: this.autoLoad}); + }, + /** - * @cfg {String} tabTip - * A string to be used as innerHTML (html tags are accepted) to show in a tooltip when mousing over - * the tab of a Ext.Panel which is an item of a {@link Ext.TabPanel}. {@link Ext.QuickTips}.init() - * must be called in order for the tips to render. + * Retrieve a tool by id. + * @param {String} id + * @return {Object} tool */ - /** - * @cfg {Boolean} disabled - * Render this panel disabled (default is false). An important note when using the disabled - * config on panels is that IE will often fail to initialize the disabled mask element correectly if - * the panel's layout has not yet completed by the time the Panel is disabled during the render process. - * If you experience this issue, you may need to instead use the {@link #afterlayout} event to initialize - * the disabled state: - *
            
            -new Ext.Panel({
            -    ...
            -    listeners: {
            -        'afterlayout': {
            -            fn: function(p){
            -                p.disable();
            -            },
            -            single: true // important, as many layouts can occur
            -        }
            +    getTool : function(id) {
            +        return this.tools[id];
                 }
            +
            +/**
            + * @cfg {String} autoEl @hide
            + */
             });
            -
            +Ext.reg('panel', Ext.Panel); +/** + * @class Ext.Editor + * @extends Ext.Component + * A base editor field that handles displaying/hiding on demand and has some built-in sizing and event handling logic. + * @constructor + * Create a new Editor + * @param {Object} config The config object + * @xtype editor + */ +Ext.Editor = function(field, config){ + if(field.field){ + this.field = Ext.create(field.field, 'textfield'); + config = Ext.apply({}, field); // copy so we don't disturb original config + delete config.field; + }else{ + this.field = field; + } + Ext.Editor.superclass.constructor.call(this, config); +}; + +Ext.extend(Ext.Editor, Ext.Component, { + /** + * @cfg {Ext.form.Field} field + * The Field object (or descendant) or config object for field + */ + /** + * @cfg {Boolean} allowBlur + * True to {@link #completeEdit complete the editing process} if in edit mode when the + * field is blurred. Defaults to true. */ + allowBlur: true, /** - * @cfg {Boolean} autoHeight - * true to use height:'auto', false to use fixed height (defaults to false). - * Note: Setting autoHeight:true means that the browser will manage the panel's height - * based on its contents, and that Ext will not manage it at all. If the panel is within a layout that - * manages dimensions (fit, border, etc.) then setting autoHeight:true - * can cause issues with scrolling and will not generally work as expected since the panel will take - * on the height of its contents rather than the height required by the Ext layout. + * @cfg {Boolean/String} autoSize + * True for the editor to automatically adopt the size of the underlying field, "width" to adopt the width only, + * or "height" to adopt the height only, "none" to always use the field dimensions. (defaults to false) */ - - /** - * @cfg {String} baseCls - * The base CSS class to apply to this panel's element (defaults to 'x-panel'). - *

            Another option available by default is to specify 'x-plain' which strips all styling - * except for required attributes for Ext layouts to function (e.g. overflow:hidden). - * See {@link #unstyled} also.

            + * @cfg {Boolean} revertInvalid + * True to automatically revert the field value and cancel the edit when the user completes an edit and the field + * validation fails (defaults to true) */ - baseCls : 'x-panel', /** - * @cfg {String} collapsedCls - * A CSS class to add to the panel's element after it has been collapsed (defaults to - * 'x-panel-collapsed'). + * @cfg {Boolean} ignoreNoChange + * True to skip the edit completion process (no save, no events fired) if the user completes an edit and + * the value has not changed (defaults to false). Applies only to string values - edits for other data types + * will never be ignored. */ - collapsedCls : 'x-panel-collapsed', /** - * @cfg {Boolean} maskDisabled - * true to mask the panel when it is {@link #disabled}, false to not mask it (defaults - * to true). Either way, the panel will always tell its contained elements to disable themselves - * when it is disabled, but masking the panel can provide an additional visual cue that the panel is - * disabled. + * @cfg {Boolean} hideEl + * False to keep the bound element visible while the editor is displayed (defaults to true) */ - maskDisabled : true, /** - * @cfg {Boolean} animCollapse - * true to animate the transition when the panel is collapsed, false to skip the - * animation (defaults to true if the {@link Ext.Fx} class is available, otherwise false). + * @cfg {Mixed} value + * The data value of the underlying field (defaults to "") */ - animCollapse : Ext.enableFx, + value : "", /** - * @cfg {Boolean} headerAsText - * true to display the panel {@link #title} in the {@link #header}, - * false to hide it (defaults to true). + * @cfg {String} alignment + * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "c-c?"). */ - headerAsText : true, + alignment: "c-c?", /** - * @cfg {String} buttonAlign - * The alignment of any {@link #buttons} added to this panel. Valid values are 'right', - * 'left' and 'center' (defaults to 'right'). + * @cfg {Array} offsets + * The offsets to use when aligning (see {@link Ext.Element#alignTo} for more details. Defaults to [0, 0]. */ - buttonAlign : 'right', + offsets: [0, 0], /** - * @cfg {Boolean} collapsed - * true to render the panel collapsed, false to render it expanded (defaults to - * false). + * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop" + * for bottom-right shadow (defaults to "frame") */ - collapsed : false, + shadow : "frame", /** - * @cfg {Boolean} collapseFirst - * true to make sure the collapse/expand toggle button always renders first (to the left of) - * any other tools in the panel's title bar, false to render it last (defaults to true). + * @cfg {Boolean} constrain True to constrain the editor to the viewport */ - collapseFirst : true, + constrain : false, /** - * @cfg {Number} minButtonWidth - * Minimum width in pixels of all {@link #buttons} in this panel (defaults to 75) + * @cfg {Boolean} swallowKeys Handle the keydown/keypress events so they don't propagate (defaults to true) */ - minButtonWidth : 75, + swallowKeys : true, /** - * @cfg {Boolean} unstyled - * Overrides the {@link #baseCls} setting to {@link #baseCls} = 'x-plain' which renders - * the panel unstyled except for required attributes for Ext layouts to function (e.g. overflow:hidden). + * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed. Defaults to true. */ + completeOnEnter : true, /** - * @cfg {String} elements - * A comma-delimited list of panel elements to initialize when the panel is rendered. Normally, this list will be - * generated automatically based on the items added to the panel at config time, but sometimes it might be useful to - * make sure a structural element is rendered even if not specified at config time (for example, you may want - * to add a button or toolbar dynamically after the panel has been rendered). Adding those elements to this - * list will allocate the required placeholders in the panel when it is rendered. Valid values are
              - *
            • header
            • - *
            • tbar (top bar)
            • - *
            • body
            • - *
            • bbar (bottom bar)
            • - *
            • footer
            • - *
            - * Defaults to 'body'. + * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed. Defaults to true. */ - elements : 'body', + cancelOnEsc : true, /** - * @cfg {Boolean} preventBodyReset - * Defaults to false. When set to true, an extra css class 'x-panel-normal' - * will be added to the panel's element, effectively applying css styles suggested by the W3C - * (see http://www.w3.org/TR/CSS21/sample.html) to the Panel's body element (not the header, - * footer, etc.). + * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false) */ - preventBodyReset : false, - - // protected - these could be used to customize the behavior of the window, - // but changing them would not be useful without further mofifications and - // could lead to unexpected or undesirable results. - toolTarget : 'header', - collapseEl : 'bwrap', - slideAnchor : 't', - disabledClass : '', - - // private, notify box this class will handle heights - deferHeight : true, - // private - expandDefaults: { - duration : 0.25 - }, - // private - collapseDefaults : { - duration : 0.25 - }, + updateEl : false, - // private initComponent : function(){ - Ext.Panel.superclass.initComponent.call(this); - + Ext.Editor.superclass.initComponent.call(this); this.addEvents( /** - * @event bodyresize - * Fires after the Panel has been resized. - * @param {Ext.Panel} p the Panel which has been resized. - * @param {Number} width The Panel's new width. - * @param {Number} height The Panel's new height. - */ - 'bodyresize', - /** - * @event titlechange - * Fires after the Panel title has been {@link #title set} or {@link #setTitle changed}. - * @param {Ext.Panel} p the Panel which has had its title changed. - * @param {String} The new title. - */ - 'titlechange', - /** - * @event iconchange - * Fires after the Panel icon class has been {@link #iconCls set} or {@link #setIconClass changed}. - * @param {Ext.Panel} p the Panel which has had its {@link #iconCls icon class} changed. - * @param {String} The new icon class. - * @param {String} The old icon class. - */ - 'iconchange', - /** - * @event collapse - * Fires after the Panel has been collapsed. - * @param {Ext.Panel} p the Panel that has been collapsed. - */ - 'collapse', - /** - * @event expand - * Fires after the Panel has been expanded. - * @param {Ext.Panel} p The Panel that has been expanded. - */ - 'expand', - /** - * @event beforecollapse - * Fires before the Panel is collapsed. A handler can return false to cancel the collapse. - * @param {Ext.Panel} p the Panel being collapsed. - * @param {Boolean} animate True if the collapse is animated, else false. - */ - 'beforecollapse', - /** - * @event beforeexpand - * Fires before the Panel is expanded. A handler can return false to cancel the expand. - * @param {Ext.Panel} p The Panel being expanded. - * @param {Boolean} animate True if the expand is animated, else false. + * @event beforestartedit + * Fires when editing is initiated, but before the value changes. Editing can be canceled by returning + * false from the handler of this event. + * @param {Editor} this + * @param {Ext.Element} boundEl The underlying element bound to this editor + * @param {Mixed} value The field value being set */ - 'beforeexpand', + "beforestartedit", /** - * @event beforeclose - * Fires before the Panel is closed. Note that Panels do not directly support being closed, but some - * Panel subclasses do (like {@link Ext.Window}) or a Panel within a Ext.TabPanel. This event only - * applies to such subclasses. - * A handler can return false to cancel the close. - * @param {Ext.Panel} p The Panel being closed. + * @event startedit + * Fires when this editor is displayed + * @param {Ext.Element} boundEl The underlying element bound to this editor + * @param {Mixed} value The starting field value */ - 'beforeclose', + "startedit", /** - * @event close - * Fires after the Panel is closed. Note that Panels do not directly support being closed, but some - * Panel subclasses do (like {@link Ext.Window}) or a Panel within a Ext.TabPanel. - * @param {Ext.Panel} p The Panel that has been closed. + * @event beforecomplete + * Fires after a change has been made to the field, but before the change is reflected in the underlying + * field. Saving the change to the field can be canceled by returning false from the handler of this event. + * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this + * event will not fire since no edit actually occurred. + * @param {Editor} this + * @param {Mixed} value The current field value + * @param {Mixed} startValue The original field value */ - 'close', + "beforecomplete", /** - * @event activate - * Fires after the Panel has been visually activated. - * Note that Panels do not directly support being activated, but some Panel subclasses - * do (like {@link Ext.Window}). Panels which are child Components of a TabPanel fire the - * activate and deactivate events under the control of the TabPanel. - * @param {Ext.Panel} p The Panel that has been activated. + * @event complete + * Fires after editing is complete and any changed value has been written to the underlying field. + * @param {Editor} this + * @param {Mixed} value The current field value + * @param {Mixed} startValue The original field value */ - 'activate', + "complete", /** - * @event deactivate - * Fires after the Panel has been visually deactivated. - * Note that Panels do not directly support being deactivated, but some Panel subclasses - * do (like {@link Ext.Window}). Panels which are child Components of a TabPanel fire the - * activate and deactivate events under the control of the TabPanel. - * @param {Ext.Panel} p The Panel that has been deactivated. + * @event canceledit + * Fires after editing has been canceled and the editor's value has been reset. + * @param {Editor} this + * @param {Mixed} value The user-entered field value that was discarded + * @param {Mixed} startValue The original field value that was set back into the editor after cancel */ - 'deactivate' + "canceledit", + /** + * @event specialkey + * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. You can check + * {@link Ext.EventObject#getKey} to determine which key was pressed. + * @param {Ext.form.Field} this + * @param {Ext.EventObject} e The event object + */ + "specialkey" ); + }, - if(this.unstyled){ - this.baseCls = 'x-plain'; + // private + onRender : function(ct, position){ + this.el = new Ext.Layer({ + shadow: this.shadow, + cls: "x-editor", + parentEl : ct, + shim : this.shim, + shadowOffset: this.shadowOffset || 4, + id: this.id, + constrain: this.constrain + }); + if(this.zIndex){ + this.el.setZIndex(this.zIndex); + } + this.el.setStyle("overflow", Ext.isGecko ? "auto" : "hidden"); + if(this.field.msgTarget != 'title'){ + this.field.msgTarget = 'qtip'; + } + this.field.inEditor = true; + this.mon(this.field, { + scope: this, + blur: this.onBlur, + specialkey: this.onSpecialKey + }); + if(this.field.grow){ + this.mon(this.field, "autosize", this.el.sync, this.el, {delay:1}); + } + this.field.render(this.el).show(); + this.field.getEl().dom.name = ''; + if(this.swallowKeys){ + this.field.el.swallowEvent([ + 'keypress', // *** Opera + 'keydown' // *** all other browsers + ]); } + }, - // shortcuts - if(this.tbar){ - this.elements += ',tbar'; - if(Ext.isObject(this.tbar)){ - this.topToolbar = this.tbar; + // private + onSpecialKey : function(field, e){ + var key = e.getKey(), + complete = this.completeOnEnter && key == e.ENTER, + cancel = this.cancelOnEsc && key == e.ESC; + if(complete || cancel){ + e.stopEvent(); + if(complete){ + this.completeEdit(); + }else{ + this.cancelEdit(); } - delete this.tbar; - } - if(this.bbar){ - this.elements += ',bbar'; - if(Ext.isObject(this.bbar)){ - this.bottomToolbar = this.bbar; + if(field.triggerBlur){ + field.triggerBlur(); } - delete this.bbar; } + this.fireEvent('specialkey', field, e); + }, - if(this.header === true){ - this.elements += ',header'; - delete this.header; - }else if(this.headerCfg || (this.title && this.header !== false)){ - this.elements += ',header'; + /** + * Starts the editing process and shows the editor. + * @param {Mixed} el The element to edit + * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults + * to the innerHTML of el. + */ + startEdit : function(el, value){ + if(this.editing){ + this.completeEdit(); } - - if(this.footerCfg || this.footer === true){ - this.elements += ',footer'; - delete this.footer; + this.boundEl = Ext.get(el); + var v = value !== undefined ? value : this.boundEl.dom.innerHTML; + if(!this.rendered){ + this.render(this.parentEl || document.body); + } + if(this.fireEvent("beforestartedit", this, this.boundEl, v) !== false){ + this.startValue = v; + this.field.reset(); + this.field.setValue(v); + this.realign(true); + this.editing = true; + this.show(); } + }, - if(this.buttons){ - this.elements += ',footer'; - var btns = this.buttons; - /** - * This Panel's Array of buttons as created from the {@link #buttons} - * config property. Read only. - * @type Array - * @property buttons - */ - this.buttons = []; - for(var i = 0, len = btns.length; i < len; i++) { - if(btns[i].render){ // button instance - this.buttons.push(btns[i]); - }else if(btns[i].xtype){ - this.buttons.push(Ext.create(btns[i], 'button')); - }else{ - this.addButton(btns[i]); - } + // private + doAutoSize : function(){ + if(this.autoSize){ + var sz = this.boundEl.getSize(), + fs = this.field.getSize(); + + switch(this.autoSize){ + case "width": + this.setSize(sz.width, fs.height); + break; + case "height": + this.setSize(fs.width, sz.height); + break; + case "none": + this.setSize(fs.width, fs.height); + break; + default: + this.setSize(sz.width, sz.height); } } - if(this.fbar){ - this.elements += ',footer'; + }, + + /** + * Sets the height and width of this editor. + * @param {Number} width The new width + * @param {Number} height The new height + */ + setSize : function(w, h){ + delete this.field.lastSize; + this.field.setSize(w, h); + if(this.el){ + // IE7 in strict mode doesn't size properly. + if(Ext.isGecko2 || Ext.isOpera || (Ext.isIE7 && Ext.isStrict)){ + // prevent layer scrollbars + this.el.setSize(w, h); + } + this.el.sync(); } - if(this.autoLoad){ - this.on('render', this.doAutoLoad, this, {delay:10}); + }, + + /** + * Realigns the editor to the bound field based on the current alignment config value. + * @param {Boolean} autoSize (optional) True to size the field to the dimensions of the bound element. + */ + realign : function(autoSize){ + if(autoSize === true){ + this.doAutoSize(); } + this.el.alignTo(this.boundEl, this.alignment, this.offsets); }, - // private - createElement : function(name, pnode){ - if(this[name]){ - pnode.appendChild(this[name].dom); + /** + * Ends the editing process, persists the changed value to the underlying field, and hides the editor. + * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after edit (defaults to false) + */ + completeEdit : function(remainVisible){ + if(!this.editing){ return; } - - if(name === 'bwrap' || this.elements.indexOf(name) != -1){ - if(this[name+'Cfg']){ - this[name] = Ext.fly(pnode).createChild(this[name+'Cfg']); - }else{ - var el = document.createElement('div'); - el.className = this[name+'Cls']; - this[name] = Ext.get(pnode.appendChild(el)); - } - if(this[name+'CssClass']){ - this[name].addClass(this[name+'CssClass']); + // Assert combo values first + if (this.field.assertValue) { + this.field.assertValue(); + } + var v = this.getValue(); + if(!this.field.isValid()){ + if(this.revertInvalid !== false){ + this.cancelEdit(remainVisible); } - if(this[name+'Style']){ - this[name].applyStyles(this[name+'Style']); + return; + } + if(String(v) === String(this.startValue) && this.ignoreNoChange){ + this.hideEdit(remainVisible); + return; + } + if(this.fireEvent("beforecomplete", this, v, this.startValue) !== false){ + v = this.getValue(); + if(this.updateEl && this.boundEl){ + this.boundEl.update(v); } + this.hideEdit(remainVisible); + this.fireEvent("complete", this, v, this.startValue); } }, // private - onRender : function(ct, position){ - Ext.Panel.superclass.onRender.call(this, ct, position); - this.createClasses(); + onShow : function(){ + this.el.show(); + if(this.hideEl !== false){ + this.boundEl.hide(); + } + this.field.show().focus(false, true); + this.fireEvent("startedit", this.boundEl, this.startValue); + }, - var el = this.el, - d = el.dom, - bw; - el.addClass(this.baseCls); - if(d.firstChild){ // existing markup - this.header = el.down('.'+this.headerCls); - this.bwrap = el.down('.'+this.bwrapCls); - var cp = this.bwrap ? this.bwrap : el; - this.tbar = cp.down('.'+this.tbarCls); - this.body = cp.down('.'+this.bodyCls); - this.bbar = cp.down('.'+this.bbarCls); - this.footer = cp.down('.'+this.footerCls); - this.fromMarkup = true; + /** + * Cancels the editing process and hides the editor without persisting any changes. The field value will be + * reverted to the original starting value. + * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after + * cancel (defaults to false) + */ + cancelEdit : function(remainVisible){ + if(this.editing){ + var v = this.getValue(); + this.setValue(this.startValue); + this.hideEdit(remainVisible); + this.fireEvent("canceledit", this, v, this.startValue); } - if (this.preventBodyReset === true) { - el.addClass('x-panel-reset'); + }, + + // private + hideEdit: function(remainVisible){ + if(remainVisible !== true){ + this.editing = false; + this.hide(); } - if(this.cls){ - el.addClass(this.cls); + }, + + // private + onBlur : function(){ + // selectSameEditor flag allows the same editor to be started without onBlur firing on itself + if(this.allowBlur === true && this.editing && this.selectSameEditor !== true){ + this.completeEdit(); } + }, - if(this.buttons){ - this.elements += ',footer'; + // private + onHide : function(){ + if(this.editing){ + this.completeEdit(); + return; + } + this.field.blur(); + if(this.field.collapse){ + this.field.collapse(); + } + this.el.hide(); + if(this.hideEl !== false){ + this.boundEl.show(); } + }, + + /** + * Sets the data value of the editor + * @param {Mixed} value Any valid value supported by the underlying field + */ + setValue : function(v){ + this.field.setValue(v); + }, + + /** + * Gets the data value of the editor + * @return {Mixed} The data value + */ + getValue : function(){ + return this.field.getValue(); + }, + + beforeDestroy : function(){ + Ext.destroyMembers(this, 'field'); + + delete this.parentEl; + delete this.boundEl; + } +}); +Ext.reg('editor', Ext.Editor); +/** + * @class Ext.ColorPalette + * @extends Ext.Component + * Simple color palette class for choosing colors. The palette can be rendered to any container.
            + * Here's an example of typical usage: + *
            
            +var cp = new Ext.ColorPalette({value:'993300'});  // initial selected color
            +cp.render('my-div');
             
            -        // This block allows for maximum flexibility and performance when using existing markup
            +cp.on('select', function(palette, selColor){
            +    // do something with selColor
            +});
            +
            + * @constructor + * Create a new ColorPalette + * @param {Object} config The config object + * @xtype colorpalette + */ +Ext.ColorPalette = Ext.extend(Ext.Component, { + /** + * @cfg {String} tpl An existing XTemplate instance to be used in place of the default template for rendering the component. + */ + /** + * @cfg {String} itemCls + * The CSS class to apply to the containing element (defaults to 'x-color-palette') + */ + itemCls : 'x-color-palette', + /** + * @cfg {String} value + * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol). Note that + * the hex codes are case-sensitive. + */ + value : null, + /** + * @cfg {String} clickEvent + * The DOM event that will cause a color to be selected. This can be any valid event name (dblclick, contextmenu). + * Defaults to 'click'. + */ + clickEvent :'click', + // private + ctype : 'Ext.ColorPalette', - // framing requires special markup - if(this.frame){ - el.insertHtml('afterBegin', String.format(Ext.Element.boxMarkup, this.baseCls)); + /** + * @cfg {Boolean} allowReselect If set to true then reselecting a color that is already selected fires the {@link #select} event + */ + allowReselect : false, - this.createElement('header', d.firstChild.firstChild.firstChild); - this.createElement('bwrap', d); + /** + *

            An array of 6-digit color hex code strings (without the # symbol). This array can contain any number + * of colors, and each hex code should be unique. The width of the palette is controlled via CSS by adjusting + * the width property of the 'x-color-palette' class (or assigning a custom class), so you can balance the number + * of colors with the width setting until the box is symmetrical.

            + *

            You can override individual colors if needed:

            + *
            
            +var cp = new Ext.ColorPalette();
            +cp.colors[0] = 'FF0000';  // change the first box to red
            +
            - // append the mid and bottom frame to the bwrap - bw = this.bwrap.dom; - var ml = d.childNodes[1], bl = d.childNodes[2]; - bw.appendChild(ml); - bw.appendChild(bl); +Or you can provide a custom array of your own for complete control: +
            
            +var cp = new Ext.ColorPalette();
            +cp.colors = ['000000', '993300', '333300'];
            +
            + * @type Array + */ + colors : [ + '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333', + '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080', + 'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696', + 'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0', + 'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF' + ], - var mc = bw.firstChild.firstChild.firstChild; - this.createElement('tbar', mc); - this.createElement('body', mc); - this.createElement('bbar', mc); - this.createElement('footer', bw.lastChild.firstChild.firstChild); + /** + * @cfg {Function} handler + * Optional. A function that will handle the select event of this palette. + * The handler is passed the following parameters:
              + *
            • palette : ColorPalette
              The {@link #palette Ext.ColorPalette}.
            • + *
            • color : String
              The 6-digit color hex code (without the # symbol).
            • + *
            + */ + /** + * @cfg {Object} scope + * The scope (this reference) in which the {@link #handler} + * function will be called. Defaults to this ColorPalette instance. + */ + + // private + initComponent : function(){ + Ext.ColorPalette.superclass.initComponent.call(this); + this.addEvents( + /** + * @event select + * Fires when a color is selected + * @param {ColorPalette} this + * @param {String} color The 6-digit color hex code (without the # symbol) + */ + 'select' + ); - if(!this.footer){ - this.bwrap.dom.lastChild.className += ' x-panel-nofooter'; - } - }else{ - this.createElement('header', d); - this.createElement('bwrap', d); + if(this.handler){ + this.on('select', this.handler, this.scope, true); + } + }, - // append the mid and bottom frame to the bwrap - bw = this.bwrap.dom; - this.createElement('tbar', bw); - this.createElement('body', bw); - this.createElement('bbar', bw); - this.createElement('footer', bw); + // private + onRender : function(container, position){ + this.autoEl = { + tag: 'div', + cls: this.itemCls + }; + Ext.ColorPalette.superclass.onRender.call(this, container, position); + var t = this.tpl || new Ext.XTemplate( + ' ' + ); + t.overwrite(this.el, this.colors); + this.mon(this.el, this.clickEvent, this.handleClick, this, {delegate: 'a'}); + if(this.clickEvent != 'click'){ + this.mon(this.el, 'click', Ext.emptyFn, this, {delegate: 'a', preventDefault: true}); + } + }, - if(!this.header){ - this.body.addClass(this.bodyCls + '-noheader'); - if(this.tbar){ - this.tbar.addClass(this.tbarCls + '-noheader'); - } - } + // private + afterRender : function(){ + Ext.ColorPalette.superclass.afterRender.call(this); + if(this.value){ + var s = this.value; + this.value = null; + this.select(s, true); } + }, - if(this.padding !== undefined) { - this.body.setStyle('padding', this.body.addUnits(this.padding)); + // private + handleClick : function(e, t){ + e.preventDefault(); + if(!this.disabled){ + var c = t.className.match(/(?:^|\s)color-(.{6})(?:\s|$)/)[1]; + this.select(c.toUpperCase()); } + }, - if(this.border === false){ - this.el.addClass(this.baseCls + '-noborder'); - this.body.addClass(this.bodyCls + '-noborder'); - if(this.header){ - this.header.addClass(this.headerCls + '-noborder'); - } - if(this.footer){ - this.footer.addClass(this.footerCls + '-noborder'); - } - if(this.tbar){ - this.tbar.addClass(this.tbarCls + '-noborder'); + /** + * Selects the specified color in the palette (fires the {@link #select} event) + * @param {String} color A valid 6-digit color hex code (# will be stripped if included) + * @param {Boolean} suppressEvent (optional) True to stop the select event from firing. Defaults to false. + */ + select : function(color, suppressEvent){ + color = color.replace('#', ''); + if(color != this.value || this.allowReselect){ + var el = this.el; + if(this.value){ + el.child('a.color-'+this.value).removeClass('x-color-palette-sel'); } - if(this.bbar){ - this.bbar.addClass(this.bbarCls + '-noborder'); + el.child('a.color-'+color).addClass('x-color-palette-sel'); + this.value = color; + if(suppressEvent !== true){ + this.fireEvent('select', this, color); } } + } - if(this.bodyBorder === false){ - this.body.addClass(this.bodyCls + '-noborder'); - } + /** + * @cfg {String} autoEl @hide + */ +}); +Ext.reg('colorpalette', Ext.ColorPalette);/** + * @class Ext.DatePicker + * @extends Ext.Component + *

            A popup date picker. This class is used by the {@link Ext.form.DateField DateField} class + * to allow browsing and selection of valid dates.

            + *

            All the string values documented below may be overridden by including an Ext locale file in + * your page.

            + * @constructor + * Create a new DatePicker + * @param {Object} config The config object + * @xtype datepicker + */ +Ext.DatePicker = Ext.extend(Ext.BoxComponent, { + /** + * @cfg {String} todayText + * The text to display on the button that selects the current date (defaults to 'Today') + */ + todayText : 'Today', + /** + * @cfg {String} okText + * The text to display on the ok button (defaults to ' OK ' to give the user extra clicking room) + */ + okText : ' OK ', + /** + * @cfg {String} cancelText + * The text to display on the cancel button (defaults to 'Cancel') + */ + cancelText : 'Cancel', + /** + * @cfg {Function} handler + * Optional. A function that will handle the select event of this picker. + * The handler is passed the following parameters:
              + *
            • picker : DatePicker
              This DatePicker.
            • + *
            • date : Date
              The selected date.
            • + *
            + */ + /** + * @cfg {Object} scope + * The scope (this reference) in which the {@link #handler} + * function will be called. Defaults to this DatePicker instance. + */ + /** + * @cfg {String} todayTip + * A string used to format the message for displaying in a tooltip over the button that + * selects the current date. Defaults to '{0} (Spacebar)' where + * the {0} token is replaced by today's date. + */ + todayTip : '{0} (Spacebar)', + /** + * @cfg {String} minText + * The error text to display if the minDate validation fails (defaults to 'This date is before the minimum date') + */ + minText : 'This date is before the minimum date', + /** + * @cfg {String} maxText + * The error text to display if the maxDate validation fails (defaults to 'This date is after the maximum date') + */ + maxText : 'This date is after the maximum date', + /** + * @cfg {String} format + * The default date format string which can be overriden for localization support. The format must be + * valid according to {@link Date#parseDate} (defaults to 'm/d/y'). + */ + format : 'm/d/y', + /** + * @cfg {String} disabledDaysText + * The tooltip to display when the date falls on a disabled day (defaults to 'Disabled') + */ + disabledDaysText : 'Disabled', + /** + * @cfg {String} disabledDatesText + * The tooltip text to display when the date falls on a disabled date (defaults to 'Disabled') + */ + disabledDatesText : 'Disabled', + /** + * @cfg {Array} monthNames + * An array of textual month names which can be overriden for localization support (defaults to Date.monthNames) + */ + monthNames : Date.monthNames, + /** + * @cfg {Array} dayNames + * An array of textual day names which can be overriden for localization support (defaults to Date.dayNames) + */ + dayNames : Date.dayNames, + /** + * @cfg {String} nextText + * The next month navigation button tooltip (defaults to 'Next Month (Control+Right)') + */ + nextText : 'Next Month (Control+Right)', + /** + * @cfg {String} prevText + * The previous month navigation button tooltip (defaults to 'Previous Month (Control+Left)') + */ + prevText : 'Previous Month (Control+Left)', + /** + * @cfg {String} monthYearText + * The header month selector tooltip (defaults to 'Choose a month (Control+Up/Down to move years)') + */ + monthYearText : 'Choose a month (Control+Up/Down to move years)', + /** + * @cfg {Number} startDay + * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday) + */ + startDay : 0, + /** + * @cfg {Boolean} showToday + * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar + * that selects the current date (defaults to true). + */ + showToday : true, + /** + * @cfg {Date} minDate + * Minimum allowable date (JavaScript date object, defaults to null) + */ + /** + * @cfg {Date} maxDate + * Maximum allowable date (JavaScript date object, defaults to null) + */ + /** + * @cfg {Array} disabledDays + * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null). + */ + /** + * @cfg {RegExp} disabledDatesRE + * JavaScript regular expression used to disable a pattern of dates (defaults to null). The {@link #disabledDates} + * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the + * disabledDates value. + */ + /** + * @cfg {Array} disabledDates + * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular + * expression so they are very powerful. Some examples: + *
              + *
            • ['03/08/2003', '09/16/2003'] would disable those exact dates
            • + *
            • ['03/08', '09/16'] would disable those days for every year
            • + *
            • ['^03/08'] would only match the beginning (useful if you are using short years)
            • + *
            • ['03/../2006'] would disable every day in March 2006
            • + *
            • ['^03'] would disable every day in every March
            • + *
            + * Note that the format of the dates included in the array should exactly match the {@link #format} config. + * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to + * escape the dot when restricting dates. For example: ['03\\.08\\.03']. + */ - this.bwrap.enableDisplayMode('block'); + // private + // Set by other components to stop the picker focus being updated when the value changes. + focusOnSelect: true, - if(this.header){ - this.header.unselectable(); + // default value used to initialise each date in the DatePicker + // (note: 12 noon was chosen because it steers well clear of all DST timezone changes) + initHour: 12, // 24-hour format - // for tools, we need to wrap any existing header markup - if(this.headerAsText){ - this.header.dom.innerHTML = - ''+this.header.dom.innerHTML+''; + // private + initComponent : function(){ + Ext.DatePicker.superclass.initComponent.call(this); - if(this.iconCls){ - this.setIconClass(this.iconCls); - } - } - } + this.value = this.value ? + this.value.clearTime(true) : new Date().clearTime(); - if(this.floating){ - this.makeFloating(this.floating); - } + this.addEvents( + /** + * @event select + * Fires when a date is selected + * @param {DatePicker} this DatePicker + * @param {Date} date The selected date + */ + 'select' + ); - if(this.collapsible){ - this.tools = this.tools ? this.tools.slice(0) : []; - if(!this.hideCollapseTool){ - this.tools[this.collapseFirst?'unshift':'push']({ - id: 'toggle', - handler : this.toggleCollapse, - scope: this - }); - } - if(this.titleCollapse && this.header){ - this.mon(this.header, 'click', this.toggleCollapse, this); - this.header.setStyle('cursor', 'pointer'); - } - } - if(this.tools){ - var ts = this.tools; - this.tools = {}; - this.addTool.apply(this, ts); - }else{ - this.tools = {}; + if(this.handler){ + this.on('select', this.handler, this.scope || this); } - if(this.buttons && this.buttons.length > 0){ - this.fbar = new Ext.Toolbar({ - items: this.buttons, - toolbarCls: 'x-panel-fbar' - }); - } - this.toolbars = []; - if(this.fbar){ - this.fbar = Ext.create(this.fbar, 'toolbar'); - this.fbar.enableOverflow = false; - if(this.fbar.items){ - this.fbar.items.each(function(c){ - c.minWidth = c.minWidth || this.minButtonWidth; - }, this); - } - this.fbar.toolbarCls = 'x-panel-fbar'; + this.initDisabledDays(); + }, - var bct = this.footer.createChild({cls: 'x-panel-btns x-panel-btns-'+this.buttonAlign}); - this.fbar.ownerCt = this; - this.fbar.render(bct); - bct.createChild({cls:'x-clear'}); - this.toolbars.push(this.fbar); - } + // private + initDisabledDays : function(){ + if(!this.disabledDatesRE && this.disabledDates){ + var dd = this.disabledDates, + len = dd.length - 1, + re = '(?:'; - if(this.tbar && this.topToolbar){ - if(Ext.isArray(this.topToolbar)){ - this.topToolbar = new Ext.Toolbar(this.topToolbar); - }else if(!this.topToolbar.events){ - this.topToolbar = Ext.create(this.topToolbar, 'toolbar'); - } - this.topToolbar.ownerCt = this; - this.topToolbar.render(this.tbar); - this.toolbars.push(this.topToolbar); - } - if(this.bbar && this.bottomToolbar){ - if(Ext.isArray(this.bottomToolbar)){ - this.bottomToolbar = new Ext.Toolbar(this.bottomToolbar); - }else if(!this.bottomToolbar.events){ - this.bottomToolbar = Ext.create(this.bottomToolbar, 'toolbar'); - } - this.bottomToolbar.ownerCt = this; - this.bottomToolbar.render(this.bbar); - this.toolbars.push(this.bottomToolbar); + Ext.each(dd, function(d, i){ + re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i]; + if(i != len){ + re += '|'; + } + }, this); + this.disabledDatesRE = new RegExp(re + ')'); } - Ext.each(this.toolbars, function(tb){ - tb.on({ - scope: this, - afterlayout: this.syncHeight, - remove: this.syncHeight - }); - }, this); }, /** - * Sets the CSS class that provides the icon image for this panel. This method will replace any existing - * icon class if one has already been set and fire the {@link #iconchange} event after completion. - * @param {String} cls The new CSS class name + * Replaces any existing disabled dates with new values and refreshes the DatePicker. + * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config + * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates. */ - setIconClass : function(cls){ - var old = this.iconCls; - this.iconCls = cls; - if(this.rendered && this.header){ - if(this.frame){ - this.header.addClass('x-panel-icon'); - this.header.replaceClass(old, this.iconCls); - }else{ - var hd = this.header.dom; - var img = hd.firstChild && String(hd.firstChild.tagName).toLowerCase() == 'img' ? hd.firstChild : null; - if(img){ - Ext.fly(img).replaceClass(old, this.iconCls); - }else{ - Ext.DomHelper.insertBefore(hd.firstChild, { - tag:'img', src: Ext.BLANK_IMAGE_URL, cls:'x-panel-inline-icon '+this.iconCls - }); - } - } + setDisabledDates : function(dd){ + if(Ext.isArray(dd)){ + this.disabledDates = dd; + this.disabledDatesRE = null; + }else{ + this.disabledDatesRE = dd; } - this.fireEvent('iconchange', this, cls, old); + this.initDisabledDays(); + this.update(this.value, true); }, - // private - makeFloating : function(cfg){ - this.floating = true; - this.el = new Ext.Layer( - Ext.isObject(cfg) ? cfg : { - shadow: this.shadow !== undefined ? this.shadow : 'sides', - shadowOffset: this.shadowOffset, - constrain:false, - shim: this.shim === false ? false : undefined - }, this.el - ); + /** + * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker. + * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config + * for details on supported values. + */ + setDisabledDays : function(dd){ + this.disabledDays = dd; + this.update(this.value, true); }, /** - * Returns the {@link Ext.Toolbar toolbar} from the top ({@link #tbar}) section of the panel. - * @return {Ext.Toolbar} The toolbar + * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker. + * @param {Date} value The minimum date that can be selected */ - getTopToolbar : function(){ - return this.topToolbar; + setMinDate : function(dt){ + this.minDate = dt; + this.update(this.value, true); }, /** - * Returns the {@link Ext.Toolbar toolbar} from the bottom ({@link #bbar}) section of the panel. - * @return {Ext.Toolbar} The toolbar + * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker. + * @param {Date} value The maximum date that can be selected */ - getBottomToolbar : function(){ - return this.bottomToolbar; + setMaxDate : function(dt){ + this.maxDate = dt; + this.update(this.value, true); }, /** - * Adds a button to this panel. Note that this method must be called prior to rendering. The preferred - * approach is to add buttons via the {@link #buttons} config. - * @param {String/Object} config A valid {@link Ext.Button} config. A string will become the text for a default - * button config, an object will be treated as a button config object. - * @param {Function} handler The function to be called on button {@link Ext.Button#click} - * @param {Object} scope The scope to use for the button handler function - * @return {Ext.Button} The button that was added + * Sets the value of the date field + * @param {Date} value The date to set */ - addButton : function(config, handler, scope){ - var bc = { - handler: handler, - scope: scope, - minWidth: this.minButtonWidth, - hideParent:true - }; - if(typeof config == "string"){ - bc.text = config; - }else{ - Ext.apply(bc, config); - } - var btn = new Ext.Button(bc); - if(!this.buttons){ - this.buttons = []; - } - this.buttons.push(btn); - return btn; + setValue : function(value){ + this.value = value.clearTime(true); + this.update(this.value); }, - // private - addTool : function(){ - if(!this[this.toolTarget]) { // no where to render tools! - return; - } - if(!this.toolTemplate){ - // initialize the global tool template on first use - var tt = new Ext.Template( - '
             
            ' - ); - tt.disableFormats = true; - tt.compile(); - Ext.Panel.prototype.toolTemplate = tt; - } - for(var i = 0, a = arguments, len = a.length; i < len; i++) { - var tc = a[i]; - if(!this.tools[tc.id]){ - var overCls = 'x-tool-'+tc.id+'-over'; - var t = this.toolTemplate.insertFirst((tc.align !== 'left') ? this[this.toolTarget] : this[this.toolTarget].child('span'), tc, true); - this.tools[tc.id] = t; - t.enableDisplayMode('block'); - this.mon(t, 'click', this.createToolHandler(t, tc, overCls, this)); - if(tc.on){ - this.mon(t, tc.on); - } - if(tc.hidden){ - t.hide(); - } - if(tc.qtip){ - if(Ext.isObject(tc.qtip)){ - Ext.QuickTips.register(Ext.apply({ - target: t.id - }, tc.qtip)); - } else { - t.dom.qtip = tc.qtip; - } - } - t.addClassOnOver(overCls); - } - } + /** + * Gets the current selected value of the date field + * @return {Date} The selected date + */ + getValue : function(){ + return this.value; }, - onLayout : function(){ - if(this.toolbars.length > 0){ - this.duringLayout = true; - Ext.each(this.toolbars, function(tb){ - tb.doLayout(); - }); - delete this.duringLayout; - this.syncHeight(); - } + // private + focus : function(){ + this.update(this.activeDate); }, - syncHeight : function(){ - if(!(this.autoHeight || this.duringLayout)){ - var last = this.lastSize; - if(last && !Ext.isEmpty(last.height)){ - var old = last.height, h = this.el.getHeight(); - if(old != 'auto' && old != h){ - var bd = this.body, bdh = bd.getHeight(); - h = Math.max(bdh + old - h, 0); - if(bdh > 0 && bdh != h){ - bd.setHeight(h); - if(Ext.isIE && h <= 0){ - return; - } - var sz = bd.getSize(); - this.fireEvent('bodyresize', sz.width, sz.height); - } - } - } + // private + onEnable: function(initial){ + Ext.DatePicker.superclass.onEnable.call(this); + this.doDisabled(false); + this.update(initial ? this.value : this.activeDate); + if(Ext.isIE){ + this.el.repaint(); } + }, // private - onShow : function(){ - if(this.floating){ - return this.el.show(); + onDisable : function(){ + Ext.DatePicker.superclass.onDisable.call(this); + this.doDisabled(true); + if(Ext.isIE && !Ext.isIE8){ + /* Really strange problem in IE6/7, when disabled, have to explicitly + * repaint each of the nodes to get them to display correctly, simply + * calling repaint on the main element doesn't appear to be enough. + */ + Ext.each([].concat(this.textNodes, this.el.query('th span')), function(el){ + Ext.fly(el).repaint(); + }); } - Ext.Panel.superclass.onShow.call(this); }, // private - onHide : function(){ - if(this.floating){ - return this.el.hide(); + doDisabled : function(disabled){ + this.keyNav.setDisabled(disabled); + this.prevRepeater.setDisabled(disabled); + this.nextRepeater.setDisabled(disabled); + if(this.showToday){ + this.todayKeyListener.setDisabled(disabled); + this.todayBtn.setDisabled(disabled); } - Ext.Panel.superclass.onHide.call(this); }, // private - createToolHandler : function(t, tc, overCls, panel){ - return function(e){ - t.removeClass(overCls); - if(tc.stopEvent !== false){ - e.stopEvent(); - } - if(tc.handler){ - tc.handler.call(tc.scope || t, e, t, panel, tc); + onRender : function(container, position){ + var m = [ + '
        • ', + '', + '', + this.showToday ? '' : '', + '
            
          '], + dn = this.dayNames, + i; + for(i = 0; i < 7; i++){ + var d = this.startDay+i; + if(d > 6){ + d = d-7; } - }; - }, - - // private - afterRender : function(){ - if(this.floating && !this.hidden){ - this.el.show(); - } - if(this.title){ - this.setTitle(this.title); - } - this.setAutoScroll(); - if(this.html){ - this.body.update(Ext.isObject(this.html) ? - Ext.DomHelper.markup(this.html) : - this.html); - delete this.html; + m.push(''); } - if(this.contentEl){ - var ce = Ext.getDom(this.contentEl); - Ext.fly(ce).removeClass(['x-hidden', 'x-hide-display']); - this.body.dom.appendChild(ce); + m[m.length] = ''; + for(i = 0; i < 42; i++) { + if(i % 7 === 0 && i !== 0){ + m[m.length] = ''; + } + m[m.length] = ''; } - if(this.collapsed){ - this.collapsed = false; - this.collapse(false); + m.push('
          ', dn[d].substr(0,1), '
          '); + + var el = document.createElement('div'); + el.className = 'x-date-picker'; + el.innerHTML = m.join(''); + + container.dom.insertBefore(el, position); + + this.el = Ext.get(el); + this.eventEl = Ext.get(el.firstChild); + + this.prevRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-left a'), { + handler: this.showPrevMonth, + scope: this, + preventDefault:true, + stopDefault:true + }); + + this.nextRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-right a'), { + handler: this.showNextMonth, + scope: this, + preventDefault:true, + stopDefault:true + }); + + this.monthPicker = this.el.down('div.x-date-mp'); + this.monthPicker.enableDisplayMode('block'); + + this.keyNav = new Ext.KeyNav(this.eventEl, { + 'left' : function(e){ + if(e.ctrlKey){ + this.showPrevMonth(); + }else{ + this.update(this.activeDate.add('d', -1)); + } + }, + + 'right' : function(e){ + if(e.ctrlKey){ + this.showNextMonth(); + }else{ + this.update(this.activeDate.add('d', 1)); + } + }, + + 'up' : function(e){ + if(e.ctrlKey){ + this.showNextYear(); + }else{ + this.update(this.activeDate.add('d', -7)); + } + }, + + 'down' : function(e){ + if(e.ctrlKey){ + this.showPrevYear(); + }else{ + this.update(this.activeDate.add('d', 7)); + } + }, + + 'pageUp' : function(e){ + this.showNextMonth(); + }, + + 'pageDown' : function(e){ + this.showPrevMonth(); + }, + + 'enter' : function(e){ + e.stopPropagation(); + return true; + }, + + scope : this + }); + + this.el.unselectable(); + + this.cells = this.el.select('table.x-date-inner tbody td'); + this.textNodes = this.el.query('table.x-date-inner tbody span'); + + this.mbtn = new Ext.Button({ + text: ' ', + tooltip: this.monthYearText, + renderTo: this.el.child('td.x-date-middle', true) + }); + this.mbtn.el.child('em').addClass('x-btn-arrow'); + + if(this.showToday){ + this.todayKeyListener = this.eventEl.addKeyListener(Ext.EventObject.SPACE, this.selectToday, this); + var today = (new Date()).dateFormat(this.format); + this.todayBtn = new Ext.Button({ + renderTo: this.el.child('td.x-date-bottom', true), + text: String.format(this.todayText, today), + tooltip: String.format(this.todayTip, today), + handler: this.selectToday, + scope: this + }); } - Ext.Panel.superclass.afterRender.call(this); // do sizing calcs last - this.initEvents(); + this.mon(this.eventEl, 'mousewheel', this.handleMouseWheel, this); + this.mon(this.eventEl, 'click', this.handleDateClick, this, {delegate: 'a.x-date-date'}); + this.mon(this.mbtn, 'click', this.showMonthPicker, this); + this.onEnable(true); }, // private - setAutoScroll : function(){ - if(this.rendered && this.autoScroll){ - var el = this.body || this.el; - if(el){ - el.setOverflow('auto'); + createMonthPicker : function(){ + if(!this.monthPicker.dom.firstChild){ + var buf = ['']; + for(var i = 0; i < 6; i++){ + buf.push( + '', + '', + i === 0 ? + '' : + '' + ); } + buf.push( + '', + '
          ', Date.getShortMonthName(i), '', Date.getShortMonthName(i + 6), '
          ' + ); + this.monthPicker.update(buf.join('')); + + this.mon(this.monthPicker, 'click', this.onMonthClick, this); + this.mon(this.monthPicker, 'dblclick', this.onMonthDblClick, this); + + this.mpMonths = this.monthPicker.select('td.x-date-mp-month'); + this.mpYears = this.monthPicker.select('td.x-date-mp-year'); + + this.mpMonths.each(function(m, a, i){ + i += 1; + if((i%2) === 0){ + m.dom.xmonth = 5 + Math.round(i * 0.5); + }else{ + m.dom.xmonth = Math.round((i-1) * 0.5); + } + }); } }, // private - getKeyMap : function(){ - if(!this.keyMap){ - this.keyMap = new Ext.KeyMap(this.el, this.keys); + showMonthPicker : function(){ + if(!this.disabled){ + this.createMonthPicker(); + var size = this.el.getSize(); + this.monthPicker.setSize(size); + this.monthPicker.child('table').setSize(size); + + this.mpSelMonth = (this.activeDate || this.value).getMonth(); + this.updateMPMonth(this.mpSelMonth); + this.mpSelYear = (this.activeDate || this.value).getFullYear(); + this.updateMPYear(this.mpSelYear); + + this.monthPicker.slideIn('t', {duration:0.2}); } - return this.keyMap; }, // private - initEvents : function(){ - if(this.keys){ - this.getKeyMap(); - } - if(this.draggable){ - this.initDraggable(); + updateMPYear : function(y){ + this.mpyear = y; + var ys = this.mpYears.elements; + for(var i = 1; i <= 10; i++){ + var td = ys[i-1], y2; + if((i%2) === 0){ + y2 = y + Math.round(i * 0.5); + td.firstChild.innerHTML = y2; + td.xyear = y2; + }else{ + y2 = y - (5-Math.round(i * 0.5)); + td.firstChild.innerHTML = y2; + td.xyear = y2; + } + this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel'); } }, // private - initDraggable : function(){ - /** - *

          If this Panel is configured {@link #draggable}, this property will contain - * an instance of {@link Ext.dd.DragSource} which handles dragging the Panel.

          - * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource} - * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}. - * @type Ext.dd.DragSource. - * @property dd - */ - this.dd = new Ext.Panel.DD(this, typeof this.draggable == 'boolean' ? null : this.draggable); + updateMPMonth : function(sm){ + this.mpMonths.each(function(m, a, i){ + m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel'); + }); }, // private - beforeEffect : function(){ - if(this.floating){ - this.el.beforeAction(); - } - this.el.addClass('x-panel-animated'); + selectMPMonth : function(m){ + }, // private - afterEffect : function(){ - this.syncShadow(); - this.el.removeClass('x-panel-animated'); + onMonthClick : function(e, t){ + e.stopEvent(); + var el = new Ext.Element(t), pn; + if(el.is('button.x-date-mp-cancel')){ + this.hideMonthPicker(); + } + else if(el.is('button.x-date-mp-ok')){ + var d = new Date(this.mpSelYear, this.mpSelMonth, (this.activeDate || this.value).getDate()); + if(d.getMonth() != this.mpSelMonth){ + // 'fix' the JS rolling date conversion if needed + d = new Date(this.mpSelYear, this.mpSelMonth, 1).getLastDateOfMonth(); + } + this.update(d); + this.hideMonthPicker(); + } + else if((pn = el.up('td.x-date-mp-month', 2))){ + this.mpMonths.removeClass('x-date-mp-sel'); + pn.addClass('x-date-mp-sel'); + this.mpSelMonth = pn.dom.xmonth; + } + else if((pn = el.up('td.x-date-mp-year', 2))){ + this.mpYears.removeClass('x-date-mp-sel'); + pn.addClass('x-date-mp-sel'); + this.mpSelYear = pn.dom.xyear; + } + else if(el.is('a.x-date-mp-prev')){ + this.updateMPYear(this.mpyear-10); + } + else if(el.is('a.x-date-mp-next')){ + this.updateMPYear(this.mpyear+10); + } }, - // private - wraps up an animation param with internal callbacks - createEffect : function(a, cb, scope){ - var o = { - scope:scope, - block:true - }; - if(a === true){ - o.callback = cb; - return o; - }else if(!a.callback){ - o.callback = cb; - }else { // wrap it up - o.callback = function(){ - cb.call(scope); - Ext.callback(a.callback, a.scope); - }; + // private + onMonthDblClick : function(e, t){ + e.stopEvent(); + var el = new Ext.Element(t), pn; + if((pn = el.up('td.x-date-mp-month', 2))){ + this.update(new Date(this.mpSelYear, pn.dom.xmonth, (this.activeDate || this.value).getDate())); + this.hideMonthPicker(); + } + else if((pn = el.up('td.x-date-mp-year', 2))){ + this.update(new Date(pn.dom.xyear, this.mpSelMonth, (this.activeDate || this.value).getDate())); + this.hideMonthPicker(); } - return Ext.applyIf(o, a); }, - /** - * Collapses the panel body so that it becomes hidden. Fires the {@link #beforecollapse} event which will - * cancel the collapse action if it returns false. - * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the - * {@link #animCollapse} panel config) - * @return {Ext.Panel} this - */ - collapse : function(animate){ - if(this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforecollapse', this, animate) === false){ - return; - } - var doAnim = animate === true || (animate !== false && this.animCollapse); - this.beforeEffect(); - this.onCollapse(doAnim, animate); - return this; + // private + hideMonthPicker : function(disableAnim){ + if(this.monthPicker){ + if(disableAnim === true){ + this.monthPicker.hide(); + }else{ + this.monthPicker.slideOut('t', {duration:0.2}); + } + } }, // private - onCollapse : function(doAnim, animArg){ - if(doAnim){ - this[this.collapseEl].slideOut(this.slideAnchor, - Ext.apply(this.createEffect(animArg||true, this.afterCollapse, this), - this.collapseDefaults)); - }else{ - this[this.collapseEl].hide(); - this.afterCollapse(); - } + showPrevMonth : function(e){ + this.update(this.activeDate.add('mo', -1)); }, // private - afterCollapse : function(){ - this.collapsed = true; - this.el.addClass(this.collapsedCls); - this.afterEffect(); - this.fireEvent('collapse', this); + showNextMonth : function(e){ + this.update(this.activeDate.add('mo', 1)); }, - /** - * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will - * cancel the expand action if it returns false. - * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the - * {@link #animCollapse} panel config) - * @return {Ext.Panel} this - */ - expand : function(animate){ - if(!this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforeexpand', this, animate) === false){ - return; - } - var doAnim = animate === true || (animate !== false && this.animCollapse); - this.el.removeClass(this.collapsedCls); - this.beforeEffect(); - this.onExpand(doAnim, animate); - return this; + // private + showPrevYear : function(){ + this.update(this.activeDate.add('y', -1)); }, // private - onExpand : function(doAnim, animArg){ - if(doAnim){ - this[this.collapseEl].slideIn(this.slideAnchor, - Ext.apply(this.createEffect(animArg||true, this.afterExpand, this), - this.expandDefaults)); - }else{ - this[this.collapseEl].show(); - this.afterExpand(); - } + showNextYear : function(){ + this.update(this.activeDate.add('y', 1)); }, // private - afterExpand : function(){ - this.collapsed = false; - this.afterEffect(); - if(this.deferLayout !== undefined){ - this.doLayout(true); + handleMouseWheel : function(e){ + e.stopEvent(); + if(!this.disabled){ + var delta = e.getWheelDelta(); + if(delta > 0){ + this.showPrevMonth(); + } else if(delta < 0){ + this.showNextMonth(); + } } - this.fireEvent('expand', this); - }, - - /** - * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel. - * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the - * {@link #animCollapse} panel config) - * @return {Ext.Panel} this - */ - toggleCollapse : function(animate){ - this[this.collapsed ? 'expand' : 'collapse'](animate); - return this; }, // private - onDisable : function(){ - if(this.rendered && this.maskDisabled){ - this.el.mask(); + handleDateClick : function(e, t){ + e.stopEvent(); + if(!this.disabled && t.dateValue && !Ext.fly(t.parentNode).hasClass('x-date-disabled')){ + this.cancelFocus = this.focusOnSelect === false; + this.setValue(new Date(t.dateValue)); + delete this.cancelFocus; + this.fireEvent('select', this, this.value); } - Ext.Panel.superclass.onDisable.call(this); }, // private - onEnable : function(){ - if(this.rendered && this.maskDisabled){ - this.el.unmask(); + selectToday : function(){ + if(this.todayBtn && !this.todayBtn.disabled){ + this.setValue(new Date().clearTime()); + this.fireEvent('select', this, this.value); } - Ext.Panel.superclass.onEnable.call(this); }, // private - onResize : function(w, h){ - if(w !== undefined || h !== undefined){ - if(!this.collapsed){ - if(typeof w == 'number'){ - w = this.adjustBodyWidth(w - this.getFrameWidth()); - if(this.tbar){ - this.tbar.setWidth(w); - if(this.topToolbar){ - this.topToolbar.setSize(w); - } + update : function(date, forceRefresh){ + if(this.rendered){ + var vd = this.activeDate, vis = this.isVisible(); + this.activeDate = date; + if(!forceRefresh && vd && this.el){ + var t = date.getTime(); + if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){ + this.cells.removeClass('x-date-selected'); + this.cells.each(function(c){ + if(c.dom.firstChild.dateValue == t){ + c.addClass('x-date-selected'); + if(vis && !this.cancelFocus){ + Ext.fly(c.dom.firstChild).focus(50); + } + return false; + } + }, this); + return; + } + } + var days = date.getDaysInMonth(), + firstOfMonth = date.getFirstDateOfMonth(), + startingPos = firstOfMonth.getDay()-this.startDay; + + if(startingPos < 0){ + startingPos += 7; + } + days += startingPos; + + var pm = date.add('mo', -1), + prevStart = pm.getDaysInMonth()-startingPos, + cells = this.cells.elements, + textEls = this.textNodes, + // convert everything to numbers so it's fast + d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart, this.initHour)), + today = new Date().clearTime().getTime(), + sel = date.clearTime(true).getTime(), + min = this.minDate ? this.minDate.clearTime(true) : Number.NEGATIVE_INFINITY, + max = this.maxDate ? this.maxDate.clearTime(true) : Number.POSITIVE_INFINITY, + ddMatch = this.disabledDatesRE, + ddText = this.disabledDatesText, + ddays = this.disabledDays ? this.disabledDays.join('') : false, + ddaysText = this.disabledDaysText, + format = this.format; + + if(this.showToday){ + var td = new Date().clearTime(), + disable = (td < min || td > max || + (ddMatch && format && ddMatch.test(td.dateFormat(format))) || + (ddays && ddays.indexOf(td.getDay()) != -1)); + + if(!this.disabled){ + this.todayBtn.setDisabled(disable); + this.todayKeyListener[disable ? 'disable' : 'enable'](); + } + } + + var setCellClass = function(cal, cell){ + cell.title = ''; + var t = d.clearTime(true).getTime(); + cell.firstChild.dateValue = t; + if(t == today){ + cell.className += ' x-date-today'; + cell.title = cal.todayText; + } + if(t == sel){ + cell.className += ' x-date-selected'; + if(vis){ + Ext.fly(cell.firstChild).focus(50); } - if(this.bbar){ - this.bbar.setWidth(w); - if(this.bottomToolbar){ - this.bottomToolbar.setSize(w); - } + } + // disabling + if(t < min) { + cell.className = ' x-date-disabled'; + cell.title = cal.minText; + return; + } + if(t > max) { + cell.className = ' x-date-disabled'; + cell.title = cal.maxText; + return; + } + if(ddays){ + if(ddays.indexOf(d.getDay()) != -1){ + cell.title = ddaysText; + cell.className = ' x-date-disabled'; } - if(this.fbar){ - var f = this.fbar, - fWidth = 1, - strict = Ext.isStrict; - if(this.buttonAlign == 'left'){ - fWidth = w - f.container.getFrameWidth('lr'); - }else{ - //center/right alignment off in webkit - if(Ext.isIE || Ext.isWebKit){ - //center alignment ok on webkit. - //right broken in both, center on IE - if(!(this.buttonAlign == 'center' && Ext.isWebKit) && (!strict || (!Ext.isIE8 && strict))){ - (function(){ - f.setWidth(f.getEl().child('.x-toolbar-ct').getWidth()); - }).defer(1); - }else{ - fWidth = 'auto'; - } - }else{ - fWidth = 'auto'; - } - } - f.setWidth(fWidth); + } + if(ddMatch && format){ + var fvalue = d.dateFormat(format); + if(ddMatch.test(fvalue)){ + cell.title = ddText.replace('%0', fvalue); + cell.className = ' x-date-disabled'; } - this.body.setWidth(w); - }else if(w == 'auto'){ - this.body.setWidth(w); } + }; - if(typeof h == 'number'){ - h = Math.max(0, this.adjustBodyHeight(h - this.getFrameHeight())); - this.body.setHeight(h); - }else if(h == 'auto'){ - this.body.setHeight(h); - } + var i = 0; + for(; i < startingPos; i++) { + textEls[i].innerHTML = (++prevStart); + d.setDate(d.getDate()+1); + cells[i].className = 'x-date-prevday'; + setCellClass(this, cells[i]); + } + for(; i < days; i++){ + var intDay = i - startingPos + 1; + textEls[i].innerHTML = (intDay); + d.setDate(d.getDate()+1); + cells[i].className = 'x-date-active'; + setCellClass(this, cells[i]); + } + var extraDays = 0; + for(; i < 42; i++) { + textEls[i].innerHTML = (++extraDays); + d.setDate(d.getDate()+1); + cells[i].className = 'x-date-nextday'; + setCellClass(this, cells[i]); + } - if(this.disabled && this.el._mask){ - this.el._mask.setSize(this.el.dom.clientWidth, this.el.getHeight()); - } - }else{ - this.queuedBodySize = {width: w, height: h}; - if(!this.queuedExpand && this.allowQueuedExpand !== false){ - this.queuedExpand = true; - this.on('expand', function(){ - delete this.queuedExpand; - this.onResize(this.queuedBodySize.width, this.queuedBodySize.height); - this.doLayout(); - }, this, {single:true}); + this.mbtn.setText(this.monthNames[date.getMonth()] + ' ' + date.getFullYear()); + + if(!this.internalRender){ + var main = this.el.dom.firstChild, + w = main.offsetWidth; + this.el.setWidth(w + this.el.getBorderWidth('lr')); + Ext.fly(main).setWidth(w); + this.internalRender = true; + // opera does not respect the auto grow header center column + // then, after it gets a width opera refuses to recalculate + // without a second pass + if(Ext.isOpera && !this.secondPass){ + main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + 'px'; + this.secondPass = true; + this.update.defer(10, this, [date]); } } - this.fireEvent('bodyresize', this, w, h); } - this.syncShadow(); }, // private - adjustBodyHeight : function(h){ - return h; + beforeDestroy : function() { + if(this.rendered){ + Ext.destroy( + this.keyNav, + this.monthPicker, + this.eventEl, + this.mbtn, + this.nextRepeater, + this.prevRepeater, + this.cells.el, + this.todayBtn + ); + delete this.textNodes; + delete this.cells.elements; + } + } + + /** + * @cfg {String} autoEl @hide + */ +}); + +Ext.reg('datepicker', Ext.DatePicker); +/** + * @class Ext.LoadMask + * A simple utility class for generically masking elements while loading data. If the {@link #store} + * config option is specified, the masking will be automatically synchronized with the store's loading + * process and the mask element will be cached for reuse. For all other elements, this mask will replace the + * element's Updater load indicator and will be destroyed after the initial load. + *

          Example usage:

          + *
          
          +// Basic mask:
          +var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
          +myMask.show();
          +
          + * @constructor + * Create a new LoadMask + * @param {Mixed} el The element or DOM node, or its id + * @param {Object} config The config object + */ +Ext.LoadMask = function(el, config){ + this.el = Ext.get(el); + Ext.apply(this, config); + if(this.store){ + this.store.on({ + scope: this, + beforeload: this.onBeforeLoad, + load: this.onLoad, + exception: this.onLoad + }); + this.removeMask = Ext.value(this.removeMask, false); + }else{ + var um = this.el.getUpdater(); + um.showLoadIndicator = false; // disable the default indicator + um.on({ + scope: this, + beforeupdate: this.onBeforeLoad, + update: this.onLoad, + failure: this.onLoad + }); + this.removeMask = Ext.value(this.removeMask, true); + } +}; + +Ext.LoadMask.prototype = { + /** + * @cfg {Ext.data.Store} store + * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and + * hidden on either load sucess, or load fail. + */ + /** + * @cfg {Boolean} removeMask + * True to create a single-use mask that is automatically destroyed after loading (useful for page loads), + * False to persist the mask element reference for multiple uses (e.g., for paged data widgets). Defaults to false. + */ + /** + * @cfg {String} msg + * The text to display in a centered loading message box (defaults to 'Loading...') + */ + msg : 'Loading...', + /** + * @cfg {String} msgCls + * The CSS class to apply to the loading message element (defaults to "x-mask-loading") + */ + msgCls : 'x-mask-loading', + + /** + * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false) + * @type Boolean + */ + disabled: false, + + /** + * Disables the mask to prevent it from being displayed + */ + disable : function(){ + this.disabled = true; + }, + + /** + * Enables the mask so that it can be displayed + */ + enable : function(){ + this.disabled = false; }, // private - adjustBodyWidth : function(w){ - return w; + onLoad : function(){ + this.el.unmask(this.removeMask); }, // private - onPosition : function(){ - this.syncShadow(); + onBeforeLoad : function(){ + if(!this.disabled){ + this.el.mask(this.msg, this.msgCls); + } }, /** - * Returns the width in pixels of the framing elements of this panel (not including the body width). To - * retrieve the body width see {@link #getInnerWidth}. - * @return {Number} The frame width + * Show this LoadMask over the configured Element. */ - getFrameWidth : function(){ - var w = this.el.getFrameWidth('lr')+this.bwrap.getFrameWidth('lr'); - - if(this.frame){ - var l = this.bwrap.dom.firstChild; - w += (Ext.fly(l).getFrameWidth('l') + Ext.fly(l.firstChild).getFrameWidth('r')); - var mc = this.bwrap.dom.firstChild.firstChild.firstChild; - w += Ext.fly(mc).getFrameWidth('lr'); - } - return w; + show: function(){ + this.onBeforeLoad(); }, /** - * Returns the height in pixels of the framing elements of this panel (including any top and bottom bars and - * header and footer elements, but not including the body height). To retrieve the body height see {@link #getInnerHeight}. - * @return {Number} The frame height + * Hide this LoadMask. */ - getFrameHeight : function(){ - var h = this.el.getFrameWidth('tb')+this.bwrap.getFrameWidth('tb'); - h += (this.tbar ? this.tbar.getHeight() : 0) + - (this.bbar ? this.bbar.getHeight() : 0); + hide: function(){ + this.onLoad(); + }, - if(this.frame){ - var hd = this.el.dom.firstChild; - var ft = this.bwrap.dom.lastChild; - h += (hd.offsetHeight + ft.offsetHeight); - var mc = this.bwrap.dom.firstChild.firstChild.firstChild; - h += Ext.fly(mc).getFrameWidth('tb'); + // private + destroy : function(){ + if(this.store){ + this.store.un('beforeload', this.onBeforeLoad, this); + this.store.un('load', this.onLoad, this); + this.store.un('exception', this.onLoad, this); }else{ - h += (this.header ? this.header.getHeight() : 0) + - (this.footer ? this.footer.getHeight() : 0); + var um = this.el.getUpdater(); + um.un('beforeupdate', this.onBeforeLoad, this); + um.un('update', this.onLoad, this); + um.un('failure', this.onLoad, this); + } + } +};Ext.ns('Ext.slider'); + +/** + * @class Ext.slider.Thumb + * @extends Object + * Represents a single thumb element on a Slider. This would not usually be created manually and would instead + * be created internally by an {@link Ext.slider.MultiSlider Ext.Slider}. + */ +Ext.slider.Thumb = Ext.extend(Object, { + + /** + * @constructor + * @cfg {Ext.slider.MultiSlider} slider The Slider to render to (required) + */ + constructor: function(config) { + /** + * @property slider + * @type Ext.slider.MultiSlider + * The slider this thumb is contained within + */ + Ext.apply(this, config || {}, { + cls: 'x-slider-thumb', + + /** + * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings + */ + constrain: false + }); + + Ext.slider.Thumb.superclass.constructor.call(this, config); + + if (this.slider.vertical) { + Ext.apply(this, Ext.slider.Thumb.Vertical); } - return h; }, /** - * Returns the width in pixels of the body element (not including the width of any framing elements). - * For the frame width see {@link #getFrameWidth}. - * @return {Number} The body width + * Renders the thumb into a slider */ - getInnerWidth : function(){ - return this.getSize().width - this.getFrameWidth(); + render: function() { + this.el = this.slider.innerEl.insertFirst({cls: this.cls}); + + this.initEvents(); }, /** - * Returns the height in pixels of the body element (not including the height of any framing elements). - * For the frame height see {@link #getFrameHeight}. - * @return {Number} The body height + * Enables the thumb if it is currently disabled */ - getInnerHeight : function(){ - return this.getSize().height - this.getFrameHeight(); + enable: function() { + this.disabled = false; + this.el.removeClass(this.slider.disabledClass); }, - // private - syncShadow : function(){ - if(this.floating){ - this.el.sync(true); - } + /** + * Disables the thumb if it is currently enabled + */ + disable: function() { + this.disabled = true; + this.el.addClass(this.slider.disabledClass); }, - // private - getLayoutTarget : function(){ - return this.body; + /** + * Sets up an Ext.dd.DragTracker for this thumb + */ + initEvents: function() { + var el = this.el; + + el.addClassOnOver('x-slider-thumb-over'); + + this.tracker = new Ext.dd.DragTracker({ + onBeforeStart: this.onBeforeDragStart.createDelegate(this), + onStart : this.onDragStart.createDelegate(this), + onDrag : this.onDrag.createDelegate(this), + onEnd : this.onDragEnd.createDelegate(this), + tolerance : 3, + autoStart : 300 + }); + + this.tracker.initEl(el); }, /** - *

          Sets the title text for the panel and optionally the {@link #iconCls icon class}.

          - *

          In order to be able to set the title, a header element must have been created - * for the Panel. This is triggered either by configuring the Panel with a non-blank {@link #title}, - * or configuring it with {@link #header}: true.

          - * @param {String} title The title text to set - * @param {String} iconCls (optional) {@link #iconCls iconCls} A user-defined CSS class that provides the icon image for this panel + * @private + * This is tied into the internal Ext.dd.DragTracker. If the slider is currently disabled, + * this returns false to disable the DragTracker too. + * @return {Boolean} False if the slider is currently disabled */ - setTitle : function(title, iconCls){ - this.title = title; - if(this.header && this.headerAsText){ - this.header.child('span').update(title); - } - if(iconCls){ - this.setIconClass(iconCls); + onBeforeDragStart : function(e) { + if (this.disabled) { + return false; + } else { + this.slider.promoteThumb(this); + return true; } - this.fireEvent('titlechange', this, title); - return this; }, /** - * Get the {@link Ext.Updater} for this panel. Enables you to perform Ajax updates of this panel's body. - * @return {Ext.Updater} The Updater + * @private + * This is tied into the internal Ext.dd.DragTracker's onStart template method. Adds the drag CSS class + * to the thumb and fires the 'dragstart' event */ - getUpdater : function(){ - return this.body.getUpdater(); + onDragStart: function(e){ + this.el.addClass('x-slider-thumb-drag'); + this.dragging = true; + this.dragStartValue = this.value; + + this.slider.fireEvent('dragstart', this.slider, e, this); }, - /** - * Loads this content panel immediately with content returned from an XHR call. - * @param {Object/String/Function} config A config object containing any of the following options: -
          
          -panel.load({
          -    url: "your-url.php",
          -    params: {param1: "foo", param2: "bar"}, // or a URL encoded string
          -    callback: yourFunction,
          -    scope: yourObject, // optional scope for the callback
          -    discardUrl: false,
          -    nocache: false,
          -    text: "Loading...",
          -    timeout: 30,
          -    scripts: false
          -});
          -
          - * The only required property is url. The optional properties nocache, text and scripts - * are shorthand for disableCaching, indicatorText and loadScripts and are used to set their - * associated property on this panel Updater instance. - * @return {Ext.Panel} this + /** + * @private + * This is tied into the internal Ext.dd.DragTracker's onDrag template method. This is called every time + * the DragTracker detects a drag movement. It updates the Slider's value using the position of the drag */ - load : function(){ - var um = this.body.getUpdater(); - um.update.apply(um, arguments); - return this; - }, + onDrag: function(e) { + var slider = this.slider, + index = this.index, + newValue = this.getNewValue(); - // private - beforeDestroy : function(){ - if(this.header){ - this.header.removeAllListeners(); - if(this.headerAsText){ - Ext.Element.uncache(this.header.child('span')); - } - } - Ext.Element.uncache( - this.header, - this.tbar, - this.bbar, - this.footer, - this.body, - this.bwrap - ); - if(this.tools){ - for(var k in this.tools){ - Ext.destroy(this.tools[k]); - } - } - if(this.buttons){ - for(var b in this.buttons){ - Ext.destroy(this.buttons[b]); - } + if (this.constrain) { + var above = slider.thumbs[index + 1], + below = slider.thumbs[index - 1]; + + if (below != undefined && newValue <= below.value) newValue = below.value; + if (above != undefined && newValue >= above.value) newValue = above.value; } - Ext.destroy(this.toolbars); - Ext.Panel.superclass.beforeDestroy.call(this); - }, - // private - createClasses : function(){ - this.headerCls = this.baseCls + '-header'; - this.headerTextCls = this.baseCls + '-header-text'; - this.bwrapCls = this.baseCls + '-bwrap'; - this.tbarCls = this.baseCls + '-tbar'; - this.bodyCls = this.baseCls + '-body'; - this.bbarCls = this.baseCls + '-bbar'; - this.footerCls = this.baseCls + '-footer'; + slider.setValue(index, newValue, false); + slider.fireEvent('drag', slider, e, this); }, - // private - createGhost : function(cls, useShim, appendTo){ - var el = document.createElement('div'); - el.className = 'x-panel-ghost ' + (cls ? cls : ''); - if(this.header){ - el.appendChild(this.el.dom.firstChild.cloneNode(true)); - } - Ext.fly(el.appendChild(document.createElement('ul'))).setHeight(this.bwrap.getHeight()); - el.style.width = this.el.dom.offsetWidth + 'px';; - if(!appendTo){ - this.container.dom.appendChild(el); - }else{ - Ext.getDom(appendTo).appendChild(el); - } - if(useShim !== false && this.el.useShim !== false){ - var layer = new Ext.Layer({shadow:false, useDisplay:true, constrain:false}, el); - layer.show(); - return layer; - }else{ - return new Ext.Element(el); - } + getNewValue: function() { + var slider = this.slider, + pos = slider.innerEl.translatePoints(this.tracker.getXY()); + + return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision); }, - // private - doAutoLoad : function(){ - var u = this.body.getUpdater(); - if(this.renderer){ - u.setRenderer(this.renderer); + /** + * @private + * This is tied to the internal Ext.dd.DragTracker's onEnd template method. Removes the drag CSS class and + * fires the 'changecomplete' event with the new value + */ + onDragEnd: function(e) { + var slider = this.slider, + value = this.value; + + this.el.removeClass('x-slider-thumb-drag'); + + this.dragging = false; + slider.fireEvent('dragend', slider, e); + + if (this.dragStartValue != value) { + slider.fireEvent('changecomplete', slider, value, this); } - u.update(Ext.isObject(this.autoLoad) ? this.autoLoad : {url: this.autoLoad}); }, - + /** - * Retrieve a tool by id. - * @param {String} id - * @return {Object} tool + * @private + * Destroys the thumb */ - getTool : function(id) { - return this.tools[id]; + destroy: function(){ + Ext.destroyMembers(this, 'tracker', 'el'); } +}); /** - * @cfg {String} autoEl @hide - */ + * @class Ext.slider.MultiSlider + * @extends Ext.BoxComponent + * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking and animation. Can be added as an item to any container. Example usage: +
          +new Ext.Slider({
          +    renderTo: Ext.getBody(),
          +    width: 200,
          +    value: 50,
          +    increment: 10,
          +    minValue: 0,
          +    maxValue: 100
           });
          -Ext.reg('panel', Ext.Panel);
          -/**
          - * @class Ext.Editor
          - * @extends Ext.Component
          - * A base editor field that handles displaying/hiding on demand and has some built-in sizing and event handling logic.
          - * @constructor
          - * Create a new Editor
          - * @param {Object} config The config object
          - * @xtype editor
          - */
          -Ext.Editor = function(field, config){
          -    if(field.field){
          -        this.field = Ext.create(field.field, 'textfield');
          -        config = Ext.apply({}, field); // copy so we don't disturb original config
          -        delete config.field;
          -    }else{
          -        this.field = field;
          -    }
          -    Ext.Editor.superclass.constructor.call(this, config);
          -};
          +
          + * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one: +
          +new Ext.Slider({
          +    renderTo: Ext.getBody(),
          +    width: 200,
          +    values: [25, 50, 75],
          +    minValue: 0,
          +    maxValue: 100,
           
          -Ext.extend(Ext.Editor, Ext.Component, {
          -    /**
          -    * @cfg {Ext.form.Field} field
          -    * The Field object (or descendant) or config object for field
          -    */
          +    //this defaults to true, setting to false allows the thumbs to pass each other
          +    {@link #constrainThumbs}: false
          +});
          +
          + */ +Ext.slider.MultiSlider = Ext.extend(Ext.BoxComponent, { /** - * @cfg {Boolean} allowBlur - * True to {@link #completeEdit complete the editing process} if in edit mode when the - * field is blurred. Defaults to false. + * @cfg {Number} value The value to initialize the slider with. Defaults to minValue. */ /** - * @cfg {Boolean/String} autoSize - * True for the editor to automatically adopt the size of the element being edited, "width" to adopt the width only, - * or "height" to adopt the height only (defaults to false) + * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false. */ + vertical: false, /** - * @cfg {Boolean} revertInvalid - * True to automatically revert the field value and cancel the edit when the user completes an edit and the field - * validation fails (defaults to true) + * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0. */ + minValue: 0, /** - * @cfg {Boolean} ignoreNoChange - * True to skip the edit completion process (no save, no events fired) if the user completes an edit and - * the value has not changed (defaults to false). Applies only to string values - edits for other data types - * will never be ignored. + * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100. */ + maxValue: 100, /** - * @cfg {Boolean} hideEl - * False to keep the bound element visible while the editor is displayed (defaults to true) + * @cfg {Number/Boolean} decimalPrecision. + *

          The number of decimal places to which to round the Slider's value. Defaults to 0.

          + *

          To disable rounding, configure as false.

          */ + decimalPrecision: 0, /** - * @cfg {Mixed} value - * The data value of the underlying field (defaults to "") + * @cfg {Number} keyIncrement How many units to change the Slider when adjusting with keyboard navigation. Defaults to 1. If the increment config is larger, it will be used instead. */ - value : "", + keyIncrement: 1, /** - * @cfg {String} alignment - * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "c-c?"). + * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'. */ - alignment: "c-c?", + increment: 0, + /** - * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop" - * for bottom-right shadow (defaults to "frame") + * @private + * @property clickRange + * @type Array + * Determines whether or not a click to the slider component is considered to be a user request to change the value. Specified as an array of [top, bottom], + * the click event's 'top' property is compared to these numbers and the click only considered a change request if it falls within them. e.g. if the 'top' + * value of the click event is 4 or 16, the click is not considered a change request as it falls outside of the [5, 15] range */ - shadow : "frame", + clickRange: [5,15], + /** - * @cfg {Boolean} constrain True to constrain the editor to the viewport + * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true */ - constrain : false, + clickToChange : true, /** - * @cfg {Boolean} swallowKeys Handle the keydown/keypress events so they don't propagate (defaults to true) + * @cfg {Boolean} animate Turn on or off animation. Defaults to true */ - swallowKeys : true, + animate: true, + /** - * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed (defaults to false) + * True while the thumb is in a drag operation + * @type Boolean */ - completeOnEnter : false, + dragging: false, + /** - * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed (defaults to false) + * @cfg {Boolean} constrainThumbs True to disallow thumbs from overlapping one another. Defaults to true */ - cancelOnEsc : false, + constrainThumbs: true, + /** - * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false) + * @private + * @property topThumbZIndex + * @type Number + * The number used internally to set the z index of the top thumb (see promoteThumb for details) */ - updateEl : false, + topThumbZIndex: 10000, + // private override initComponent : function(){ - Ext.Editor.superclass.initComponent.call(this); + if(!Ext.isDefined(this.value)){ + this.value = this.minValue; + } + + /** + * @property thumbs + * @type Array + * Array containing references to each thumb + */ + this.thumbs = []; + + Ext.slider.MultiSlider.superclass.initComponent.call(this); + + this.keyIncrement = Math.max(this.increment, this.keyIncrement); this.addEvents( /** - * @event beforestartedit - * Fires when editing is initiated, but before the value changes. Editing can be canceled by returning - * false from the handler of this event. - * @param {Editor} this - * @param {Ext.Element} boundEl The underlying element bound to this editor - * @param {Mixed} value The field value being set + * @event beforechange + * Fires before the slider value is changed. By returning false from an event handler, + * you can cancel the event and prevent the slider from changing. + * @param {Ext.Slider} slider The slider + * @param {Number} newValue The new value which the slider is being changed to. + * @param {Number} oldValue The old value which the slider was previously. */ - "beforestartedit", + 'beforechange', + /** - * @event startedit - * Fires when this editor is displayed - * @param {Ext.Element} boundEl The underlying element bound to this editor - * @param {Mixed} value The starting field value + * @event change + * Fires when the slider value is changed. + * @param {Ext.Slider} slider The slider + * @param {Number} newValue The new value which the slider has been changed to. + * @param {Ext.slider.Thumb} thumb The thumb that was changed */ - "startedit", + 'change', + /** - * @event beforecomplete - * Fires after a change has been made to the field, but before the change is reflected in the underlying - * field. Saving the change to the field can be canceled by returning false from the handler of this event. - * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this - * event will not fire since no edit actually occurred. - * @param {Editor} this - * @param {Mixed} value The current field value - * @param {Mixed} startValue The original field value + * @event changecomplete + * Fires when the slider value is changed by the user and any drag operations have completed. + * @param {Ext.Slider} slider The slider + * @param {Number} newValue The new value which the slider has been changed to. + * @param {Ext.slider.Thumb} thumb The thumb that was changed */ - "beforecomplete", + 'changecomplete', + /** - * @event complete - * Fires after editing is complete and any changed value has been written to the underlying field. - * @param {Editor} this - * @param {Mixed} value The current field value - * @param {Mixed} startValue The original field value + * @event dragstart + * Fires after a drag operation has started. + * @param {Ext.Slider} slider The slider + * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker */ - "complete", + 'dragstart', + /** - * @event canceledit - * Fires after editing has been canceled and the editor's value has been reset. - * @param {Editor} this - * @param {Mixed} value The user-entered field value that was discarded - * @param {Mixed} startValue The original field value that was set back into the editor after cancel + * @event drag + * Fires continuously during the drag operation while the mouse is moving. + * @param {Ext.Slider} slider The slider + * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker */ - "canceledit", + 'drag', + /** - * @event specialkey - * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. You can check - * {@link Ext.EventObject#getKey} to determine which key was pressed. - * @param {Ext.form.Field} this - * @param {Ext.EventObject} e The event object + * @event dragend + * Fires after the drag operation has completed. + * @param {Ext.Slider} slider The slider + * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker */ - "specialkey" + 'dragend' ); + + /** + * @property values + * @type Array + * Array of values to initalize the thumbs with + */ + if (this.values == undefined || Ext.isEmpty(this.values)) this.values = [0]; + + var values = this.values; + + for (var i=0; i < values.length; i++) { + this.addThumb(values[i]); + } + + if(this.vertical){ + Ext.apply(this, Ext.slider.Vertical); + } + }, + + /** + * Creates a new thumb and adds it to the slider + * @param {Number} value The initial value to set on the thumb. Defaults to 0 + */ + addThumb: function(value) { + var thumb = new Ext.slider.Thumb({ + value : value, + slider : this, + index : this.thumbs.length, + constrain: this.constrainThumbs + }); + this.thumbs.push(thumb); + + //render the thumb now if needed + if (this.rendered) thumb.render(); + }, + + /** + * @private + * Moves the given thumb above all other by increasing its z-index. This is called when as drag + * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is + * required when the thumbs are stacked on top of each other at one of the ends of the slider's + * range, which can result in the user not being able to move any of them. + * @param {Ext.slider.Thumb} topThumb The thumb to move to the top + */ + promoteThumb: function(topThumb) { + var thumbs = this.thumbs, + zIndex, thumb; + + for (var i = 0, j = thumbs.length; i < j; i++) { + thumb = thumbs[i]; + + if (thumb == topThumb) { + zIndex = this.topThumbZIndex; + } else { + zIndex = ''; + } + + thumb.el.setStyle('zIndex', zIndex); + } }, - // private - onRender : function(ct, position){ - this.el = new Ext.Layer({ - shadow: this.shadow, - cls: "x-editor", - parentEl : ct, - shim : this.shim, - shadowOffset: this.shadowOffset || 4, - id: this.id, - constrain: this.constrain + // private override + onRender : function() { + this.autoEl = { + cls: 'x-slider ' + (this.vertical ? 'x-slider-vert' : 'x-slider-horz'), + cn : { + cls: 'x-slider-end', + cn : { + cls:'x-slider-inner', + cn : [{tag:'a', cls:'x-slider-focus', href:"#", tabIndex: '-1', hidefocus:'on'}] + } + } + }; + + Ext.slider.MultiSlider.superclass.onRender.apply(this, arguments); + + this.endEl = this.el.first(); + this.innerEl = this.endEl.first(); + this.focusEl = this.innerEl.child('.x-slider-focus'); + + //render each thumb + for (var i=0; i < this.thumbs.length; i++) { + this.thumbs[i].render(); + } + + //calculate the size of half a thumb + var thumb = this.innerEl.child('.x-slider-thumb'); + this.halfThumb = (this.vertical ? thumb.getHeight() : thumb.getWidth()) / 2; + + this.initEvents(); + }, + + /** + * @private + * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element. + * Creates a new DragTracker which is used to control what happens when the user drags the thumb around. + */ + initEvents : function(){ + this.mon(this.el, { + scope : this, + mousedown: this.onMouseDown, + keydown : this.onKeyDown }); - if(this.zIndex){ - this.el.setZIndex(this.zIndex); + + this.focusEl.swallowEvent("click", true); + }, + + /** + * @private + * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb', + * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb + * @param {Ext.EventObject} e The click event + */ + onMouseDown : function(e){ + if(this.disabled){ + return; } - this.el.setStyle("overflow", Ext.isGecko ? "auto" : "hidden"); - if(this.field.msgTarget != 'title'){ - this.field.msgTarget = 'qtip'; + + //see if the click was on any of the thumbs + var thumbClicked = false; + for (var i=0; i < this.thumbs.length; i++) { + thumbClicked = thumbClicked || e.target == this.thumbs[i].el.dom; } - this.field.inEditor = true; - this.field.render(this.el); - if(Ext.isGecko){ - this.field.el.dom.setAttribute('autocomplete', 'off'); + + if (this.clickToChange && !thumbClicked) { + var local = this.innerEl.translatePoints(e.getXY()); + this.onClickChange(local); } - this.mon(this.field, "specialkey", this.onSpecialKey, this); - if(this.swallowKeys){ - this.field.el.swallowEvent(['keydown','keypress']); + this.focus(); + }, + + /** + * @private + * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Vertical. + * Only changes the value if the click was within this.clickRange. + * @param {Object} local Object containing top and left values for the click event. + */ + onClickChange : function(local) { + if (local.top > this.clickRange[0] && local.top < this.clickRange[1]) { + //find the nearest thumb to the click event + var thumb = this.getNearest(local, 'left'), + index = thumb.index; + + this.setValue(index, Ext.util.Format.round(this.reverseValue(local.left), this.decimalPrecision), undefined, true); } - this.field.show(); - this.mon(this.field, "blur", this.onBlur, this); - if(this.field.grow){ - this.mon(this.field, "autosize", this.el.sync, this.el, {delay:1}); + }, + + /** + * @private + * Returns the nearest thumb to a click event, along with its distance + * @param {Object} local Object containing top and left values from a click event + * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones + * @return {Object} The closest thumb object and its distance from the click event + */ + getNearest: function(local, prop) { + var localValue = prop == 'top' ? this.innerEl.getHeight() - local[prop] : local[prop], + clickValue = this.reverseValue(localValue), + nearestDistance = (this.maxValue - this.minValue) + 5, //add a small fudge for the end of the slider + index = 0, + nearest = null; + + for (var i=0; i < this.thumbs.length; i++) { + var thumb = this.thumbs[i], + value = thumb.value, + dist = Math.abs(value - clickValue); + + if (Math.abs(dist <= nearestDistance)) { + nearest = thumb; + index = i; + nearestDistance = dist; + } } + return nearest; }, - // private - onSpecialKey : function(field, e){ - var key = e.getKey(); - if(this.completeOnEnter && key == e.ENTER){ - e.stopEvent(); - this.completeEdit(); - }else if(this.cancelOnEsc && key == e.ESC){ - this.cancelEdit(); - }else{ - this.fireEvent('specialkey', field, e); + /** + * @private + * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right + * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction + * @param {Ext.EventObject} e The Event object + */ + onKeyDown : function(e){ + /* + * The behaviour for keyboard handling with multiple thumbs is currently undefined. + * There's no real sane default for it, so leave it like this until we come up + * with a better way of doing it. + */ + if(this.disabled || this.thumbs.length !== 1){ + e.preventDefault(); + return; } - if(this.field.triggerBlur && (key == e.ENTER || key == e.ESC || key == e.TAB)){ - this.field.triggerBlur(); + var k = e.getKey(), + val; + switch(k){ + case e.UP: + case e.RIGHT: + e.stopEvent(); + val = e.ctrlKey ? this.maxValue : this.getValue(0) + this.keyIncrement; + this.setValue(0, val, undefined, true); + break; + case e.DOWN: + case e.LEFT: + e.stopEvent(); + val = e.ctrlKey ? this.minValue : this.getValue(0) - this.keyIncrement; + this.setValue(0, val, undefined, true); + break; + default: + e.preventDefault(); } }, /** - * Starts the editing process and shows the editor. - * @param {Mixed} el The element to edit - * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults - * to the innerHTML of el. + * @private + * If using snapping, this takes a desired new value and returns the closest snapped + * value to it + * @param {Number} value The unsnapped value + * @return {Number} The value of the nearest snap target */ - startEdit : function(el, value){ - if(this.editing){ - this.completeEdit(); - } - this.boundEl = Ext.get(el); - var v = value !== undefined ? value : this.boundEl.dom.innerHTML; - if(!this.rendered){ - this.render(this.parentEl || document.body); + doSnap : function(value){ + if (!(this.increment && value)) { + return value; } - if(this.fireEvent("beforestartedit", this, this.boundEl, v) === false){ - return; + var newValue = value, + inc = this.increment, + m = value % inc; + if (m != 0) { + newValue -= m; + if (m * 2 >= inc) { + newValue += inc; + } else if (m * 2 < -inc) { + newValue -= inc; + } } - this.startValue = v; - this.field.setValue(v); - this.doAutoSize(); - this.el.alignTo(this.boundEl, this.alignment); - this.editing = true; - this.show(); + return newValue.constrain(this.minValue, this.maxValue); }, // private - doAutoSize : function(){ - if(this.autoSize){ - var sz = this.boundEl.getSize(); - switch(this.autoSize){ - case "width": - this.setSize(sz.width, ""); - break; - case "height": - this.setSize("", sz.height); - break; - default: - this.setSize(sz.width, sz.height); + afterRender : function(){ + Ext.slider.MultiSlider.superclass.afterRender.apply(this, arguments); + + for (var i=0; i < this.thumbs.length; i++) { + var thumb = this.thumbs[i]; + + if (thumb.value !== undefined) { + var v = this.normalizeValue(thumb.value); + + if (v !== thumb.value) { + // delete this.value; + this.setValue(i, v, false); + } else { + this.moveThumb(i, this.translateValue(v), false); + } } - } + }; }, /** - * Sets the height and width of this editor. - * @param {Number} width The new width - * @param {Number} height The new height + * @private + * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100, + * the ratio is 2 + * @return {Number} The ratio of pixels to mapped values */ - setSize : function(w, h){ - delete this.field.lastSize; - this.field.setSize(w, h); - if(this.el){ - if(Ext.isGecko2 || Ext.isOpera){ - // prevent layer scrollbars - this.el.setSize(w, h); - } - this.el.sync(); - } + getRatio : function(){ + var w = this.innerEl.getWidth(), + v = this.maxValue - this.minValue; + return v == 0 ? w : (w/v); }, /** - * Realigns the editor to the bound field based on the current alignment config value. + * @private + * Returns a snapped, constrained value when given a desired value + * @param {Number} value Raw number value + * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values */ - realign : function(){ - this.el.alignTo(this.boundEl, this.alignment); + normalizeValue : function(v){ + v = this.doSnap(v); + v = Ext.util.Format.round(v, this.decimalPrecision); + v = v.constrain(this.minValue, this.maxValue); + return v; }, /** - * Ends the editing process, persists the changed value to the underlying field, and hides the editor. - * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after edit (defaults to false) + * Sets the minimum value for the slider instance. If the current value is less than the + * minimum value, the current value will be changed. + * @param {Number} val The new minimum value */ - completeEdit : function(remainVisible){ - if(!this.editing){ - return; - } - var v = this.getValue(); - if(!this.field.isValid()){ - if(this.revertInvalid !== false){ - this.cancelEdit(remainVisible); - } - return; + setMinValue : function(val){ + this.minValue = val; + var i = 0, + thumbs = this.thumbs, + len = thumbs.length, + t; + + for(; i < len; ++i){ + t = thumbs[i]; + t.value = t.value < val ? val : t.value; } - if(String(v) === String(this.startValue) && this.ignoreNoChange){ - this.hideEdit(remainVisible); - return; + this.syncThumb(); + }, + + /** + * Sets the maximum value for the slider instance. If the current value is more than the + * maximum value, the current value will be changed. + * @param {Number} val The new maximum value + */ + setMaxValue : function(val){ + this.maxValue = val; + var i = 0, + thumbs = this.thumbs, + len = thumbs.length, + t; + + for(; i < len; ++i){ + t = thumbs[i]; + t.value = t.value > val ? val : t.value; } - if(this.fireEvent("beforecomplete", this, v, this.startValue) !== false){ - v = this.getValue(); - if(this.updateEl && this.boundEl){ - this.boundEl.update(v); + this.syncThumb(); + }, + + /** + * Programmatically sets the value of the Slider. Ensures that the value is constrained within + * the minValue and maxValue. + * @param {Number} index Index of the thumb to move + * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue) + * @param {Boolean} animate Turn on or off animation, defaults to true + */ + setValue : function(index, v, animate, changeComplete) { + var thumb = this.thumbs[index], + el = thumb.el; + + v = this.normalizeValue(v); + + if (v !== thumb.value && this.fireEvent('beforechange', this, v, thumb.value, thumb) !== false) { + thumb.value = v; + if(this.rendered){ + this.moveThumb(index, this.translateValue(v), animate !== false); + this.fireEvent('change', this, v, thumb); + if(changeComplete){ + this.fireEvent('changecomplete', this, v, thumb); + } } - this.hideEdit(remainVisible); - this.fireEvent("complete", this, v, this.startValue); } }, - // private - onShow : function(){ - this.el.show(); - if(this.hideEl !== false){ - this.boundEl.hide(); - } - this.field.show(); - if(Ext.isIE && !this.fixIEFocus){ // IE has problems with focusing the first time - this.fixIEFocus = true; - this.deferredFocus.defer(50, this); - }else{ - this.field.focus(); - } - this.fireEvent("startedit", this.boundEl, this.startValue); + /** + * @private + */ + translateValue : function(v) { + var ratio = this.getRatio(); + return (v * ratio) - (this.minValue * ratio) - this.halfThumb; }, - deferredFocus : function(){ - if(this.editing){ - this.field.focus(); - } + /** + * @private + * Given a pixel location along the slider, returns the mapped slider value for that pixel. + * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50) + * returns 200 + * @param {Number} pos The position along the slider to return a mapped value for + * @return {Number} The mapped value for the given position + */ + reverseValue : function(pos){ + var ratio = this.getRatio(); + return (pos + (this.minValue * ratio)) / ratio; }, /** - * Cancels the editing process and hides the editor without persisting any changes. The field value will be - * reverted to the original starting value. - * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after - * cancel (defaults to false) + * @private + * @param {Number} index Index of the thumb to move */ - cancelEdit : function(remainVisible){ - if(this.editing){ - var v = this.getValue(); - this.setValue(this.startValue); - this.hideEdit(remainVisible); - this.fireEvent("canceledit", this, v, this.startValue); + moveThumb: function(index, v, animate){ + var thumb = this.thumbs[index].el; + + if(!animate || this.animate === false){ + thumb.setLeft(v); + }else{ + thumb.shift({left: v, stopFx: true, duration:.35}); } }, - + // private - hideEdit: function(remainVisible){ - if(remainVisible !== true){ - this.editing = false; - this.hide(); - } + focus : function(){ + this.focusEl.focus(10); }, // private - onBlur : function(){ - if(this.allowBlur !== true && this.editing){ - this.completeEdit(); + onResize : function(w, h){ + var thumbs = this.thumbs, + len = thumbs.length, + i = 0; + + /* + * If we happen to be animating during a resize, the position of the thumb will likely be off + * when the animation stops. As such, just stop any animations before syncing the thumbs. + */ + for(; i < len; ++i){ + thumbs[i].el.stopFx(); + } + // check to see if we're using an auto width + if(Ext.isNumber(w)){ + this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r'))); } + this.syncThumb(); + Ext.slider.MultiSlider.superclass.onResize.apply(this, arguments); }, - // private - onHide : function(){ - if(this.editing){ - this.completeEdit(); - return; + //private + onDisable: function(){ + Ext.slider.MultiSlider.superclass.onDisable.call(this); + + for (var i=0; i < this.thumbs.length; i++) { + var thumb = this.thumbs[i], + el = thumb.el; + + thumb.disable(); + + if(Ext.isIE){ + //IE breaks when using overflow visible and opacity other than 1. + //Create a place holder for the thumb and display it. + var xy = el.getXY(); + el.hide(); + + this.innerEl.addClass(this.disabledClass).dom.disabled = true; + + if (!this.thumbHolder) { + this.thumbHolder = this.endEl.createChild({cls: 'x-slider-thumb ' + this.disabledClass}); + } + + this.thumbHolder.show().setXY(xy); + } } - this.field.blur(); - if(this.field.collapse){ - this.field.collapse(); + }, + + //private + onEnable: function(){ + Ext.slider.MultiSlider.superclass.onEnable.call(this); + + for (var i=0; i < this.thumbs.length; i++) { + var thumb = this.thumbs[i], + el = thumb.el; + + thumb.enable(); + + if (Ext.isIE) { + this.innerEl.removeClass(this.disabledClass).dom.disabled = false; + + if (this.thumbHolder) this.thumbHolder.hide(); + + el.show(); + this.syncThumb(); + } } - this.el.hide(); - if(this.hideEl !== false){ - this.boundEl.show(); + }, + + /** + * Synchronizes the thumb position to the proper proportion of the total component width based + * on the current slider {@link #value}. This will be called automatically when the Slider + * is resized by a layout, but if it is rendered auto width, this method can be called from + * another resize handler to sync the Slider if necessary. + */ + syncThumb : function() { + if (this.rendered) { + for (var i=0; i < this.thumbs.length; i++) { + this.moveThumb(i, this.translateValue(this.thumbs[i].value)); + } } }, /** - * Sets the data value of the editor - * @param {Mixed} value Any valid value supported by the underlying field + * Returns the current value of the slider + * @param {Number} index The index of the thumb to return a value for + * @return {Number} The current value of the slider */ - setValue : function(v){ - this.field.setValue(v); + getValue : function(index) { + return this.thumbs[index].value; + }, + + /** + * Returns an array of values - one for the location of each thumb + * @return {Array} The set of thumb values + */ + getValues: function() { + var values = []; + + for (var i=0; i < this.thumbs.length; i++) { + values.push(this.thumbs[i].value); + } + + return values; + }, + + // private + beforeDestroy : function(){ + var thumbs = this.thumbs; + for(var i = 0, len = thumbs.length; i < len; ++i){ + thumbs[i].destroy(); + thumbs[i] = null; + } + Ext.destroyMembers(this, 'endEl', 'innerEl', 'focusEl', 'thumbHolder'); + Ext.slider.MultiSlider.superclass.beforeDestroy.call(this); + } +}); + +Ext.reg('multislider', Ext.slider.MultiSlider); + +/** + * @class Ext.slider.SingleSlider + * @extends Ext.slider.MultiSlider + * Slider which supports vertical or horizontal orientation, keyboard adjustments, + * configurable snapping, axis clicking and animation. Can be added as an item to + * any container. Example usage: +
          
          +new Ext.slider.SingleSlider({
          +    renderTo: Ext.getBody(),
          +    width: 200,
          +    value: 50,
          +    increment: 10,
          +    minValue: 0,
          +    maxValue: 100
          +});
          +
          + * The class Ext.slider.SingleSlider is aliased to Ext.Slider for backwards compatibility. + */ +Ext.slider.SingleSlider = Ext.extend(Ext.slider.MultiSlider, { + constructor: function(config) { + config = config || {}; + + Ext.applyIf(config, { + values: [config.value || 0] + }); + + Ext.slider.SingleSlider.superclass.constructor.call(this, config); + }, + + /** + * Returns the current value of the slider + * @return {Number} The current value of the slider + */ + getValue: function() { + //just returns the value of the first thumb, which should be the only one in a single slider + return Ext.slider.SingleSlider.superclass.getValue.call(this, 0); }, /** - * Gets the data value of the editor - * @return {Mixed} The data value + * Programmatically sets the value of the Slider. Ensures that the value is constrained within + * the minValue and maxValue. + * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue) + * @param {Boolean} animate Turn on or off animation, defaults to true */ - getValue : function(){ - return this.field.getValue(); + setValue: function(value, animate) { + var args = Ext.toArray(arguments), + len = args.length; + + //this is to maintain backwards compatiblity for sliders with only one thunb. Usually you must pass the thumb + //index to setValue, but if we only have one thumb we inject the index here first if given the multi-slider + //signature without the required index. The index will always be 0 for a single slider + if (len == 1 || (len <= 3 && typeof arguments[1] != 'number')) { + args.unshift(0); + } + + return Ext.slider.SingleSlider.superclass.setValue.apply(this, args); }, - beforeDestroy : function(){ - Ext.destroy(this.field); - this.field = null; + /** + * Synchronizes the thumb position to the proper proportion of the total component width based + * on the current slider {@link #value}. This will be called automatically when the Slider + * is resized by a layout, but if it is rendered auto width, this method can be called from + * another resize handler to sync the Slider if necessary. + */ + syncThumb : function() { + return Ext.slider.SingleSlider.superclass.syncThumb.apply(this, [0].concat(arguments)); + }, + + // private + getNearest : function(){ + // Since there's only 1 thumb, it's always the nearest + return this.thumbs[0]; } }); -Ext.reg('editor', Ext.Editor);/** - * @class Ext.ColorPalette - * @extends Ext.Component - * Simple color palette class for choosing colors. The palette can be rendered to any container.
          - * Here's an example of typical usage: - *
          
          -var cp = new Ext.ColorPalette({value:'993300'});  // initial selected color
          -cp.render('my-div');
           
          -cp.on('select', function(palette, selColor){
          -    // do something with selColor
          -});
          -
          - * @constructor - * Create a new ColorPalette - * @param {Object} config The config object - * @xtype colorpalette - */ -Ext.ColorPalette = function(config){ - Ext.ColorPalette.superclass.constructor.call(this, config); - this.addEvents( - /** - * @event select - * Fires when a color is selected - * @param {ColorPalette} this - * @param {String} color The 6-digit color hex code (without the # symbol) - */ - 'select' - ); +//backwards compatibility +Ext.Slider = Ext.slider.SingleSlider; + +Ext.reg('slider', Ext.slider.SingleSlider); + +// private class to support vertical sliders +Ext.slider.Vertical = { + onResize : function(w, h){ + this.innerEl.setHeight(h - (this.el.getPadding('t') + this.endEl.getPadding('b'))); + this.syncThumb(); + }, + + getRatio : function(){ + var h = this.innerEl.getHeight(), + v = this.maxValue - this.minValue; + return h/v; + }, + + moveThumb: function(index, v, animate) { + var thumb = this.thumbs[index], + el = thumb.el; - if(this.handler){ - this.on("select", this.handler, this.scope, true); + if (!animate || this.animate === false) { + el.setBottom(v); + } else { + el.shift({bottom: v, stopFx: true, duration:.35}); + } + }, + + onClickChange : function(local) { + if (local.left > this.clickRange[0] && local.left < this.clickRange[1]) { + var thumb = this.getNearest(local, 'top'), + index = thumb.index, + value = this.minValue + this.reverseValue(this.innerEl.getHeight() - local.top); + + this.setValue(index, Ext.util.Format.round(value, this.decimalPrecision), undefined, true); + } } }; -Ext.extend(Ext.ColorPalette, Ext.Component, { - /** - * @cfg {String} tpl An existing XTemplate instance to be used in place of the default template for rendering the component. - */ - /** - * @cfg {String} itemCls - * The CSS class to apply to the containing element (defaults to "x-color-palette") - */ - itemCls : "x-color-palette", - /** - * @cfg {String} value - * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol). Note that - * the hex codes are case-sensitive. - */ - value : null, - clickEvent:'click', - // private - ctype: "Ext.ColorPalette", - /** - * @cfg {Boolean} allowReselect If set to true then reselecting a color that is already selected fires the {@link #select} event - */ - allowReselect : false, +//private class to support vertical dragging of thumbs within a slider +Ext.slider.Thumb.Vertical = { + getNewValue: function() { + var slider = this.slider, + innerEl = slider.innerEl, + pos = innerEl.translatePoints(this.tracker.getXY()), + bottom = innerEl.getHeight() - pos.top; + return slider.minValue + Ext.util.Format.round(bottom / slider.getRatio(), slider.decimalPrecision); + } +}; +/** + * @class Ext.ProgressBar + * @extends Ext.BoxComponent + *

          An updateable progress bar component. The progress bar supports two different modes: manual and automatic.

          + *

          In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the + * progress bar as needed from your own code. This method is most appropriate when you want to show progress + * throughout an operation that has predictable points of interest at which you can update the control.

          + *

          In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it + * once the operation is complete. You can optionally have the progress bar wait for a specific amount of time + * and then clear itself. Automatic mode is most appropriate for timed operations or asynchronous operations in + * which you have no need for indicating intermediate progress.

          + * @cfg {Float} value A floating point value between 0 and 1 (e.g., .5, defaults to 0) + * @cfg {String} text The progress bar text (defaults to '') + * @cfg {Mixed} textEl The element to render the progress text to (defaults to the progress + * bar's internal text element) + * @cfg {String} id The progress bar element's id (defaults to an auto-generated id) + * @xtype progress + */ +Ext.ProgressBar = Ext.extend(Ext.BoxComponent, { + /** + * @cfg {String} baseCls + * The base CSS class to apply to the progress bar's wrapper element (defaults to 'x-progress') + */ + baseCls : 'x-progress', + /** - *

          An array of 6-digit color hex code strings (without the # symbol). This array can contain any number - * of colors, and each hex code should be unique. The width of the palette is controlled via CSS by adjusting - * the width property of the 'x-color-palette' class (or assigning a custom class), so you can balance the number - * of colors with the width setting until the box is symmetrical.

          - *

          You can override individual colors if needed:

          - *
          
          -var cp = new Ext.ColorPalette();
          -cp.colors[0] = "FF0000";  // change the first box to red
          -
          + * @cfg {Boolean} animate + * True to animate the progress bar during transitions (defaults to false) + */ + animate : false, -Or you can provide a custom array of your own for complete control: -
          
          -var cp = new Ext.ColorPalette();
          -cp.colors = ["000000", "993300", "333300"];
          -
          - * @type Array - */ - colors : [ - "000000", "993300", "333300", "003300", "003366", "000080", "333399", "333333", - "800000", "FF6600", "808000", "008000", "008080", "0000FF", "666699", "808080", - "FF0000", "FF9900", "99CC00", "339966", "33CCCC", "3366FF", "800080", "969696", - "FF00FF", "FFCC00", "FFFF00", "00FF00", "00FFFF", "00CCFF", "993366", "C0C0C0", - "FF99CC", "FFCC99", "FFFF99", "CCFFCC", "CCFFFF", "99CCFF", "CC99FF", "FFFFFF" - ], + // private + waitTimer : null, // private - onRender : function(container, position){ - var t = this.tpl || new Ext.XTemplate( - ' ' + initComponent : function(){ + Ext.ProgressBar.superclass.initComponent.call(this); + this.addEvents( + /** + * @event update + * Fires after each update interval + * @param {Ext.ProgressBar} this + * @param {Number} The current progress value + * @param {String} The current progress text + */ + "update" ); - var el = document.createElement("div"); - el.id = this.getId(); - el.className = this.itemCls; - t.overwrite(el, this.colors); - container.dom.insertBefore(el, position); - this.el = Ext.get(el); - this.mon(this.el, this.clickEvent, this.handleClick, this, {delegate: 'a'}); - if(this.clickEvent != 'click'){ - this.mon(this.el, 'click', Ext.emptyFn, this, {delegate: 'a', preventDefault: true}); - } }, // private - afterRender : function(){ - Ext.ColorPalette.superclass.afterRender.call(this); - if(this.value){ - var s = this.value; - this.value = null; - this.select(s); + onRender : function(ct, position){ + var tpl = new Ext.Template( + '
          ', + '
          ', + '
          ', + '
          ', + '
           
          ', + '
          ', + '
          ', + '
          ', + '
           
          ', + '
          ', + '
          ', + '
          ' + ); + + this.el = position ? tpl.insertBefore(position, {cls: this.baseCls}, true) + : tpl.append(ct, {cls: this.baseCls}, true); + + if(this.id){ + this.el.dom.id = this.id; } - }, + var inner = this.el.dom.firstChild; + this.progressBar = Ext.get(inner.firstChild); + if(this.textEl){ + //use an external text el + this.textEl = Ext.get(this.textEl); + delete this.textTopEl; + }else{ + //setup our internal layered text els + this.textTopEl = Ext.get(this.progressBar.dom.firstChild); + var textBackEl = Ext.get(inner.childNodes[1]); + this.textTopEl.setStyle("z-index", 99).addClass('x-hidden'); + this.textEl = new Ext.CompositeElement([this.textTopEl.dom.firstChild, textBackEl.dom.firstChild]); + this.textEl.setWidth(inner.offsetWidth); + } + this.progressBar.setHeight(inner.offsetHeight); + }, + // private - handleClick : function(e, t){ - e.preventDefault(); - if(!this.disabled){ - var c = t.className.match(/(?:^|\s)color-(.{6})(?:\s|$)/)[1]; - this.select(c.toUpperCase()); + afterRender : function(){ + Ext.ProgressBar.superclass.afterRender.call(this); + if(this.value){ + this.updateProgress(this.value, this.text); + }else{ + this.updateText(this.text); } }, /** - * Selects the specified color in the palette (fires the {@link #select} event) - * @param {String} color A valid 6-digit color hex code (# will be stripped if included) + * Updates the progress bar value, and optionally its text. If the text argument is not specified, + * any existing text value will be unchanged. To blank out existing text, pass ''. Note that even + * if the progress bar value exceeds 1, it will never automatically reset -- you are responsible for + * determining when the progress is complete and calling {@link #reset} to clear and/or hide the control. + * @param {Float} value (optional) A floating point value between 0 and 1 (e.g., .5, defaults to 0) + * @param {String} text (optional) The string to display in the progress text element (defaults to '') + * @param {Boolean} animate (optional) Whether to animate the transition of the progress bar. If this value is + * not specified, the default for the class is used (default to false) + * @return {Ext.ProgressBar} this */ - select : function(color){ - color = color.replace("#", ""); - if(color != this.value || this.allowReselect){ - var el = this.el; - if(this.value){ - el.child("a.color-"+this.value).removeClass("x-color-palette-sel"); + updateProgress : function(value, text, animate){ + this.value = value || 0; + if(text){ + this.updateText(text); + } + if(this.rendered && !this.isDestroyed){ + var w = Math.floor(value*this.el.dom.firstChild.offsetWidth); + this.progressBar.setWidth(w, animate === true || (animate !== false && this.animate)); + if(this.textTopEl){ + //textTopEl should be the same width as the bar so overflow will clip as the bar moves + this.textTopEl.removeClass('x-hidden').setWidth(w); } - el.child("a.color-"+color).addClass("x-color-palette-sel"); - this.value = color; - this.fireEvent("select", this, color); } - } + this.fireEvent('update', this, value, text); + return this; + }, /** - * @cfg {String} autoEl @hide - */ + * Initiates an auto-updating progress bar. A duration can be specified, in which case the progress + * bar will automatically reset after a fixed amount of time and optionally call a callback function + * if specified. If no duration is passed in, then the progress bar will run indefinitely and must + * be manually cleared by calling {@link #reset}. The wait method accepts a config object with + * the following properties: + *
          +Property   Type          Description
          +---------- ------------  ----------------------------------------------------------------------
          +duration   Number        The length of time in milliseconds that the progress bar should
          +                         run before resetting itself (defaults to undefined, in which case it
          +                         will run indefinitely until reset is called)
          +interval   Number        The length of time in milliseconds between each progress update
          +                         (defaults to 1000 ms)
          +animate    Boolean       Whether to animate the transition of the progress bar. If this value is
          +                         not specified, the default for the class is used.                                                   
          +increment  Number        The number of progress update segments to display within the progress
          +                         bar (defaults to 10).  If the bar reaches the end and is still
          +                         updating, it will automatically wrap back to the beginning.
          +text       String        Optional text to display in the progress bar element (defaults to '').
          +fn         Function      A callback function to execute after the progress bar finishes auto-
          +                         updating.  The function will be called with no arguments.  This function
          +                         will be ignored if duration is not specified since in that case the
          +                         progress bar can only be stopped programmatically, so any required function
          +                         should be called by the same code after it resets the progress bar.
          +scope      Object        The scope that is passed to the callback function (only applies when
          +                         duration and fn are both passed).
          +
          + * + * Example usage: + *
          
          +var p = new Ext.ProgressBar({
          +   renderTo: 'my-el'
           });
          -Ext.reg('colorpalette', Ext.ColorPalette);/**
          - * @class Ext.DatePicker
          - * @extends Ext.Component
          - * Simple date picker class.
          - * @constructor
          - * Create a new DatePicker
          - * @param {Object} config The config object
          - * @xtype datepicker
          - */
          -Ext.DatePicker = Ext.extend(Ext.BoxComponent, {
          -    /**
          -     * @cfg {String} todayText
          -     * The text to display on the button that selects the current date (defaults to 'Today')
          -     */
          -    todayText : 'Today',
          -    /**
          -     * @cfg {String} okText
          -     * The text to display on the ok button (defaults to ' OK ' to give the user extra clicking room)
          -     */
          -    okText : ' OK ',
          -    /**
          -     * @cfg {String} cancelText
          -     * The text to display on the cancel button (defaults to 'Cancel')
          -     */
          -    cancelText : 'Cancel',
          -    /**
          -     * @cfg {String} todayTip
          -     * The tooltip to display for the button that selects the current date (defaults to '{current date} (Spacebar)')
          -     */
          -    todayTip : '{0} (Spacebar)',
          -    /**
          -     * @cfg {String} minText
          -     * The error text to display if the minDate validation fails (defaults to 'This date is before the minimum date')
          -     */
          -    minText : 'This date is before the minimum date',
          -    /**
          -     * @cfg {String} maxText
          -     * The error text to display if the maxDate validation fails (defaults to 'This date is after the maximum date')
          -     */
          -    maxText : 'This date is after the maximum date',
          -    /**
          -     * @cfg {String} format
          -     * The default date format string which can be overriden for localization support.  The format must be
          -     * valid according to {@link Date#parseDate} (defaults to 'm/d/y').
          -     */
          -    format : 'm/d/y',
          -    /**
          -     * @cfg {String} disabledDaysText
          -     * The tooltip to display when the date falls on a disabled day (defaults to 'Disabled')
          -     */
          -    disabledDaysText : 'Disabled',
          -    /**
          -     * @cfg {String} disabledDatesText
          -     * The tooltip text to display when the date falls on a disabled date (defaults to 'Disabled')
          -     */
          -    disabledDatesText : 'Disabled',
          -    /**
          -     * @cfg {Array} monthNames
          -     * An array of textual month names which can be overriden for localization support (defaults to Date.monthNames)
          -     */
          -    monthNames : Date.monthNames,
          -    /**
          -     * @cfg {Array} dayNames
          -     * An array of textual day names which can be overriden for localization support (defaults to Date.dayNames)
          -     */
          -    dayNames : Date.dayNames,
          -    /**
          -     * @cfg {String} nextText
          -     * The next month navigation button tooltip (defaults to 'Next Month (Control+Right)')
          -     */
          -    nextText : 'Next Month (Control+Right)',
          -    /**
          -     * @cfg {String} prevText
          -     * The previous month navigation button tooltip (defaults to 'Previous Month (Control+Left)')
          -     */
          -    prevText : 'Previous Month (Control+Left)',
          -    /**
          -     * @cfg {String} monthYearText
          -     * The header month selector tooltip (defaults to 'Choose a month (Control+Up/Down to move years)')
          -     */
          -    monthYearText : 'Choose a month (Control+Up/Down to move years)',
          -    /**
          -     * @cfg {Number} startDay
          -     * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
          -     */
          -    startDay : 0,
          -    /**
          -     * @cfg {Boolean} showToday
          -     * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar
          -     * that selects the current date (defaults to true).
          -     */
          -    showToday : true,
          -    /**
          -     * @cfg {Date} minDate
          -     * Minimum allowable date (JavaScript date object, defaults to null)
          -     */
          -    /**
          -     * @cfg {Date} maxDate
          -     * Maximum allowable date (JavaScript date object, defaults to null)
          -     */
          -    /**
          -     * @cfg {Array} disabledDays
          -     * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
          -     */
          -    /**
          -     * @cfg {RegExp} disabledDatesRE
          -     * JavaScript regular expression used to disable a pattern of dates (defaults to null).  The {@link #disabledDates}
          -     * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
          -     * disabledDates value.
          -     */
          -    /**
          -     * @cfg {Array} disabledDates
          -     * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular
          -     * expression so they are very powerful. Some examples:
          -     * 
            - *
          • ['03/08/2003', '09/16/2003'] would disable those exact dates
          • - *
          • ['03/08', '09/16'] would disable those days for every year
          • - *
          • ['^03/08'] would only match the beginning (useful if you are using short years)
          • - *
          • ['03/../2006'] would disable every day in March 2006
          • - *
          • ['^03'] would disable every day in every March
          • - *
          - * Note that the format of the dates included in the array should exactly match the {@link #format} config. - * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to - * escape the dot when restricting dates. For example: ['03\\.08\\.03']. - */ - - // private - initComponent : function(){ - Ext.DatePicker.superclass.initComponent.call(this); - - this.value = this.value ? - this.value.clearTime() : new Date().clearTime(); - - this.addEvents( - /** - * @event select - * Fires when a date is selected - * @param {DatePicker} this - * @param {Date} date The selected date - */ - 'select' - ); - - if(this.handler){ - this.on('select', this.handler, this.scope || this); - } - - this.initDisabledDays(); - }, - - // private - initDisabledDays : function(){ - if(!this.disabledDatesRE && this.disabledDates){ - var dd = this.disabledDates, - len = dd.length - 1, - re = '(?:'; - - Ext.each(dd, function(d, i){ - re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i]; - if(i != len){ - re += '|'; - } - }, this); - this.disabledDatesRE = new RegExp(re + ')'); - } - }, - - /** - * Replaces any existing disabled dates with new values and refreshes the DatePicker. - * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config - * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates. - */ - setDisabledDates : function(dd){ - if(Ext.isArray(dd)){ - this.disabledDates = dd; - this.disabledDatesRE = null; - }else{ - this.disabledDatesRE = dd; - } - this.initDisabledDays(); - this.update(this.value, true); - }, - - /** - * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker. - * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config - * for details on supported values. - */ - setDisabledDays : function(dd){ - this.disabledDays = dd; - this.update(this.value, true); - }, - - /** - * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker. - * @param {Date} value The minimum date that can be selected - */ - setMinDate : function(dt){ - this.minDate = dt; - this.update(this.value, true); - }, - - /** - * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker. - * @param {Date} value The maximum date that can be selected - */ - setMaxDate : function(dt){ - this.maxDate = dt; - this.update(this.value, true); - }, - - /** - * Sets the value of the date field - * @param {Date} value The date to set - */ - setValue : function(value){ - var old = this.value; - this.value = value.clearTime(true); - if(this.el){ - this.update(this.value); - } - }, - - /** - * Gets the current selected value of the date field - * @return {Date} The selected date - */ - getValue : function(){ - return this.value; - }, - - // private - focus : function(){ - if(this.el){ - this.update(this.activeDate); - } - }, - - // private - onEnable: function(initial){ - Ext.DatePicker.superclass.onEnable.call(this); - this.doDisabled(false); - this.update(initial ? this.value : this.activeDate); - if(Ext.isIE){ - this.el.repaint(); - } - - }, - - // private - onDisable: function(){ - Ext.DatePicker.superclass.onDisable.call(this); - this.doDisabled(true); - if(Ext.isIE && !Ext.isIE8){ - /* Really strange problem in IE6/7, when disabled, have to explicitly - * repaint each of the nodes to get them to display correctly, simply - * calling repaint on the main element doesn't appear to be enough. - */ - Ext.each([].concat(this.textNodes, this.el.query('th span')), function(el){ - Ext.fly(el).repaint(); - }); - } - }, - - // private - doDisabled: function(disabled){ - this.keyNav.setDisabled(disabled); - this.prevRepeater.setDisabled(disabled); - this.nextRepeater.setDisabled(disabled); - if(this.showToday){ - this.todayKeyListener.setDisabled(disabled); - this.todayBtn.setDisabled(disabled); - } - }, - - // private - onRender : function(container, position){ - var m = [ - '', - '', - '', - this.showToday ? '' : '', - '
            
          '], - dn = this.dayNames, - i; - for(i = 0; i < 7; i++){ - var d = this.startDay+i; - if(d > 6){ - d = d-7; - } - m.push(''); - } - m[m.length] = ''; - for(i = 0; i < 42; i++) { - if(i % 7 === 0 && i !== 0){ - m[m.length] = ''; - } - m[m.length] = ''; - } - m.push('
          ', dn[d].substr(0,1), '
          '); - - var el = document.createElement('div'); - el.className = 'x-date-picker'; - el.innerHTML = m.join(''); - - container.dom.insertBefore(el, position); - - this.el = Ext.get(el); - this.eventEl = Ext.get(el.firstChild); - - this.prevRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-left a'), { - handler: this.showPrevMonth, - scope: this, - preventDefault:true, - stopDefault:true - }); - - this.nextRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-right a'), { - handler: this.showNextMonth, - scope: this, - preventDefault:true, - stopDefault:true - }); - - this.monthPicker = this.el.down('div.x-date-mp'); - this.monthPicker.enableDisplayMode('block'); - - this.keyNav = new Ext.KeyNav(this.eventEl, { - 'left' : function(e){ - if(e.ctrlKey){ - this.showPrevMonth(); - }else{ - this.update(this.activeDate.add('d', -1)); - } - }, - - 'right' : function(e){ - if(e.ctrlKey){ - this.showNextMonth(); - }else{ - this.update(this.activeDate.add('d', 1)); - } - }, - - 'up' : function(e){ - if(e.ctrlKey){ - this.showNextYear(); - }else{ - this.update(this.activeDate.add('d', -7)); - } - }, - - 'down' : function(e){ - if(e.ctrlKey){ - this.showPrevYear(); - }else{ - this.update(this.activeDate.add('d', 7)); - } - }, - - 'pageUp' : function(e){ - this.showNextMonth(); - }, - - 'pageDown' : function(e){ - this.showPrevMonth(); - }, - - 'enter' : function(e){ - e.stopPropagation(); - return true; - }, - - scope : this - }); - - this.el.unselectable(); - - this.cells = this.el.select('table.x-date-inner tbody td'); - this.textNodes = this.el.query('table.x-date-inner tbody span'); - - this.mbtn = new Ext.Button({ - text: ' ', - tooltip: this.monthYearText, - renderTo: this.el.child('td.x-date-middle', true) - }); - this.mbtn.el.child('em').addClass('x-btn-arrow'); - - if(this.showToday){ - this.todayKeyListener = this.eventEl.addKeyListener(Ext.EventObject.SPACE, this.selectToday, this); - var today = (new Date()).dateFormat(this.format); - this.todayBtn = new Ext.Button({ - renderTo: this.el.child('td.x-date-bottom', true), - text: String.format(this.todayText, today), - tooltip: String.format(this.todayTip, today), - handler: this.selectToday, - scope: this - }); - } - this.mon(this.eventEl, 'mousewheel', this.handleMouseWheel, this); - this.mon(this.eventEl, 'click', this.handleDateClick, this, {delegate: 'a.x-date-date'}); - this.mon(this.mbtn, 'click', this.showMonthPicker, this); - this.onEnable(true); - }, - - // private - createMonthPicker : function(){ - if(!this.monthPicker.dom.firstChild){ - var buf = ['']; - for(var i = 0; i < 6; i++){ - buf.push( - '', - '', - i === 0 ? - '' : - '' - ); - } - buf.push( - '', - '
          ', Date.getShortMonthName(i), '', Date.getShortMonthName(i + 6), '
          ' - ); - this.monthPicker.update(buf.join('')); - - this.mon(this.monthPicker, 'click', this.onMonthClick, this); - this.mon(this.monthPicker, 'dblclick', this.onMonthDblClick, this); - - this.mpMonths = this.monthPicker.select('td.x-date-mp-month'); - this.mpYears = this.monthPicker.select('td.x-date-mp-year'); - - this.mpMonths.each(function(m, a, i){ - i += 1; - if((i%2) === 0){ - m.dom.xmonth = 5 + Math.round(i * 0.5); - }else{ - m.dom.xmonth = Math.round((i-1) * 0.5); - } - }); - } - }, - - // private - showMonthPicker : function(){ - if(!this.disabled){ - this.createMonthPicker(); - var size = this.el.getSize(); - this.monthPicker.setSize(size); - this.monthPicker.child('table').setSize(size); - - this.mpSelMonth = (this.activeDate || this.value).getMonth(); - this.updateMPMonth(this.mpSelMonth); - this.mpSelYear = (this.activeDate || this.value).getFullYear(); - this.updateMPYear(this.mpSelYear); - - this.monthPicker.slideIn('t', {duration:0.2}); - } - }, - - // private - updateMPYear : function(y){ - this.mpyear = y; - var ys = this.mpYears.elements; - for(var i = 1; i <= 10; i++){ - var td = ys[i-1], y2; - if((i%2) === 0){ - y2 = y + Math.round(i * 0.5); - td.firstChild.innerHTML = y2; - td.xyear = y2; - }else{ - y2 = y - (5-Math.round(i * 0.5)); - td.firstChild.innerHTML = y2; - td.xyear = y2; - } - this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel'); - } - }, - - // private - updateMPMonth : function(sm){ - this.mpMonths.each(function(m, a, i){ - m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel'); - }); - }, - - // private - selectMPMonth : function(m){ - - }, - - // private - onMonthClick : function(e, t){ - e.stopEvent(); - var el = new Ext.Element(t), pn; - if(el.is('button.x-date-mp-cancel')){ - this.hideMonthPicker(); - } - else if(el.is('button.x-date-mp-ok')){ - var d = new Date(this.mpSelYear, this.mpSelMonth, (this.activeDate || this.value).getDate()); - if(d.getMonth() != this.mpSelMonth){ - // 'fix' the JS rolling date conversion if needed - d = new Date(this.mpSelYear, this.mpSelMonth, 1).getLastDateOfMonth(); - } - this.update(d); - this.hideMonthPicker(); - } - else if((pn = el.up('td.x-date-mp-month', 2))){ - this.mpMonths.removeClass('x-date-mp-sel'); - pn.addClass('x-date-mp-sel'); - this.mpSelMonth = pn.dom.xmonth; - } - else if((pn = el.up('td.x-date-mp-year', 2))){ - this.mpYears.removeClass('x-date-mp-sel'); - pn.addClass('x-date-mp-sel'); - this.mpSelYear = pn.dom.xyear; - } - else if(el.is('a.x-date-mp-prev')){ - this.updateMPYear(this.mpyear-10); - } - else if(el.is('a.x-date-mp-next')){ - this.updateMPYear(this.mpyear+10); - } - }, - - // private - onMonthDblClick : function(e, t){ - e.stopEvent(); - var el = new Ext.Element(t), pn; - if((pn = el.up('td.x-date-mp-month', 2))){ - this.update(new Date(this.mpSelYear, pn.dom.xmonth, (this.activeDate || this.value).getDate())); - this.hideMonthPicker(); - } - else if((pn = el.up('td.x-date-mp-year', 2))){ - this.update(new Date(pn.dom.xyear, this.mpSelMonth, (this.activeDate || this.value).getDate())); - this.hideMonthPicker(); - } - }, - - // private - hideMonthPicker : function(disableAnim){ - if(this.monthPicker){ - if(disableAnim === true){ - this.monthPicker.hide(); - }else{ - this.monthPicker.slideOut('t', {duration:0.2}); - } - } - }, - - // private - showPrevMonth : function(e){ - this.update(this.activeDate.add('mo', -1)); - }, - - // private - showNextMonth : function(e){ - this.update(this.activeDate.add('mo', 1)); - }, - - // private - showPrevYear : function(){ - this.update(this.activeDate.add('y', -1)); - }, - - // private - showNextYear : function(){ - this.update(this.activeDate.add('y', 1)); - }, - - // private - handleMouseWheel : function(e){ - e.stopEvent(); - if(!this.disabled){ - var delta = e.getWheelDelta(); - if(delta > 0){ - this.showPrevMonth(); - } else if(delta < 0){ - this.showNextMonth(); - } - } - }, - - // private - handleDateClick : function(e, t){ - e.stopEvent(); - if(!this.disabled && t.dateValue && !Ext.fly(t.parentNode).hasClass('x-date-disabled')){ - this.setValue(new Date(t.dateValue)); - this.fireEvent('select', this, this.value); - } - }, - - // private - selectToday : function(){ - if(this.todayBtn && !this.todayBtn.disabled){ - this.setValue(new Date().clearTime()); - this.fireEvent('select', this, this.value); - } - }, - - // private - update : function(date, forceRefresh){ - var vd = this.activeDate, vis = this.isVisible(); - this.activeDate = date; - if(!forceRefresh && vd && this.el){ - var t = date.getTime(); - if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){ - this.cells.removeClass('x-date-selected'); - this.cells.each(function(c){ - if(c.dom.firstChild.dateValue == t){ - c.addClass('x-date-selected'); - if(vis){ - Ext.fly(c.dom.firstChild).focus(50); - } - return false; - } - }); - return; - } - } - var days = date.getDaysInMonth(); - var firstOfMonth = date.getFirstDateOfMonth(); - var startingPos = firstOfMonth.getDay()-this.startDay; - - if(startingPos <= this.startDay){ - startingPos += 7; - } - - var pm = date.add('mo', -1); - var prevStart = pm.getDaysInMonth()-startingPos; - - var cells = this.cells.elements; - var textEls = this.textNodes; - days += startingPos; - - // convert everything to numbers so it's fast - var day = 86400000; - var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime(); - var today = new Date().clearTime().getTime(); - var sel = date.clearTime().getTime(); - var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY; - var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY; - var ddMatch = this.disabledDatesRE; - var ddText = this.disabledDatesText; - var ddays = this.disabledDays ? this.disabledDays.join('') : false; - var ddaysText = this.disabledDaysText; - var format = this.format; - - if(this.showToday){ - var td = new Date().clearTime(); - var disable = (td < min || td > max || - (ddMatch && format && ddMatch.test(td.dateFormat(format))) || - (ddays && ddays.indexOf(td.getDay()) != -1)); - - if(!this.disabled){ - this.todayBtn.setDisabled(disable); - this.todayKeyListener[disable ? 'disable' : 'enable'](); - } - } - - var setCellClass = function(cal, cell){ - cell.title = ''; - var t = d.getTime(); - cell.firstChild.dateValue = t; - if(t == today){ - cell.className += ' x-date-today'; - cell.title = cal.todayText; - } - if(t == sel){ - cell.className += ' x-date-selected'; - if(vis){ - Ext.fly(cell.firstChild).focus(50); - } - } - // disabling - if(t < min) { - cell.className = ' x-date-disabled'; - cell.title = cal.minText; - return; - } - if(t > max) { - cell.className = ' x-date-disabled'; - cell.title = cal.maxText; - return; - } - if(ddays){ - if(ddays.indexOf(d.getDay()) != -1){ - cell.title = ddaysText; - cell.className = ' x-date-disabled'; - } - } - if(ddMatch && format){ - var fvalue = d.dateFormat(format); - if(ddMatch.test(fvalue)){ - cell.title = ddText.replace('%0', fvalue); - cell.className = ' x-date-disabled'; - } - } - }; - - var i = 0; - for(; i < startingPos; i++) { - textEls[i].innerHTML = (++prevStart); - d.setDate(d.getDate()+1); - cells[i].className = 'x-date-prevday'; - setCellClass(this, cells[i]); - } - for(; i < days; i++){ - var intDay = i - startingPos + 1; - textEls[i].innerHTML = (intDay); - d.setDate(d.getDate()+1); - cells[i].className = 'x-date-active'; - setCellClass(this, cells[i]); - } - var extraDays = 0; - for(; i < 42; i++) { - textEls[i].innerHTML = (++extraDays); - d.setDate(d.getDate()+1); - cells[i].className = 'x-date-nextday'; - setCellClass(this, cells[i]); - } - - this.mbtn.setText(this.monthNames[date.getMonth()] + ' ' + date.getFullYear()); - - if(!this.internalRender){ - var main = this.el.dom.firstChild; - var w = main.offsetWidth; - this.el.setWidth(w + this.el.getBorderWidth('lr')); - Ext.fly(main).setWidth(w); - this.internalRender = true; - // opera does not respect the auto grow header center column - // then, after it gets a width opera refuses to recalculate - // without a second pass - if(Ext.isOpera && !this.secondPass){ - main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + 'px'; - this.secondPass = true; - this.update.defer(10, this, [date]); - } - } - }, - - // private - beforeDestroy : function() { - if(this.rendered){ - this.keyNav.disable(); - this.keyNav = null; - Ext.destroy( - this.leftClickRpt, - this.rightClickRpt, - this.monthPicker, - this.eventEl, - this.mbtn, - this.todayBtn - ); - } - } - - /** - * @cfg {String} autoEl @hide - */ -}); - -Ext.reg('datepicker', Ext.DatePicker); -/** - * @class Ext.LoadMask - * A simple utility class for generically masking elements while loading data. If the {@link #store} - * config option is specified, the masking will be automatically synchronized with the store's loading - * process and the mask element will be cached for reuse. For all other elements, this mask will replace the - * element's Updater load indicator and will be destroyed after the initial load. - *

          Example usage:

          - *
          
          -// Basic mask:
          -var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
          -myMask.show();
          -
          - * @constructor - * Create a new LoadMask - * @param {Mixed} el The element or DOM node, or its id - * @param {Object} config The config object - */ -Ext.LoadMask = function(el, config){ - this.el = Ext.get(el); - Ext.apply(this, config); - if(this.store){ - this.store.on('beforeload', this.onBeforeLoad, this); - this.store.on('load', this.onLoad, this); - this.store.on('exception', this.onLoad, this); - this.removeMask = Ext.value(this.removeMask, false); - }else{ - var um = this.el.getUpdater(); - um.showLoadIndicator = false; // disable the default indicator - um.on('beforeupdate', this.onBeforeLoad, this); - um.on('update', this.onLoad, this); - um.on('failure', this.onLoad, this); - this.removeMask = Ext.value(this.removeMask, true); - } -}; -Ext.LoadMask.prototype = { - /** - * @cfg {Ext.data.Store} store - * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and - * hidden on either load sucess, or load fail. - */ - /** - * @cfg {Boolean} removeMask - * True to create a single-use mask that is automatically destroyed after loading (useful for page loads), - * False to persist the mask element reference for multiple uses (e.g., for paged data widgets). Defaults to false. - */ - /** - * @cfg {String} msg - * The text to display in a centered loading message box (defaults to 'Loading...') - */ - msg : 'Loading...', - /** - * @cfg {String} msgCls - * The CSS class to apply to the loading message element (defaults to "x-mask-loading") - */ - msgCls : 'x-mask-loading', +//Wait for 5 seconds, then update the status el (progress bar will auto-reset) +p.wait({ + interval: 100, //bar will move fast! + duration: 5000, + increment: 15, + text: 'Updating...', + scope: this, + fn: function(){ + Ext.fly('status').update('Done!'); + } +}); - /** - * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false) - * @type Boolean - */ - disabled: false, +//Or update indefinitely until some async action completes, then reset manually +p.wait(); +myAction.on('complete', function(){ + p.reset(); + Ext.fly('status').update('Done!'); +}); +
          + * @param {Object} config (optional) Configuration options + * @return {Ext.ProgressBar} this + */ + wait : function(o){ + if(!this.waitTimer){ + var scope = this; + o = o || {}; + this.updateText(o.text); + this.waitTimer = Ext.TaskMgr.start({ + run: function(i){ + var inc = o.increment || 10; + i -= 1; + this.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate); + }, + interval: o.interval || 1000, + duration: o.duration, + onStop: function(){ + if(o.fn){ + o.fn.apply(o.scope || this); + } + this.reset(); + }, + scope: scope + }); + } + return this; + }, /** - * Disables the mask to prevent it from being displayed + * Returns true if the progress bar is currently in a {@link #wait} operation + * @return {Boolean} True if waiting, else false */ - disable : function(){ - this.disabled = true; + isWaiting : function(){ + return this.waitTimer !== null; }, /** - * Enables the mask so that it can be displayed + * Updates the progress bar text. If specified, textEl will be updated, otherwise the progress + * bar itself will display the updated text. + * @param {String} text (optional) The string to display in the progress text element (defaults to '') + * @return {Ext.ProgressBar} this */ - enable : function(){ - this.disabled = false; - }, - - // private - onLoad : function(){ - this.el.unmask(this.removeMask); + updateText : function(text){ + this.text = text || ' '; + if(this.rendered){ + this.textEl.update(this.text); + } + return this; }, - - // private - onBeforeLoad : function(){ - if(!this.disabled){ - this.el.mask(this.msg, this.msgCls); + + /** + * Synchronizes the inner bar width to the proper proportion of the total componet width based + * on the current progress {@link #value}. This will be called automatically when the ProgressBar + * is resized by a layout, but if it is rendered auto width, this method can be called from + * another resize handler to sync the ProgressBar if necessary. + */ + syncProgressBar : function(){ + if(this.value){ + this.updateProgress(this.value, this.text); } + return this; }, /** - * Show this LoadMask over the configured Element. + * Sets the size of the progress bar. + * @param {Number} width The new width in pixels + * @param {Number} height The new height in pixels + * @return {Ext.ProgressBar} this */ - show: function(){ - this.onBeforeLoad(); + setSize : function(w, h){ + Ext.ProgressBar.superclass.setSize.call(this, w, h); + if(this.textTopEl){ + var inner = this.el.dom.firstChild; + this.textEl.setSize(inner.offsetWidth, inner.offsetHeight); + } + this.syncProgressBar(); + return this; }, /** - * Hide this LoadMask. + * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress + * bar will also be hidden (using the {@link #hideMode} property internally). + * @param {Boolean} hide (optional) True to hide the progress bar (defaults to false) + * @return {Ext.ProgressBar} this */ - hide: function(){ - this.onLoad(); + reset : function(hide){ + this.updateProgress(0); + if(this.textTopEl){ + this.textTopEl.addClass('x-hidden'); + } + this.clearTimer(); + if(hide === true){ + this.hide(); + } + return this; }, - + // private - destroy : function(){ - if(this.store){ - this.store.un('beforeload', this.onBeforeLoad, this); - this.store.un('load', this.onLoad, this); - this.store.un('exception', this.onLoad, this); - }else{ - var um = this.el.getUpdater(); - um.un('beforeupdate', this.onBeforeLoad, this); - um.un('update', this.onLoad, this); - um.un('failure', this.onLoad, this); + clearTimer : function(){ + if(this.waitTimer){ + this.waitTimer.onStop = null; //prevent recursion + Ext.TaskMgr.stop(this.waitTimer); + this.waitTimer = null; + } + }, + + onDestroy: function(){ + this.clearTimer(); + if(this.rendered){ + if(this.textEl.isComposite){ + this.textEl.clear(); + } + Ext.destroyMembers(this, 'textEl', 'progressBar', 'textTopEl'); } + Ext.ProgressBar.superclass.onDestroy.call(this); } -};/** - * @class Ext.Slider - * @extends Ext.BoxComponent - * Slider which supports vertical or horizontal orientation, keyboard adjustments, - * configurable snapping, axis clicking and animation. Can be added as an item to - * any container. Example usage: -
          
          -new Ext.Slider({
          -    renderTo: Ext.getBody(),
          -    width: 200,
          -    value: 50,
          -    increment: 10,
          -    minValue: 0,
          -    maxValue: 100
          -});
          -
          - */ -Ext.Slider = Ext.extend(Ext.BoxComponent, { - /** - * @cfg {Number} value The value to initialize the slider with. Defaults to minValue. - */ - /** - * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false. - */ - vertical: false, - /** - * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0. - */ - minValue: 0, - /** - * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100. - */ - maxValue: 100, - /** - * @cfg {Number/Boolean} decimalPrecision. - *

          The number of decimal places to which to round the Slider's value. Defaults to 0.

          - *

          To disable rounding, configure as false.

          - */ - decimalPrecision: 0, - /** - * @cfg {Number} keyIncrement How many units to change the Slider when adjusting with keyboard navigation. Defaults to 1. If the increment config is larger, it will be used instead. - */ - keyIncrement: 1, - /** - * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'. - */ - increment: 0, - // private - clickRange: [5,15], - /** - * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true - */ - clickToChange : true, - /** - * @cfg {Boolean} animate Turn on or off animation. Defaults to true - */ - animate: true, - - /** - * True while the thumb is in a drag operation - * @type boolean - */ - dragging: false, - - // private override - initComponent : function(){ - if(!Ext.isDefined(this.value)){ - this.value = this.minValue; - } - Ext.Slider.superclass.initComponent.call(this); - this.keyIncrement = Math.max(this.increment, this.keyIncrement); - this.addEvents( - /** - * @event beforechange - * Fires before the slider value is changed. By returning false from an event handler, - * you can cancel the event and prevent the slider from changing. - * @param {Ext.Slider} slider The slider - * @param {Number} newValue The new value which the slider is being changed to. - * @param {Number} oldValue The old value which the slider was previously. - */ - 'beforechange', - /** - * @event change - * Fires when the slider value is changed. - * @param {Ext.Slider} slider The slider - * @param {Number} newValue The new value which the slider has been changed to. - */ - 'change', - /** - * @event changecomplete - * Fires when the slider value is changed by the user and any drag operations have completed. - * @param {Ext.Slider} slider The slider - * @param {Number} newValue The new value which the slider has been changed to. - */ - 'changecomplete', - /** - * @event dragstart - * Fires after a drag operation has started. - * @param {Ext.Slider} slider The slider - * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker - */ - 'dragstart', - /** - * @event drag - * Fires continuously during the drag operation while the mouse is moving. - * @param {Ext.Slider} slider The slider - * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker - */ - 'drag', - /** - * @event dragend - * Fires after the drag operation has completed. - * @param {Ext.Slider} slider The slider - * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker - */ - 'dragend' - ); - - if(this.vertical){ - Ext.apply(this, Ext.Slider.Vertical); - } - }, - - // private override - onRender : function(){ - this.autoEl = { - cls: 'x-slider ' + (this.vertical ? 'x-slider-vert' : 'x-slider-horz'), - cn:{cls:'x-slider-end',cn:{cls:'x-slider-inner',cn:[{cls:'x-slider-thumb'},{tag:'a', cls:'x-slider-focus', href:"#", tabIndex: '-1', hidefocus:'on'}]}} - }; - Ext.Slider.superclass.onRender.apply(this, arguments); - this.endEl = this.el.first(); - this.innerEl = this.endEl.first(); - this.thumb = this.innerEl.first(); - this.halfThumb = (this.vertical ? this.thumb.getHeight() : this.thumb.getWidth())/2; - this.focusEl = this.thumb.next(); - this.initEvents(); - }, - - // private override - initEvents : function(){ - this.thumb.addClassOnOver('x-slider-thumb-over'); - this.mon(this.el, { - scope: this, - mousedown: this.onMouseDown, - keydown: this.onKeyDown - }); - - this.focusEl.swallowEvent("click", true); - - this.tracker = new Ext.dd.DragTracker({ - onBeforeStart: this.onBeforeDragStart.createDelegate(this), - onStart: this.onDragStart.createDelegate(this), - onDrag: this.onDrag.createDelegate(this), - onEnd: this.onDragEnd.createDelegate(this), - tolerance: 3, - autoStart: 300 - }); - this.tracker.initEl(this.thumb); - this.on('beforedestroy', this.tracker.destroy, this.tracker); - }, - - // private override - onMouseDown : function(e){ - if(this.disabled) {return;} - if(this.clickToChange && e.target != this.thumb.dom){ - var local = this.innerEl.translatePoints(e.getXY()); - this.onClickChange(local); - } - this.focus(); - }, - - // private - onClickChange : function(local){ - if(local.top > this.clickRange[0] && local.top < this.clickRange[1]){ - this.setValue(Ext.util.Format.round(this.reverseValue(local.left), this.decimalPrecision), undefined, true); - } - }, - - // private - onKeyDown : function(e){ - if(this.disabled){e.preventDefault();return;} - var k = e.getKey(); - switch(k){ - case e.UP: - case e.RIGHT: - e.stopEvent(); - if(e.ctrlKey){ - this.setValue(this.maxValue, undefined, true); - }else{ - this.setValue(this.value+this.keyIncrement, undefined, true); - } - break; - case e.DOWN: - case e.LEFT: - e.stopEvent(); - if(e.ctrlKey){ - this.setValue(this.minValue, undefined, true); - }else{ - this.setValue(this.value-this.keyIncrement, undefined, true); - } - break; - default: - e.preventDefault(); - } - }, - - // private - doSnap : function(value){ - if(!this.increment || this.increment == 1 || !value) { - return value; - } - var newValue = value, inc = this.increment; - var m = value % inc; - if(m != 0){ - newValue -= m; - if(m * 2 > inc){ - newValue += inc; - }else if(m * 2 < -inc){ - newValue -= inc; - } - } - return newValue.constrain(this.minValue, this.maxValue); - }, - - // private - afterRender : function(){ - Ext.Slider.superclass.afterRender.apply(this, arguments); - if(this.value !== undefined){ - var v = this.normalizeValue(this.value); - if(v !== this.value){ - delete this.value; - this.setValue(v, false); - }else{ - this.moveThumb(this.translateValue(v), false); - } - } - }, - - // private - getRatio : function(){ - var w = this.innerEl.getWidth(); - var v = this.maxValue - this.minValue; - return v == 0 ? w : (w/v); - }, - - // private - normalizeValue : function(v){ - v = this.doSnap(v); - v = Ext.util.Format.round(v, this.decimalPrecision); - v = v.constrain(this.minValue, this.maxValue); - return v; - }, - - /** - * Programmatically sets the value of the Slider. Ensures that the value is constrained within - * the minValue and maxValue. - * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue) - * @param {Boolean} animate Turn on or off animation, defaults to true - */ - setValue : function(v, animate, changeComplete){ - v = this.normalizeValue(v); - if(v !== this.value && this.fireEvent('beforechange', this, v, this.value) !== false){ - this.value = v; - this.moveThumb(this.translateValue(v), animate !== false); - this.fireEvent('change', this, v); - if(changeComplete){ - this.fireEvent('changecomplete', this, v); - } - } - }, - - // private - translateValue : function(v){ - var ratio = this.getRatio(); - return (v * ratio)-(this.minValue * ratio)-this.halfThumb; - }, - - reverseValue : function(pos){ - var ratio = this.getRatio(); - return (pos+this.halfThumb+(this.minValue * ratio))/ratio; - }, - - // private - moveThumb: function(v, animate){ - if(!animate || this.animate === false){ - this.thumb.setLeft(v); - }else{ - this.thumb.shift({left: v, stopFx: true, duration:.35}); - } - }, - - // private - focus : function(){ - this.focusEl.focus(10); - }, - - // private - onBeforeDragStart : function(e){ - return !this.disabled; - }, - - // private - onDragStart: function(e){ - this.thumb.addClass('x-slider-thumb-drag'); - this.dragging = true; - this.dragStartValue = this.value; - this.fireEvent('dragstart', this, e); - }, - - // private - onDrag: function(e){ - var pos = this.innerEl.translatePoints(this.tracker.getXY()); - this.setValue(Ext.util.Format.round(this.reverseValue(pos.left), this.decimalPrecision), false); - this.fireEvent('drag', this, e); - }, - - // private - onDragEnd: function(e){ - this.thumb.removeClass('x-slider-thumb-drag'); - this.dragging = false; - this.fireEvent('dragend', this, e); - if(this.dragStartValue != this.value){ - this.fireEvent('changecomplete', this, this.value); - } - }, - - // private - onResize : function(w, h){ - this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r'))); - this.syncThumb(); - }, - - //private - onDisable: function(){ - Ext.Slider.superclass.onDisable.call(this); - this.thumb.addClass(this.disabledClass); - if(Ext.isIE){ - //IE breaks when using overflow visible and opacity other than 1. - //Create a place holder for the thumb and display it. - var xy = this.thumb.getXY(); - this.thumb.hide(); - this.innerEl.addClass(this.disabledClass).dom.disabled = true; - if (!this.thumbHolder){ - this.thumbHolder = this.endEl.createChild({cls: 'x-slider-thumb ' + this.disabledClass}); - } - this.thumbHolder.show().setXY(xy); - } - }, - - //private - onEnable: function(){ - Ext.Slider.superclass.onEnable.call(this); - this.thumb.removeClass(this.disabledClass); - if(Ext.isIE){ - this.innerEl.removeClass(this.disabledClass).dom.disabled = false; - if (this.thumbHolder){ - this.thumbHolder.hide(); - } - this.thumb.show(); - this.syncThumb(); - } - }, - - /** - * Synchronizes the thumb position to the proper proportion of the total component width based - * on the current slider {@link #value}. This will be called automatically when the Slider - * is resized by a layout, but if it is rendered auto width, this method can be called from - * another resize handler to sync the Slider if necessary. - */ - syncThumb : function(){ - if(this.rendered){ - this.moveThumb(this.translateValue(this.value)); - } - }, - - /** - * Returns the current value of the slider - * @return {Number} The current value of the slider - */ - getValue : function(){ - return this.value; - } -}); -Ext.reg('slider', Ext.Slider); - -// private class to support vertical sliders -Ext.Slider.Vertical = { - onResize : function(w, h){ - this.innerEl.setHeight(h - (this.el.getPadding('t') + this.endEl.getPadding('b'))); - this.syncThumb(); - }, - - getRatio : function(){ - var h = this.innerEl.getHeight(); - var v = this.maxValue - this.minValue; - return h/v; - }, - - moveThumb: function(v, animate){ - if(!animate || this.animate === false){ - this.thumb.setBottom(v); - }else{ - this.thumb.shift({bottom: v, stopFx: true, duration:.35}); - } - }, - - onDrag: function(e){ - var pos = this.innerEl.translatePoints(this.tracker.getXY()); - var bottom = this.innerEl.getHeight()-pos.top; - this.setValue(this.minValue + Ext.util.Format.round(bottom/this.getRatio(), this.decimalPrecision), false); - this.fireEvent('drag', this, e); - }, - - onClickChange : function(local){ - if(local.left > this.clickRange[0] && local.left < this.clickRange[1]){ - var bottom = this.innerEl.getHeight()-local.top; - this.setValue(this.minValue + Ext.util.Format.round(bottom/this.getRatio(), this.decimalPrecision), undefined, true); - } - } -};/** - * @class Ext.ProgressBar - * @extends Ext.BoxComponent - *

          An updateable progress bar component. The progress bar supports two different modes: manual and automatic.

          - *

          In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the - * progress bar as needed from your own code. This method is most appropriate when you want to show progress - * throughout an operation that has predictable points of interest at which you can update the control.

          - *

          In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it - * once the operation is complete. You can optionally have the progress bar wait for a specific amount of time - * and then clear itself. Automatic mode is most appropriate for timed operations or asynchronous operations in - * which you have no need for indicating intermediate progress.

          - * @cfg {Float} value A floating point value between 0 and 1 (e.g., .5, defaults to 0) - * @cfg {String} text The progress bar text (defaults to '') - * @cfg {Mixed} textEl The element to render the progress text to (defaults to the progress - * bar's internal text element) - * @cfg {String} id The progress bar element's id (defaults to an auto-generated id) - * @xtype progress - */ -Ext.ProgressBar = Ext.extend(Ext.BoxComponent, { - /** - * @cfg {String} baseCls - * The base CSS class to apply to the progress bar's wrapper element (defaults to 'x-progress') - */ - baseCls : 'x-progress', - - /** - * @cfg {Boolean} animate - * True to animate the progress bar during transitions (defaults to false) - */ - animate : false, - - // private - waitTimer : null, - - // private - initComponent : function(){ - Ext.ProgressBar.superclass.initComponent.call(this); - this.addEvents( - /** - * @event update - * Fires after each update interval - * @param {Ext.ProgressBar} this - * @param {Number} The current progress value - * @param {String} The current progress text - */ - "update" - ); - }, - - // private - onRender : function(ct, position){ - var tpl = new Ext.Template( - '
          ', - '
          ', - '
          ', - '
          ', - '
           
          ', - '
          ', - '
          ', - '
          ', - '
           
          ', - '
          ', - '
          ', - '
          ' - ); - - this.el = position ? tpl.insertBefore(position, {cls: this.baseCls}, true) - : tpl.append(ct, {cls: this.baseCls}, true); - - if(this.id){ - this.el.dom.id = this.id; - } - var inner = this.el.dom.firstChild; - this.progressBar = Ext.get(inner.firstChild); - - if(this.textEl){ - //use an external text el - this.textEl = Ext.get(this.textEl); - delete this.textTopEl; - }else{ - //setup our internal layered text els - this.textTopEl = Ext.get(this.progressBar.dom.firstChild); - var textBackEl = Ext.get(inner.childNodes[1]); - this.textTopEl.setStyle("z-index", 99).addClass('x-hidden'); - this.textEl = new Ext.CompositeElement([this.textTopEl.dom.firstChild, textBackEl.dom.firstChild]); - this.textEl.setWidth(inner.offsetWidth); - } - this.progressBar.setHeight(inner.offsetHeight); - }, - - // private - afterRender : function(){ - Ext.ProgressBar.superclass.afterRender.call(this); - if(this.value){ - this.updateProgress(this.value, this.text); - }else{ - this.updateText(this.text); - } - }, - - /** - * Updates the progress bar value, and optionally its text. If the text argument is not specified, - * any existing text value will be unchanged. To blank out existing text, pass ''. Note that even - * if the progress bar value exceeds 1, it will never automatically reset -- you are responsible for - * determining when the progress is complete and calling {@link #reset} to clear and/or hide the control. - * @param {Float} value (optional) A floating point value between 0 and 1 (e.g., .5, defaults to 0) - * @param {String} text (optional) The string to display in the progress text element (defaults to '') - * @param {Boolean} animate (optional) Whether to animate the transition of the progress bar. If this value is - * not specified, the default for the class is used (default to false) - * @return {Ext.ProgressBar} this - */ - updateProgress : function(value, text, animate){ - this.value = value || 0; - if(text){ - this.updateText(text); - } - if(this.rendered){ - var w = Math.floor(value*this.el.dom.firstChild.offsetWidth); - this.progressBar.setWidth(w, animate === true || (animate !== false && this.animate)); - if(this.textTopEl){ - //textTopEl should be the same width as the bar so overflow will clip as the bar moves - this.textTopEl.removeClass('x-hidden').setWidth(w); - } - } - this.fireEvent('update', this, value, text); - return this; - }, - - /** - * Initiates an auto-updating progress bar. A duration can be specified, in which case the progress - * bar will automatically reset after a fixed amount of time and optionally call a callback function - * if specified. If no duration is passed in, then the progress bar will run indefinitely and must - * be manually cleared by calling {@link #reset}. The wait method accepts a config object with - * the following properties: - *
          -Property   Type          Description
          ----------- ------------  ----------------------------------------------------------------------
          -duration   Number        The length of time in milliseconds that the progress bar should
          -                         run before resetting itself (defaults to undefined, in which case it
          -                         will run indefinitely until reset is called)
          -interval   Number        The length of time in milliseconds between each progress update
          -                         (defaults to 1000 ms)
          -animate    Boolean       Whether to animate the transition of the progress bar. If this value is
          -                         not specified, the default for the class is used.                                                   
          -increment  Number        The number of progress update segments to display within the progress
          -                         bar (defaults to 10).  If the bar reaches the end and is still
          -                         updating, it will automatically wrap back to the beginning.
          -text       String        Optional text to display in the progress bar element (defaults to '').
          -fn         Function      A callback function to execute after the progress bar finishes auto-
          -                         updating.  The function will be called with no arguments.  This function
          -                         will be ignored if duration is not specified since in that case the
          -                         progress bar can only be stopped programmatically, so any required function
          -                         should be called by the same code after it resets the progress bar.
          -scope      Object        The scope that is passed to the callback function (only applies when
          -                         duration and fn are both passed).
          -
          - * - * Example usage: - *
          
          -var p = new Ext.ProgressBar({
          -   renderTo: 'my-el'
          -});
          -
          -//Wait for 5 seconds, then update the status el (progress bar will auto-reset)
          -p.wait({
          -   interval: 100, //bar will move fast!
          -   duration: 5000,
          -   increment: 15,
          -   text: 'Updating...',
          -   scope: this,
          -   fn: function(){
          -      Ext.fly('status').update('Done!');
          -   }
          -});
          -
          -//Or update indefinitely until some async action completes, then reset manually
          -p.wait();
          -myAction.on('complete', function(){
          -    p.reset();
          -    Ext.fly('status').update('Done!');
          -});
          -
          - * @param {Object} config (optional) Configuration options - * @return {Ext.ProgressBar} this - */ - wait : function(o){ - if(!this.waitTimer){ - var scope = this; - o = o || {}; - this.updateText(o.text); - this.waitTimer = Ext.TaskMgr.start({ - run: function(i){ - var inc = o.increment || 10; - this.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate); - }, - interval: o.interval || 1000, - duration: o.duration, - onStop: function(){ - if(o.fn){ - o.fn.apply(o.scope || this); - } - this.reset(); - }, - scope: scope - }); - } - return this; - }, - - /** - * Returns true if the progress bar is currently in a {@link #wait} operation - * @return {Boolean} True if waiting, else false - */ - isWaiting : function(){ - return this.waitTimer !== null; - }, - - /** - * Updates the progress bar text. If specified, textEl will be updated, otherwise the progress - * bar itself will display the updated text. - * @param {String} text (optional) The string to display in the progress text element (defaults to '') - * @return {Ext.ProgressBar} this - */ - updateText : function(text){ - this.text = text || ' '; - if(this.rendered){ - this.textEl.update(this.text); - } - return this; - }, - - /** - * Synchronizes the inner bar width to the proper proportion of the total componet width based - * on the current progress {@link #value}. This will be called automatically when the ProgressBar - * is resized by a layout, but if it is rendered auto width, this method can be called from - * another resize handler to sync the ProgressBar if necessary. - */ - syncProgressBar : function(){ - if(this.value){ - this.updateProgress(this.value, this.text); - } - return this; - }, - - /** - * Sets the size of the progress bar. - * @param {Number} width The new width in pixels - * @param {Number} height The new height in pixels - * @return {Ext.ProgressBar} this - */ - setSize : function(w, h){ - Ext.ProgressBar.superclass.setSize.call(this, w, h); - if(this.textTopEl){ - var inner = this.el.dom.firstChild; - this.textEl.setSize(inner.offsetWidth, inner.offsetHeight); - } - this.syncProgressBar(); - return this; - }, - - /** - * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress - * bar will also be hidden (using the {@link #hideMode} property internally). - * @param {Boolean} hide (optional) True to hide the progress bar (defaults to false) - * @return {Ext.ProgressBar} this - */ - reset : function(hide){ - this.updateProgress(0); - if(this.textTopEl){ - this.textTopEl.addClass('x-hidden'); - } - if(this.waitTimer){ - this.waitTimer.onStop = null; //prevent recursion - Ext.TaskMgr.stop(this.waitTimer); - this.waitTimer = null; - } - if(hide === true){ - this.hide(); - } - return this; - } -}); +}); Ext.reg('progress', Ext.ProgressBar); \ No newline at end of file