X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/6e39d509471fe9b4e2660e0d1631b350d0c66f40..0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6:/pkgs/cmp-foundation-debug.js diff --git a/pkgs/cmp-foundation-debug.js b/pkgs/cmp-foundation-debug.js index b791522b..f6bcd75b 100644 --- a/pkgs/cmp-foundation-debug.js +++ b/pkgs/cmp-foundation-debug.js @@ -1,8 +1,8 @@ /*! - * Ext JS Library 3.1.0 - * Copyright(c) 2006-2009 Ext JS, LLC - * licensing@extjs.com - * http://www.extjs.com/license + * Ext JS Library 3.3.1 + * Copyright(c) 2006-2010 Sencha Inc. + * licensing@sencha.com + * http://www.sencha.com/license */ /** * @class Ext.ComponentMgr @@ -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 @@ -205,10 +226,11 @@ editorgrid {@link Ext.grid.EditorGridPanel} flash {@link Ext.FlashComponent} grid {@link Ext.grid.GridPanel} listview {@link Ext.ListView} +multislider {@link Ext.slider.MultiSlider} panel {@link Ext.Panel} progress {@link Ext.ProgressBar} propertygrid {@link Ext.grid.PropertyGrid} -slider {@link Ext.Slider} +slider {@link Ext.slider.SingleSlider} spacer {@link Ext.Spacer} splitbutton {@link Ext.SplitButton} tabpanel {@link Ext.TabPanel} @@ -245,6 +267,7 @@ form {@link Ext.form.FormPanel} checkbox {@link Ext.form.Checkbox} checkboxgroup {@link Ext.form.CheckboxGroup} combo {@link Ext.form.ComboBox} +compositefield {@link Ext.form.CompositeField} datefield {@link Ext.form.DateField} displayfield {@link Ext.form.DisplayField} field {@link Ext.form.Field} @@ -608,17 +631,6 @@ new Ext.FormPanel({ */ - // 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.

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

The unique id of this component (defaults to an {@link #getId auto-assigned id}). @@ -999,7 +1011,7 @@ new Ext.Panel({ * @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 + * Used in conjunction with the {@link #data} and * {@link #tplWriteMode} configurations. */ @@ -1015,6 +1027,14 @@ new Ext.Panel({ * 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 @@ -1069,7 +1089,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.

@@ -1261,10 +1296,10 @@ var myGrid = new Ext.grid.EditorGridPanel({ */ if(this.ref && !this.refOwner){ var levels = this.ref.split('/'), - last = levels.length, + last = levels.length, i = 0, t = this; - + while(t && i < last){ t = t.ownerCt; ++i; @@ -1307,7 +1342,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 @@ -1444,6 +1479,10 @@ var myGrid = new Ext.grid.EditorGridPanel({ 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); @@ -1526,10 +1565,11 @@ new Ext.Panel({ */ focus : function(selectText, delay){ if(delay){ - this.focus.defer(Ext.isNumber(delay) ? delay : 10, this, [selectText, false]); - return; + this.focusTask = new Ext.util.DelayedTask(this.focus, this, [selectText, false]); + this.focusTask.delay(Ext.isNumber(delay) ? delay : 10); + return this; } - if(this.rendered){ + if(this.rendered && !this.isDestroyed){ this.el.focus(); if(selectText === true){ this.el.dom.select(); @@ -1710,7 +1750,13 @@ var isText = t.isXType('textfield'); // true var isBoxSubclass = t.isXType('box'); // true, descended from BoxComponent var isBoxInstance = t.isXType('box', true); // false, not a direct BoxComponent instance - * @param {String} xtype The xtype to check for this Component + * @param {String/Ext.Component/Class} xtype The xtype to check for this Component. Note that the the component can either be an instance + * or a component class: + *

+var c = new Ext.Component();
+console.log(c.isXType(c));
+console.log(c.isXType(Ext.Component)); 
+
* @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is * the default), or true to check whether this Component is directly of the specified xtype. * @return {Boolean} True if this component descends from the specified xtype, false otherwise. @@ -1765,17 +1811,37 @@ alert(t.getXTypes()); // alerts 'component/box/field/textfield' /** * Find a container above this component at any level by xtype or class - * @param {String/Class} xtype The xtype string for a component, or the class of the component directly + * @param {String/Ext.Component/Class} xtype The xtype to check for this Component. Note that the the component can either be an instance + * or a component class: + * @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is + * the default), or true to check whether this Component is directly of the specified xtype. * @return {Ext.Container} The first Container which matches the given xtype or class */ - findParentByType : function(xtype) { - return Ext.isFunction(xtype) ? - this.findParentBy(function(p){ - return p.constructor === xtype; - }) : - this.findParentBy(function(p){ - return p.constructor.xtype === xtype; - }); + findParentByType : function(xtype, shallow){ + return this.findParentBy(function(c){ + return c.isXType(xtype, shallow); + }); + }, + + /** + * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope (this) of + * function call will be the scope provided or the current component. The arguments to the function + * will be the args provided or the current component. If the function returns false at any point, + * the bubble is stopped. + * @param {Function} fn The function to call + * @param {Object} scope (optional) The scope of the function (defaults to current node) + * @param {Array} args (optional) The args to call the function with (default to passing the current component) + * @return {Ext.Component} this + */ + bubble : function(fn, scope, args){ + var p = this; + while(p){ + if(fn.apply(scope || p, args || [p]) === false){ + break; + } + p = p.ownerCt; + } + return this; }, // protected @@ -1921,253 +1987,254 @@ myGridPanel.mon(myGridPanel.getSelectionModel(), { } }); -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); - } -}); +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 @@ -2191,9 +2258,10 @@ Ext.Action = Ext.extend(Object, { (function(){ Ext.Layer = function(config, existingEl){ config = config || {}; - var dh = Ext.DomHelper; - var cp = config.parentEl, pel = cp ? Ext.getDom(cp) : document.body; - if(existingEl){ + var dh = Ext.DomHelper, + cp = config.parentEl, pel = cp ? Ext.getDom(cp) : document.body; + + if (existingEl) { this.dom = Ext.getDom(existingEl); } if(!this.dom){ @@ -2292,41 +2360,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); } - } }, @@ -2410,6 +2476,10 @@ Ext.extend(Ext.Layer, Ext.Element, { } return this; }, + + getConstrainOffset : function(){ + return this.shadowOffset; + }, isVisible : function(){ return this.visible; @@ -2637,19 +2707,23 @@ Ext.extend(Ext.Layer, Ext.Element, { * Create a new Shadow * @param {Object} config The config object */ -Ext.Shadow = function(config){ +Ext.Shadow = function(config) { Ext.apply(this, config); - if(typeof this.mode != "string"){ + if (typeof this.mode != "string") { this.mode = this.defaultMode; } - var o = this.offset, a = {h: 0}; - var rad = Math.floor(this.offset/2); - switch(this.mode.toLowerCase()){ // all this hideous nonsense calculates the various offsets for shadows + var o = this.offset, + a = { + h: 0 + }, + rad = Math.floor(this.offset / 2); + switch (this.mode.toLowerCase()) { + // all this hideous nonsense calculates the various offsets for shadows case "drop": a.w = 0; a.l = a.t = o; a.t -= 1; - if(Ext.isIE){ + if (Ext.isIE) { a.l -= this.offset + rad; a.t -= this.offset + rad; a.w -= rad; @@ -2658,24 +2732,24 @@ Ext.Shadow = function(config){ } break; case "sides": - a.w = (o*2); + a.w = (o * 2); a.l = -o; - a.t = o-1; - if(Ext.isIE){ + a.t = o - 1; + if (Ext.isIE) { a.l -= (this.offset - rad); a.t -= this.offset + rad; a.l += 1; - a.w -= (this.offset - rad)*2; + a.w -= (this.offset - rad) * 2; a.w -= rad + 1; a.h -= 1; } break; case "frame": - a.w = a.h = (o*2); + a.w = a.h = (o * 2); a.l = a.t = -o; a.t += 1; a.h -= 2; - if(Ext.isIE){ + if (Ext.isIE) { a.l -= (this.offset - rad); a.t -= (this.offset - rad); a.l += 1; @@ -2711,23 +2785,23 @@ Ext.Shadow.prototype = { * Displays the shadow under the target element * @param {Mixed} targetEl The id or element under which the shadow should display */ - show : function(target){ + show: function(target) { target = Ext.get(target); - if(!this.el){ + if (!this.el) { this.el = Ext.Shadow.Pool.pull(); - if(this.el.dom.nextSibling != target.dom){ + if (this.el.dom.nextSibling != target.dom) { this.el.insertBefore(target); } } - this.el.setStyle("z-index", this.zIndex || parseInt(target.getStyle("z-index"), 10)-1); - if(Ext.isIE){ - this.el.dom.style.filter="progid:DXImageTransform.Microsoft.alpha(opacity=50) progid:DXImageTransform.Microsoft.Blur(pixelradius="+(this.offset)+")"; + this.el.setStyle("z-index", this.zIndex || parseInt(target.getStyle("z-index"), 10) - 1); + if (Ext.isIE) { + this.el.dom.style.filter = "progid:DXImageTransform.Microsoft.alpha(opacity=50) progid:DXImageTransform.Microsoft.Blur(pixelradius=" + (this.offset) + ")"; } this.realign( - target.getLeft(true), - target.getTop(true), - target.getWidth(), - target.getHeight() + target.getLeft(true), + target.getTop(true), + target.getWidth(), + target.getHeight() ); this.el.dom.style.display = "block"; }, @@ -2735,8 +2809,8 @@ Ext.Shadow.prototype = { /** * Returns true if the shadow is visible, else false */ - isVisible : function(){ - return this.el ? true : false; + isVisible: function() { + return this.el ? true: false; }, /** @@ -2747,25 +2821,32 @@ Ext.Shadow.prototype = { * @param {Number} width The target element width * @param {Number} height The target element height */ - realign : function(l, t, w, h){ - if(!this.el){ + realign: function(l, t, w, h) { + if (!this.el) { return; } - var a = this.adjusts, d = this.el.dom, s = d.style; - var iea = 0; - s.left = (l+a.l)+"px"; - s.top = (t+a.t)+"px"; - var sw = (w+a.w), sh = (h+a.h), sws = sw +"px", shs = sh + "px"; - if(s.width != sws || s.height != shs){ + var a = this.adjusts, + d = this.el.dom, + s = d.style, + iea = 0, + sw = (w + a.w), + sh = (h + a.h), + sws = sw + "px", + shs = sh + "px", + cn, + sww; + s.left = (l + a.l) + "px"; + s.top = (t + a.t) + "px"; + if (s.width != sws || s.height != shs) { s.width = sws; s.height = shs; - if(!Ext.isIE){ - var cn = d.childNodes; - var sww = Math.max(0, (sw-12))+"px"; + if (!Ext.isIE) { + cn = d.childNodes; + sww = Math.max(0, (sw - 12)) + "px"; cn[0].childNodes[1].style.width = sww; cn[1].childNodes[1].style.width = sww; cn[2].childNodes[1].style.width = sww; - cn[1].style.height = Math.max(0, (sh-12))+"px"; + cn[1].style.height = Math.max(0, (sh - 12)) + "px"; } } }, @@ -2773,8 +2854,8 @@ Ext.Shadow.prototype = { /** * Hides this shadow */ - hide : function(){ - if(this.el){ + hide: function() { + if (this.el) { this.el.dom.style.display = "none"; Ext.Shadow.Pool.push(this.el); delete this.el; @@ -2785,31 +2866,31 @@ Ext.Shadow.prototype = { * Adjust the z-index of this shadow * @param {Number} zindex The new z-index */ - setZIndex : function(z){ + setZIndex: function(z) { this.zIndex = z; - if(this.el){ + if (this.el) { this.el.setStyle("z-index", z); } } }; // Private utility class that manages the internal Shadow cache -Ext.Shadow.Pool = function(){ - var p = []; - var markup = Ext.isIE ? - '
' : - '
'; +Ext.Shadow.Pool = function() { + var p = [], + markup = Ext.isIE ? + '
': + '
'; return { - pull : function(){ + pull: function() { var sh = p.shift(); - if(!sh){ + if (!sh) { sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, markup)); sh.autoBoxAdjust = false; } return sh; }, - push : function(sh){ + push: function(sh) { p.push(sh); } }; @@ -2843,6 +2924,26 @@ var myImage = new Ext.BoxComponent({ */ Ext.BoxComponent = Ext.extend(Ext.Component, { + // Configs below are used for all Components when rendered by BoxLayout. + /** + * @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:

+ *

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 @@ -3068,7 +3169,8 @@ var myPanel = new Ext.Panel({ // support for standard size objects if(typeof w == 'object'){ - h = w.height, w = w.width; + h = w.height; + w = w.width; } if (Ext.isDefined(w) && Ext.isDefined(this.boxMinWidth) && (w < this.boxMinWidth)) { w = this.boxMinWidth; @@ -3084,7 +3186,8 @@ var myPanel = new Ext.Panel({ } // not rendered if(!this.boxReady){ - this.width = w, this.height = h; + this.width = w; + this.height = h; return this; } @@ -3107,14 +3210,15 @@ var myPanel = new Ext.Panel({ rz.setWidth(aw); } this.onResize(aw, ah, w, h); + this.fireEvent('resize', this, aw, ah, w, h); } return this; }, /** * 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:
* @param (Ext.form.Field} field The {@link Ext.form.Field Field} being rendered. - * @return An object hash containing the properties required to render the Field. + * @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: this.getLabelStyle(field.labelStyle), - elementStyle: this.elementStyle||'', - labelSeparator: noLabelSep ? '' : (Ext.isDefined(field.labelSeparator) ? field.labelSeparator : this.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) }; }, @@ -6988,4950 +7351,7404 @@ new Ext.Template( }); 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'
-    },
-    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.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); - } - 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){ - 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>'
-    }]
-});
-
- */ -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, - - targetCls: 'x-table-layout-ct', - - /** - * @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){ - 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)); - 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); - } - - /** - * @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.

+ * @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'
+    },
+    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.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, { +Ext.layout.AccordionLayout = Ext.extend(Ext.layout.FitLayout, { /** - * @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}
-     * 
+ * @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). */ - defaultMargins : {left:0,top:0,right:0,bottom:0}, + fill : true, /** - * @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"

+ * @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. */ - padding : '0', - // documented in subclasses - pack : 'start', + 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, - // private - monitorResize : true, - scrollOffset : 0, - extraCls : 'x-box-item', - targetCls : 'x-box-layout-ct', - innerCls : 'x-box-inner', + type: 'accordion', - constructor : function(config){ - Ext.layout.BoxLayout.superclass.constructor.call(this, config); - if(Ext.isString(this.defaultMargins)){ - this.defaultMargins = this.parseMargins(this.defaultMargins); + 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); }, - // private - isValidParent : function(c, target){ - return c.getPositionEl().dom.parentNode == this.innerCt.dom; + 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 - onLayout : function(ct, target){ - var cs = ct.items.items, len = cs.length, c, i, last = len-1, cm; - - 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); + 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.renderAll(ct, this.innerCt); + 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 - renderItem : function(c){ - if(Ext.isString(c.margins)){ - c.margins = this.parseMargins(c.margins); - }else if(!c.margins){ - c.margins = this.defaultMargins; + 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); } - Ext.layout.BoxLayout.superclass.renderItem.apply(this, arguments); }, - // deprecate - getTargetSize : function(target){ - return (Ext.isIE6 && Ext.isStrict && target.dom == document.body) ? target.getStyleSize() : target.getViewSize(true); + /** + * 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){ + this.setActive(item, true); }, - getItems: function(ct){ - var items = []; - ct.items.each(function(c){ - if(c.isVisible()){ - items.push(c); + // 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); } - }); - return items; + } } }); +Ext.Container.LAYOUTS.accordion = Ext.layout.AccordionLayout; -/** - * @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. +//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.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 +Ext.layout.TableLayout = Ext.extend(Ext.layout.ContainerLayout, { /** - * @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} 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, + + type: 'table', + + targetCls: 'x-table-layout-ct', + /** - * @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). + * @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){ - Ext.layout.VBoxLayout.superclass.onLayout.call(this, ct, target); - - var cs = this.getItems(ct), cm, ch, margin, cl, diff, aw, - size = target.getViewSize(true), - w = size.width, - h = size.height - this.scrollOffset, - l = this.padding.left, t = this.padding.top, - isStart = this.pack == 'start', - stretchWidth = w - (this.padding.left + this.padding.right), - extraHeight = 0, - maxWidth = 0, - totalFlex = 0, - flexHeight = 0, - usedHeight = 0, - idx = 0, - heights = [], - restore = [], - c, - csLen = cs.length; + var cs = ct.items.items, len = cs.length, c, i; - // Do only width calculations and apply those first, as they can affect height - for (i = 0 ; i < csLen; i++) { - c = cs[i]; - cm = c.margins; - margin = cm.top + cm.bottom; - maxWidth = Math.max(maxWidth, c.getWidth() + cm.left + cm.right); - } + if(!this.table){ + target.addClass('x-table-layout-ct'); - 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; + 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); + }, - var availableWidth = Math.max(0, w - this.padding.left - this.padding.right); - // Apply widths - for (i = 0 ; i < csLen; i++) { - c = cs[i]; - 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(isStart && c.flex){ - c.setWidth(); - } - + // 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; + }, - // Do height calculations - for (i = 0 ; i < csLen; i++) { - c = cs[i]; - cm = c.margins; - totalFlex += c.flex || 0; - ch = c.getHeight(); - margin = cm.top + cm.bottom; - extraHeight += ch + margin; - flexHeight += margin + (c.flex ? 0 : ch); - } - extraHeight = h - extraHeight - this.padding.top - this.padding.bottom; - - var availHeight = Math.max(0, h - this.padding.top - this.padding.bottom - flexHeight), - leftOver = availHeight; - for (i = 0 ; i < csLen; i++) { - c = cs[i]; - if(isStart && c.flex){ - ch = Math.floor(availHeight * (c.flex / totalFlex)); - leftOver -= ch; - heights.push(ch); + // 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; } } - if(this.pack == 'center'){ - t += extraHeight ? extraHeight / 2 : 0; - }else if(this.pack == 'end'){ - t += extraHeight; + var td = document.createElement('td'); + if(c.cellId){ + td.id = c.cellId; } - idx = 0; - // Apply heights - for (i = 0 ; i < csLen; i++) { - c = cs[i]; - cm = c.margins; - t += cm.top; - aw = availableWidth; - cl = l + cm.left // default left pos - -// Adjust left pos for centering - if(this.align == 'center'){ - if((diff = availableWidth - (c.getWidth() + cm.left + cm.right)) > 0){ - cl += (diff/2); - aw -= diff; - } - } + 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; + }, - c.setPosition(cl, t); - if(isStart && c.flex){ - ch = Math.max(0, heights[idx++] + (leftOver-- > 0 ? 1 : 0)); - c.setSize(aw, ch); + // 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{ - ch = c.getHeight(); + colIndex++; } - t += ch + cm.bottom; } - } -}); - -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 - /** - * @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). - */ + return [colIndex, rowIndex]; + }, // private - onLayout : function(ct, target){ - Ext.layout.HBoxLayout.superclass.onLayout.call(this, ct, target); + 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); + }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); + } + }, - var cs = this.getItems(ct), cm, cw, margin, ch, diff, - size = target.getViewSize(true), - w = size.width - this.scrollOffset, - h = size.height, - 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; + // 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); + } - 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; - } + /** + * @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, { - 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); + extraCls: 'x-abs-layout-item', + type: 'absolute', - Ext.each(cs, function(c){ - if(isStart && c.flex){ - cw = Math.floor(availWidth * (c.flex / totalFlex)); - leftOver -= cw; - widths.push(cw); - } - }); + 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); + }, - 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; - }); + // private + adjustWidthAnchor : function(value, comp){ + return value ? value - comp.getPosition(true)[0] + this.paddingLeft : value; + }, - idx = 0; - Ext.each(cs, function(c){ - cm = c.margins; - ch = c.getHeight(); - if(isStart && c.flex){ - ch = restore[idx++]; - } - 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'){ - diff = availableHeight - (ch + cm.top + cm.bottom); - ch = t + cm.top + (diff/2); - if(diff > 0){ - c.setPosition(c.x, ch); - } - } - if(isStart && c.flex){ - c.setHeight(ch); - } - } - }, this); + // private + adjustHeightAnchor : function(value, comp){ + return value ? value - comp.getPosition(true)[1] + this.paddingTop : value; } + /** + * @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.onResize(w, h, w, h); - } -}); -Ext.reg('viewport', Ext.Viewport); +Ext.Container.LAYOUTS['absolute'] = Ext.layout.AbsoluteLayout; /** - * @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 + * @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.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 - */ +Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, { /** - * 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 + * @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}, /** - * True if this panel is collapsed. Read-only. - * @type Boolean - * @property collapsed + * @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"

    */ - /** - * @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'
    +    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);
    +        }
    +        
    +        var handler = this.overflowHandler;
    +        
    +        if (typeof handler == 'string') {
    +            handler = {
    +                type: handler
    +            };
    +        }
    +        
    +        var handlerType = 'none';
    +        if (handler && handler.type != undefined) {
    +            handlerType = handler.type;
    +        }
    +        
    +        var constructor = Ext.layout.boxOverflow[handlerType];
    +        if (constructor[this.type]) {
    +            constructor = constructor[this.type];
    +        }
    +        
    +        this.overflowHandler = new constructor(this, handler);
         },
    -    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.

    + * @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 tSize = this.getLayoutTargetSize(), + items = this.getVisibleItems(container), + calcs = this.calculateChildBoxes(items, tSize), + boxes = calcs.boxes, + meta = calcs.meta; + + //invoke the overflow handler, if one is configured + if (tSize.width > 0) { + var handler = this.overflowHandler, + method = meta.tooNarrow ? 'handleOverflow' : 'clearOverflow'; + + var results = handler[method](calcs, tSize); + + if (results) { + if (results.targetSize) { + tSize = results.targetSize; + } + + if (results.recalculate) { + items = this.getVisibleItems(container); + calcs = this.calculateChildBoxes(items, tSize); + boxes = calcs.boxes; + } + } + } + + /** + * @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 = calcs; + + this.updateInnerCtSize(tSize, calcs); + this.updateChildBoxes(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 {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.

    + * 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 {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.

    - */ + * @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: function(tSize, calcs) { + var align = this.align, + padding = this.padding, + width = tSize.width, + height = tSize.height; + + if (this.type == 'hbox') { + var innerCtWidth = width, + innerCtHeight = calcs.meta.maxHeight + padding.top + padding.bottom; + + if (align == 'stretch') { + innerCtHeight = height; + } else if (align == 'middle') { + innerCtHeight = Math.max(height, innerCtHeight); + } + } else { + var innerCtHeight = height, + innerCtWidth = calcs.meta.maxWidth + padding.left + padding.right; + + if (align == 'stretch') { + innerCtWidth = width; + } else if (align == 'center') { + innerCtWidth = Math.max(width, innerCtWidth); + } + } + + this.innerCt.setSize(innerCtWidth || undefined, innerCtHeight || undefined); + }, + /** - * @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.

    + * @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; + }, + /** - * @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.

    + * @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 && c.shouldLayout !== false){ + 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); + }, + /** - * @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.

    + * @private */ + destroy: function() { + Ext.destroy(this.overflowHandler); + + Ext.layout.BoxLayout.superclass.destroy.apply(this, arguments); + } +}); + + + +Ext.ns('Ext.layout.boxOverflow'); + +/** + * @class Ext.layout.boxOverflow.None + * @extends Object + * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout + * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox) + * for its container. + */ + +Ext.layout.boxOverflow.None = Ext.extend(Object, { + constructor: function(layout, config) { + this.layout = layout; + + Ext.apply(this, config || {}); + }, + + handleOverflow: Ext.emptyFn, + + clearOverflow: Ext.emptyFn +}); + + +Ext.layout.boxOverflow.none = Ext.layout.boxOverflow.None; +/** + * @class Ext.layout.boxOverflow.Menu + * @extends Ext.layout.boxOverflow.None + * Description + */ +Ext.layout.boxOverflow.Menu = Ext.extend(Ext.layout.boxOverflow.None, { /** - * @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 afterCls + * @type String + * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers, + * which must always be present at the rightmost edge of the Container */ + afterCls: 'x-strip-right', + /** - * @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.

    + * @property noItemsMenuText + * @type String + * HTML fragment to render into the toolbar overflow menu if there are no items to display */ + noItemsMenuText : '
    (None)
    ', + + constructor: function(layout) { + Ext.layout.boxOverflow.Menu.superclass.constructor.apply(this, arguments); + + /** + * @property menuItems + * @type Array + * Array of all items that are currently hidden and should go into the dropdown menu + */ + this.menuItems = []; + }, + /** - * @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. + * @private + * Creates the beforeCt, innerCt and afterCt elements if they have not already been created + * @param {Ext.Container} container The Container attached to this Layout instance + * @param {Ext.Element} target The target Element */ + createInnerElements: function() { + if (!this.afterCt) { + this.afterCt = this.layout.innerCt.insertSibling({cls: this.afterCls}, 'before'); + } + }, + /** - * @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. + * @private */ + clearOverflow: function(calculations, targetSize) { + var newWidth = targetSize.width + (this.afterCt ? this.afterCt.getWidth() : 0), + items = this.menuItems; + + this.hideTrigger(); + + for (var index = 0, length = items.length; index < length; index++) { + items.pop().component.show(); + } + + return { + targetSize: { + height: targetSize.height, + width : newWidth + } + }; + }, + /** - * @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. + * @private */ + showTrigger: function() { + this.createMenu(); + this.menuTrigger.show(); + }, + /** - * @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. + * @private */ + hideTrigger: function() { + if (this.menuTrigger != undefined) { + this.menuTrigger.hide(); + } + }, + /** - * @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.

    + * @private + * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can. */ - /** - * @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">
    +    beforeMenuShow: function(menu) {
    +        var items = this.menuItems,
    +            len   = items.length,
    +            item,
    +            prev;
     
    -    <div class="x-panel-header"><span class="x-panel-header-text">Title: (frame:false)</span></div>
    +        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].component;
    +            
    +            if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
    +                menu.add('-');
    +            }
    +            
    +            this.addComponentToMenu(menu, item);
    +            prev = item;
    +        }
     
    -    <div class="x-panel-bwrap">
    -        <div class="x-panel-body"><p>html value goes here</p></div>
    -    </div>
    -</div>
    +        // put something so the menu isn't empty if no compatible items found
    +        if (menu.items.length < 1) {
    +            menu.add(this.noItemsMenuText);
    +        }
    +    },
    +    
    +    /**
    +     * @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
    +        });
     
    -// 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>
    +        if (group || component.enableToggle) {
    +            Ext.apply(config, {
    +                group  : group,
    +                checked: component.pressed,
    +                listeners: {
    +                    checkchange: function(item, checked){
    +                        component.toggle(checked);
    +                    }
    +                }
    +            });
    +        }
     
    -    <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>
    +        delete config.ownerCt;
    +        delete config.xtype;
    +        delete config.id;
    +
    +        return config;
    +    },
     
    -        <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. + * @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); + } + } + }, + /** - * @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}. - */ + * @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; + }); + } + }, + /** - * @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}. + * @private + * Creates the overflow trigger and menu used when enableOverflow is set to true and the items + * in the layout are too wide to fit in the space available */ - /** - * @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'
    +    createMenu: function() {
    +        if (!this.menuTrigger) {
    +            this.createInnerElements();
    +            
    +            /**
    +             * @private
    +             * @property menu
    +             * @type Ext.menu.Menu
    +             * The expand menu - holds items for every item that cannot be shown
    +             * because the container is currently not large enough.
    +             */
    +            this.menu = new Ext.menu.Menu({
    +                ownerCt : this.layout.container,
    +                listeners: {
    +                    scope: this,
    +                    beforeshow: this.beforeMenuShow
    +                }
    +            });
     
    -// 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; }
    -
    - */ + /** + * @private + * @property menuTrigger + * @type Ext.Button + * The expand button which triggers the overflow menu to be shown + */ + this.menuTrigger = new Ext.Button({ + iconCls : 'x-toolbar-more-icon', + cls : 'x-toolbar-more', + menu : this.menu, + renderTo: this.afterCt + }); + } + }, + /** - * @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). + * @private */ - /** - * @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
    +    destroy: function() {
    +        Ext.destroy(this.menu, this.menuTrigger);
         }
    -},
    -{
    -    id:'help',
    -    qtip: 'Get Help',
    -    handler: function(event, toolEl, panel){
    -        // whatever
    +});
    +
    +Ext.layout.boxOverflow.menu = Ext.layout.boxOverflow.Menu;
    +
    +
    +/**
    + * @class Ext.layout.boxOverflow.HorizontalMenu
    + * @extends Ext.layout.boxOverflow.Menu
    + * Description
    + */
    +Ext.layout.boxOverflow.HorizontalMenu = Ext.extend(Ext.layout.boxOverflow.Menu, {
    +    
    +    constructor: function() {
    +        Ext.layout.boxOverflow.HorizontalMenu.superclass.constructor.apply(this, arguments);
    +        
    +        var me = this,
    +            layout = me.layout,
    +            origFunction = layout.calculateChildBoxes;
    +        
    +        layout.calculateChildBoxes = function(visibleItems, targetSize) {
    +            var calcs = origFunction.apply(layout, arguments),
    +                meta  = calcs.meta,
    +                items = me.menuItems;
    +            
    +            //calculate the width of the items currently hidden solely because there is not enough space
    +            //to display them
    +            var hiddenWidth = 0;
    +            for (var index = 0, length = items.length; index < length; index++) {
    +                hiddenWidth += items[index].width;
    +            }
    +            
    +            meta.minimumWidth += hiddenWidth;
    +            meta.tooNarrow = meta.minimumWidth > targetSize.width;
    +            
    +            return calcs;
    +        };        
    +    },
    +    
    +    handleOverflow: function(calculations, targetSize) {
    +        this.showTrigger();
    +        
    +        var newWidth    = targetSize.width - this.afterCt.getWidth(),
    +            boxes       = calculations.boxes,
    +            usedWidth   = 0,
    +            recalculate = false;
    +        
    +        //calculate the width of all visible items and any spare width
    +        for (var index = 0, length = boxes.length; index < length; index++) {
    +            usedWidth += boxes[index].width;
    +        }
    +        
    +        var spareWidth = newWidth - usedWidth,
    +            showCount  = 0;
    +        
    +        //see if we can re-show any of the hidden components
    +        for (var index = 0, length = this.menuItems.length; index < length; index++) {
    +            var hidden = this.menuItems[index],
    +                comp   = hidden.component,
    +                width  = hidden.width;
    +            
    +            if (width < spareWidth) {
    +                comp.show();
    +                
    +                spareWidth -= width;
    +                showCount ++;
    +                recalculate = true;
    +            } else {
    +                break;
    +            }
    +        }
    +                
    +        if (recalculate) {
    +            this.menuItems = this.menuItems.slice(showCount);
    +        } else {
    +            for (var i = boxes.length - 1; i >= 0; i--) {
    +                var item  = boxes[i].component,
    +                    right = boxes[i].left + boxes[i].width;
    +
    +                if (right >= newWidth) {
    +                    this.menuItems.unshift({
    +                        component: item,
    +                        width    : boxes[i].width
    +                    });
    +
    +                    item.hide();
    +                } else {
    +                    break;
    +                }
    +            }
    +        }
    +        
    +        if (this.menuItems.length == 0) {
    +            this.hideTrigger();
    +        }
    +        
    +        return {
    +            targetSize: {
    +                height: targetSize.height,
    +                width : newWidth
    +            },
    +            recalculate: recalculate
    +        };
         }
    -}]
    -
    - *

    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;}
    -
    +}); + +Ext.layout.boxOverflow.menu.hbox = Ext.layout.boxOverflow.HorizontalMenu;/** + * @class Ext.layout.boxOverflow.Scroller + * @extends Ext.layout.boxOverflow.None + * Description + */ +Ext.layout.boxOverflow.Scroller = Ext.extend(Ext.layout.boxOverflow.None, { + /** + * @cfg animateScroll + * @type Boolean + * True to animate the scrolling of items within the layout (defaults to true, ignored if enableScroll is false) */ + animateScroll: true, + /** - * @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 scrollIncrement + * @type Number + * The number of pixels to scroll by on scroller click (defaults to 100) */ + scrollIncrement: 100, + /** - * @cfg {Boolean} hideCollapseTool - * true to hide the expand/collapse toggle button when {@link #collapsible} == true, - * false to display it (defaults to false). + * @cfg wheelIncrement + * @type Number + * The number of pixels to increment on mouse wheel scrolling (defaults to 3). */ + wheelIncrement: 3, + /** - * @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 scrollRepeatInterval + * @type Number + * Number of milliseconds between each scroll while a scroller button is held down (defaults to 400) */ - + scrollRepeatInterval: 400, + /** - * @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 scrollDuration + * @type Number + * Number of seconds that each scroll animation lasts (defaults to 0.4) */ + scrollDuration: 0.4, + /** - * @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 beforeCls + * @type String + * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers, + * which must always be present at the leftmost edge of the Container */ + beforeCls: 'x-strip-left', + /** - * @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 afterCls + * @type String + * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers, + * which must always be present at the rightmost edge of the Container */ + afterCls: 'x-strip-right', + /** - * @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 scrollerCls + * @type String + * CSS class added to both scroller elements if enableScroll is used */ + scrollerCls: 'x-strip-scroller', + /** - * @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 beforeScrollerCls + * @type String + * CSS class added to the left scroller element if enableScroll is used */ + beforeScrollerCls: 'x-strip-scroller-left', + /** - * @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);
    +     * @cfg afterScrollerCls
    +     * @type String
    +     * CSS class added to the right scroller element if enableScroll is used
    +     */
    +    afterScrollerCls: 'x-strip-scroller-right',
    +    
    +    /**
    +     * @private
    +     * Sets up an listener to scroll on the layout's innerCt mousewheel event
    +     */
    +    createWheelListener: function() {
    +        this.layout.innerCt.on({
    +            scope     : this,
    +            mousewheel: function(e) {
    +                e.stopEvent();
     
    -//          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());
    +                this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
                 }
    -        },
    -
    -//      Called on the mouseup event.
    -        endDrag : function(e){
    -            this.panel.setPosition(this.x, this.y);
    -        }
    -    }
    -}).show();
    -
    + }); + }, + + /** + * @private + * Most of the heavy lifting is done in the subclasses */ + handleOverflow: function(calculations, targetSize) { + this.createInnerElements(); + this.showScrollers(); + }, + /** - * @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
    -        }
    -    }
    -});
    -
    + * @private */ + clearOverflow: function() { + this.hideScrollers(); + }, + /** - * @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. + * @private + * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already + * present. */ - - + showScrollers: function() { + this.createScrollers(); + + this.beforeScroller.show(); + this.afterScroller.show(); + + this.updateScrollButtons(); + }, + /** - * @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.

    + * @private + * Hides the scroller elements in the beforeCt and afterCt */ - baseCls : 'x-panel', + hideScrollers: function() { + if (this.beforeScroller != undefined) { + this.beforeScroller.hide(); + this.afterScroller.hide(); + } + }, + /** - * @cfg {String} collapsedCls - * A CSS class to add to the panel's element after it has been collapsed (defaults to - * 'x-panel-collapsed'). + * @private + * Creates the clickable scroller elements and places them into the beforeCt and afterCt */ - collapsedCls : 'x-panel-collapsed', + createScrollers: function() { + if (!this.beforeScroller && !this.afterScroller) { + var before = this.beforeCt.createChild({ + cls: String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls) + }); + + var after = this.afterCt.createChild({ + cls: String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls) + }); + + before.addClassOnOver(this.beforeScrollerCls + '-hover'); + after.addClassOnOver(this.afterScrollerCls + '-hover'); + + before.setVisibilityMode(Ext.Element.DISPLAY); + after.setVisibilityMode(Ext.Element.DISPLAY); + + this.beforeRepeater = new Ext.util.ClickRepeater(before, { + interval: this.scrollRepeatInterval, + handler : this.scrollLeft, + scope : this + }); + + this.afterRepeater = new Ext.util.ClickRepeater(after, { + interval: this.scrollRepeatInterval, + handler : this.scrollRight, + scope : this + }); + + /** + * @property beforeScroller + * @type Ext.Element + * The left scroller element. Only created when needed. + */ + this.beforeScroller = before; + + /** + * @property afterScroller + * @type Ext.Element + * The left scroller element. Only created when needed. + */ + this.afterScroller = after; + } + }, + /** - * @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. + * @private */ - maskDisabled : true, + destroy: function() { + Ext.destroy(this.beforeScroller, this.afterScroller, this.beforeRepeater, this.afterRepeater, this.beforeCt, this.afterCt); + }, + /** - * @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). + * @private + * Scrolls left or right by the number of pixels specified + * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left */ - animCollapse : Ext.enableFx, + scrollBy: function(delta, animate) { + this.scrollTo(this.getScrollPosition() + delta, animate); + }, + /** - * @cfg {Boolean} headerAsText - * true to display the panel {@link #title} in the {@link #header}, - * false to hide it (defaults to true). + * @private + * Normalizes an item reference, string id or numerical index into a reference to the item + * @param {Ext.Component|String|Number} item The item reference, id or index + * @return {Ext.Component} The item */ - headerAsText : true, + getItem: function(item) { + if (Ext.isString(item)) { + item = Ext.getCmp(item); + } else if (Ext.isNumber(item)) { + item = this.items[item]; + } + + return item; + }, + /** - * @cfg {String} buttonAlign - * The alignment of any {@link #buttons} added to this panel. Valid values are 'right', - * 'left' and 'center' (defaults to 'right'). + * @private + * @return {Object} Object passed to scrollTo when scrolling */ - buttonAlign : 'right', + getScrollAnim: function() { + return { + duration: this.scrollDuration, + callback: this.updateScrollButtons, + scope : this + }; + }, + /** - * @cfg {Boolean} collapsed - * true to render the panel collapsed, false to render it expanded (defaults to - * false). + * @private + * Enables or disables each scroller button based on the current scroll position */ - collapsed : false, + updateScrollButtons: function() { + if (this.beforeScroller == undefined || this.afterScroller == undefined) { + return; + } + + var beforeMeth = this.atExtremeBefore() ? 'addClass' : 'removeClass', + afterMeth = this.atExtremeAfter() ? 'addClass' : 'removeClass', + beforeCls = this.beforeScrollerCls + '-disabled', + afterCls = this.afterScrollerCls + '-disabled'; + + this.beforeScroller[beforeMeth](beforeCls); + this.afterScroller[afterMeth](afterCls); + this.scrolling = 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). + * @private + * Returns true if the innerCt scroll is already at its left-most point + * @return {Boolean} True if already at furthest left point */ - collapseFirst : true, + atExtremeBefore: function() { + return this.getScrollPosition() === 0; + }, + /** - * @cfg {Number} minButtonWidth - * Minimum width in pixels of all {@link #buttons} in this panel (defaults to 75) + * @private + * Scrolls to the left by the configured amount */ - minButtonWidth : 75, + scrollLeft: function(animate) { + this.scrollBy(-this.scrollIncrement, animate); + }, + /** - * @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). + * @private + * Scrolls to the right by the configured amount */ + scrollRight: function(animate) { + this.scrollBy(this.scrollIncrement, animate); + }, + /** - * @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'. + * Scrolls to the given component. + * @param {String|Number|Ext.Component} item The item to scroll to. Can be a numerical index, component id + * or a reference to the component itself. + * @param {Boolean} animate True to animate the scrolling */ - elements : 'body', + scrollToItem: function(item, animate) { + item = this.getItem(item); + + if (item != undefined) { + var visibility = this.getItemVisibility(item); + + if (!visibility.fullyVisible) { + var box = item.getBox(true, true), + newX = box.x; + + if (visibility.hiddenRight) { + newX -= (this.layout.innerCt.getWidth() - box.width); + } + + this.scrollTo(newX, animate); + } + } + }, + /** - * @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, + * @private + * For a given item in the container, return an object with information on whether the item is visible + * with the current innerCt scroll value. + * @param {Ext.Component} item The item + * @return {Object} Values for fullyVisible, hiddenLeft and hiddenRight + */ + getItemVisibility: function(item) { + var box = this.getItem(item).getBox(true, true), + itemLeft = box.x, + itemRight = box.x + box.width, + scrollLeft = this.getScrollPosition(), + scrollRight = this.layout.innerCt.getWidth() + scrollLeft; + + return { + hiddenLeft : itemLeft < scrollLeft, + hiddenRight : itemRight > scrollRight, + fullyVisible: itemLeft > scrollLeft && itemRight < scrollRight + }; + } +}); + +Ext.layout.boxOverflow.scroller = Ext.layout.boxOverflow.Scroller; + +/** + * @class Ext.layout.boxOverflow.VerticalScroller + * @extends Ext.layout.boxOverflow.Scroller + * Description + */ +Ext.layout.boxOverflow.VerticalScroller = Ext.extend(Ext.layout.boxOverflow.Scroller, { + scrollIncrement: 75, + wheelIncrement : 2, + + handleOverflow: function(calculations, targetSize) { + Ext.layout.boxOverflow.VerticalScroller.superclass.handleOverflow.apply(this, arguments); + + return { + targetSize: { + height: targetSize.height - (this.beforeCt.getHeight() + this.afterCt.getHeight()), + width : targetSize.width + } + }; + }, + /** - * @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. - * + * @private + * Creates the beforeCt and afterCt elements if they have not already been created */ - padding: undefined, + createInnerElements: function() { + var target = this.layout.innerCt; + + //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of + //special items such as scrollers or dropdown menu triggers + if (!this.beforeCt) { + this.beforeCt = target.insertSibling({cls: this.beforeCls}, 'before'); + this.afterCt = target.insertSibling({cls: this.afterCls}, 'after'); - /** @cfg {String} resizeEvent - * The event to listen to for resizing in layouts. Defaults to 'bodyresize'. + this.createWheelListener(); + } + }, + + /** + * @private + * Scrolls to the given position. Performs bounds checking. + * @param {Number} position The position to scroll to. This is constrained. + * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll */ - 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 + scrollTo: function(position, animate) { + var oldPosition = this.getScrollPosition(), + newPosition = position.constrain(0, this.getMaxScrollBottom()); + + if (newPosition != oldPosition && !this.scrolling) { + if (animate == undefined) { + animate = this.animateScroll; + } + + this.layout.innerCt.scrollTo('top', newPosition, animate ? this.getScrollAnim() : false); + + if (animate) { + this.scrolling = true; + } else { + this.scrolling = false; + this.updateScrollButtons(); + } + } }, - // private - collapseDefaults : { - duration : 0.25 + + /** + * Returns the current scroll position of the innerCt element + * @return {Number} The current scroll position + */ + getScrollPosition: function(){ + return parseInt(this.layout.innerCt.dom.scrollTop, 10) || 0; + }, + + /** + * @private + * Returns the maximum value we can scrollTo + * @return {Number} The max scroll value + */ + getMaxScrollBottom: function() { + return this.layout.innerCt.dom.scrollHeight - this.layout.innerCt.getHeight(); }, + + /** + * @private + * Returns true if the innerCt scroll is already at its right-most point + * @return {Boolean} True if already at furthest right point + */ + atExtremeAfter: function() { + return this.getScrollPosition() >= this.getMaxScrollBottom(); + } +}); - // private - initComponent : function(){ - Ext.Panel.superclass.initComponent.call(this); +Ext.layout.boxOverflow.scroller.vbox = Ext.layout.boxOverflow.VerticalScroller; - 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. - */ - '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'; +/** + * @class Ext.layout.boxOverflow.HorizontalScroller + * @extends Ext.layout.boxOverflow.Scroller + * Description + */ +Ext.layout.boxOverflow.HorizontalScroller = Ext.extend(Ext.layout.boxOverflow.Scroller, { + handleOverflow: function(calculations, targetSize) { + Ext.layout.boxOverflow.HorizontalScroller.superclass.handleOverflow.apply(this, arguments); + + return { + targetSize: { + height: targetSize.height, + width : targetSize.width - (this.beforeCt.getWidth() + this.afterCt.getWidth()) + } + }; + }, + + /** + * @private + * Creates the beforeCt and afterCt elements if they have not already been created + */ + createInnerElements: function() { + var target = this.layout.innerCt; + + //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of + //special items such as scrollers or dropdown menu triggers + if (!this.beforeCt) { + this.afterCt = target.insertSibling({cls: this.afterCls}, 'before'); + this.beforeCt = target.insertSibling({cls: this.beforeCls}, 'before'); + + this.createWheelListener(); + } + }, + + /** + * @private + * Scrolls to the given position. Performs bounds checking. + * @param {Number} position The position to scroll to. This is constrained. + * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll + */ + scrollTo: function(position, animate) { + var oldPosition = this.getScrollPosition(), + newPosition = position.constrain(0, this.getMaxScrollRight()); + + if (newPosition != oldPosition && !this.scrolling) { + if (animate == undefined) { + animate = this.animateScroll; + } + + this.layout.innerCt.scrollTo('left', newPosition, animate ? this.getScrollAnim() : false); + + if (animate) { + this.scrolling = true; + } else { + this.scrolling = false; + this.updateScrollButtons(); + } } + }, + + /** + * Returns the current scroll position of the innerCt element + * @return {Number} The current scroll position + */ + getScrollPosition: function(){ + return parseInt(this.layout.innerCt.dom.scrollLeft, 10) || 0; + }, + + /** + * @private + * Returns the maximum value we can scrollTo + * @return {Number} The max scroll value + */ + getMaxScrollRight: function() { + return this.layout.innerCt.dom.scrollWidth - this.layout.innerCt.getWidth(); + }, + + /** + * @private + * Returns true if the innerCt scroll is already at its right-most point + * @return {Boolean} True if already at furthest right point + */ + atExtremeAfter: function() { + return this.getScrollPosition() >= this.getMaxScrollRight(); + } +}); +Ext.layout.boxOverflow.scroller.hbox = Ext.layout.boxOverflow.HorizontalScroller;/** + * @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 - this.toolbars = []; - // shortcuts - if(this.tbar){ - this.elements += ',tbar'; - this.topToolbar = this.createToolbar(this.tbar); - delete this.tbar; + type : 'hbox', - } - if(this.bbar){ - this.elements += ',bbar'; - this.bottomToolbar = this.createToolbar(this.bbar); - delete this.bbar; - } + /** + * @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). + */ - if(this.header === true){ - this.elements += ',header'; - delete this.header; - }else if(this.headerCfg || (this.title && this.header !== false)){ - this.elements += ',header'; - } + /** + * @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', + + nonFlexWidth = 0, + maxHeight = 0, + totalFlex = 0, + desiredWidth = 0, + minimumWidth = 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, + horizMargins, 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 && typeof child.doLayout == 'function'; + + // Static width (numeric) requires no calcs + if (typeof childWidth != 'number') { + + // 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(); + } - if(this.footerCfg || this.footer === true){ - this.elements += ',footer'; - delete this.footer; - } + childSize = child.getSize(); + childWidth = childSize.width; + childHeight = childSize.height; + } + } - if(this.buttons){ - this.fbar = this.buttons; - delete this.buttons; - } - if(this.fbar){ - this.createFbar(this.fbar); - } - if(this.autoLoad){ - this.on('render', this.doAutoLoad, this, {delay:10}); - } - }, + childMargins = child.margins; + horizMargins = childMargins.left + childMargins.right; - // 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 - }; + nonFlexWidth += horizMargins + (childWidth || 0); + desiredWidth += horizMargins + (child.flex ? child.minWidth || 0 : childWidth); + minimumWidth += horizMargins + (child.minWidth || childWidth || 0); + + // Max height for align - force layout of non-laid out subcontainers without a numeric height + if (typeof childHeight != 'number') { + if (canLayout) { + child.doLayout(); + } + childHeight = child.getHeight(); } - }); - //@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'); - result.ownerCt = this; - result.bufferResize = false; - this.toolbars.push(result); - return result; - }, + maxHeight = Math.max(maxHeight, childHeight + childMargins.top + childMargins.bottom); - // private - createElement : function(name, pnode){ - if(this[name]){ - pnode.appendChild(this[name].dom); - return; + //cache the size of each child component. Don't set height or width to 0, keep undefined instead + boxes.push({ + component: child, + height : childHeight || undefined, + width : childWidth || undefined + }); } - - 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']); + + var shortfall = desiredWidth - width, + tooNarrow = minimumWidth > width; + + //the width available to the flexed items + var availableWidth = Math.max(0, width - nonFlexWidth - paddingHoriz); + + if (tooNarrow) { + for (i = 0; i < visibleCount; i++) { + boxes[i].width = visibleItems[i].minWidth || visibleItems[i].width || boxes[i].width; } - } - }, + } else { + //all flexed items should be sized to their minimum width, other items should be shrunk down until + //the shortfall has been accounted for + if (shortfall > 0) { + var minWidths = []; + + /** + * When we have a shortfall but are not tooNarrow, we need to shrink the width of each non-flexed item. + * Flexed items are immediately reduced to their minWidth and anything already at minWidth is ignored. + * The remaining items are collected into the minWidths array, which is later used to distribute the shortfall. + */ + for (var index = 0, length = visibleCount; index < length; index++) { + var item = visibleItems[index], + minWidth = item.minWidth || 0; + + //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all + //shrunk to their minWidth because they're flexible and should be the first to lose width + if (item.flex) { + boxes[index].width = minWidth; + } else { + minWidths.push({ + minWidth : minWidth, + available: boxes[index].width - minWidth, + index : index + }); + } + } + + //sort by descending amount of width remaining before minWidth is reached + minWidths.sort(function(a, b) { + return a.available > b.available ? 1 : -1; + }); + + /* + * Distribute the shortfall (difference between total desired with of all items and actual width available) + * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the + * smallest difference between their width and minWidth first, so that if reducing the width by the average + * amount would make that item less than its minWidth, we carry the remainder over to the next item. + */ + for (var i = 0, length = minWidths.length; i < length; i++) { + var itemIndex = minWidths[i].index; + + if (itemIndex == undefined) { + continue; + } + + var item = visibleItems[itemIndex], + box = boxes[itemIndex], + oldWidth = box.width, + minWidth = item.minWidth, + newWidth = Math.max(minWidth, oldWidth - Math.ceil(shortfall / (length - i))), + reduction = oldWidth - newWidth; + + boxes[itemIndex].width = newWidth; + shortfall -= reduction; + } + } else { + //temporary variables used in the flex width calculations below + var remainingWidth = availableWidth, + remainingFlex = totalFlex; - // private - onRender : function(ct, position){ - Ext.Panel.superclass.onRender.call(this, ct, position); - this.createClasses(); + //calculate the widths of each flexed item + for (i = 0; i < visibleCount; i++) { + child = visibleItems[i]; + calcs = boxes[i]; - var el = this.el, - d = el.dom, - bw, - ts; + childMargins = child.margins; + vertMargins = childMargins.top + childMargins.bottom; + if (isStart && child.flex && !child.width) { + flexedWidth = Math.ceil((child.flex / remainingFlex) * remainingWidth); + remainingWidth -= flexedWidth; + remainingFlex -= child.flex; - 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'); + calcs.width = flexedWidth; + calcs.dirtySize = true; + } + } + } } - if(this.cls){ - el.addClass(this.cls); + + if (isCenter) { + leftOffset += availableWidth / 2; + } else if (isEnd) { + leftOffset += availableWidth; } - - if(this.buttons){ - this.elements += ',footer'; + + //finally, calculate the left and top position of each item + for (i = 0; i < visibleCount; i++) { + child = visibleItems[i]; + calcs = boxes[i]; + + childMargins = child.margins; + leftOffset += childMargins.left; + vertMargins = childMargins.top + childMargins.bottom; + + 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; } - // This block allows for maximum flexibility and performance when using existing markup + return { + boxes: boxes, + meta : { + maxHeight : maxHeight, + nonFlexWidth: nonFlexWidth, + desiredWidth: desiredWidth, + minimumWidth: minimumWidth, + shortfall : desiredWidth - width, + tooNarrow : tooNarrow + } + }; + } +}); - // framing requires special markup - if(this.frame){ - el.insertHtml('afterBegin', String.format(Ext.Element.boxMarkup, this.baseCls)); +Ext.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout;/** + * @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', - this.createElement('header', d.firstChild.firstChild.firstChild); - this.createElement('bwrap', d); + /** + * @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
      • + *
      + */ - // 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); + /** + * @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). + */ - 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); + /** + * @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, + desiredHeight= 0, + minimumHeight= 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, + horizMargins, vertMargins, 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 && typeof child.doLayout == 'function'; + + // Static height (numeric) requires no calcs + if (typeof childHeight != 'number') { + + // 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(); + } - if(!this.footer){ - this.bwrap.dom.lastChild.className += ' x-panel-nofooter'; + childSize = child.getSize(); + childWidth = childSize.width; + childHeight = childSize.height; + } } - /* - * 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); + + childMargins = child.margins; + vertMargins = childMargins.top + childMargins.bottom; - // 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); + nonFlexHeight += vertMargins + (childHeight || 0); + desiredHeight += vertMargins + (child.flex ? child.minHeight || 0 : childHeight); + minimumHeight += vertMargins + (child.minHeight || childHeight || 0); - if(!this.header){ - this.body.addClass(this.bodyCls + '-noheader'); - if(this.tbar){ - this.tbar.addClass(this.tbarCls + '-noheader'); + // Max width for align - force layout of non-layed out subcontainers without a numeric width + if (typeof childWidth != 'number') { + if (canLayout) { + child.doLayout(); } + childWidth = child.getWidth(); } - } - if(Ext.isDefined(this.padding)){ - this.body.setStyle('padding', this.body.addUnits(this.padding)); + 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 + }); } + + var shortfall = desiredHeight - height, + tooNarrow = minimumHeight > height; - 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'); + //the height available to the flexed items + var availableHeight = Math.max(0, (height - nonFlexHeight - paddingVert)); + + if (tooNarrow) { + for (i = 0, length = visibleCount; i < length; i++) { + boxes[i].height = visibleItems[i].minHeight || visibleItems[i].height || boxes[i].height; } - } + } else { + //all flexed items should be sized to their minimum width, other items should be shrunk down until + //the shortfall has been accounted for + if (shortfall > 0) { + var minHeights = []; - if(this.bodyBorder === false){ - this.body.addClass(this.bodyCls + '-noborder'); - } + /** + * When we have a shortfall but are not tooNarrow, we need to shrink the height of each non-flexed item. + * Flexed items are immediately reduced to their minHeight and anything already at minHeight is ignored. + * The remaining items are collected into the minHeights array, which is later used to distribute the shortfall. + */ + for (var index = 0, length = visibleCount; index < length; index++) { + var item = visibleItems[index], + minHeight = item.minHeight || 0; + + //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all + //shrunk to their minHeight because they're flexible and should be the first to lose height + if (item.flex) { + boxes[index].height = minHeight; + } else { + minHeights.push({ + minHeight: minHeight, + available: boxes[index].height - minHeight, + index : index + }); + } + } - this.bwrap.enableDisplayMode('block'); + //sort by descending minHeight value + minHeights.sort(function(a, b) { + return a.available > b.available ? 1 : -1; + }); - if(this.header){ - this.header.unselectable(); + /* + * Distribute the shortfall (difference between total desired with of all items and actual height available) + * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the + * smallest difference between their height and minHeight first, so that if reducing the height by the average + * amount would make that item less than its minHeight, we carry the remainder over to the next item. + */ + for (var i = 0, length = minHeights.length; i < length; i++) { + var itemIndex = minHeights[i].index; - // for tools, we need to wrap any existing header markup - if(this.headerAsText){ - this.header.dom.innerHTML = - ''+this.header.dom.innerHTML+''; + if (itemIndex == undefined) { + continue; + } - if(this.iconCls){ - this.setIconClass(this.iconCls); + var item = visibleItems[itemIndex], + box = boxes[itemIndex], + oldHeight = box.height, + minHeight = item.minHeight, + newHeight = Math.max(minHeight, oldHeight - Math.ceil(shortfall / (length - i))), + reduction = oldHeight - newHeight; + + boxes[itemIndex].height = newHeight; + shortfall -= reduction; } - } - } + } else { + //temporary variables used in the flex height calculations below + var remainingHeight = availableHeight, + remainingFlex = totalFlex; + + //calculate the height of each flexed item + for (i = 0; i < visibleCount; i++) { + child = visibleItems[i]; + calcs = boxes[i]; - if(this.floating){ - this.makeFloating(this.floating); - } + childMargins = child.margins; + horizMargins = childMargins.left + childMargins.right; - 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); - } - if(this.fbar){ - this.footer.addClass('x-panel-btns'); - this.fbar.render(this.footer); - this.footer.createChild({cls:'x-clear'}); - } + if (isStart && child.flex && !child.height) { + flexedHeight = Math.ceil((child.flex / remainingFlex) * remainingHeight); + remainingHeight -= flexedHeight; + remainingFlex -= child.flex; - if(this.tbar && this.topToolbar){ - this.topToolbar.render(this.tbar); + calcs.height = flexedHeight; + calcs.dirtySize = true; + } + } + } } - if(this.bbar && this.bottomToolbar){ - this.bottomToolbar.render(this.bbar); + if (isCenter) { + topOffset += availableHeight / 2; + } else if (isEnd) { + topOffset += availableHeight; } - }, - /** - * 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{ - Ext.DomHelper.insertBefore(hd.dom.firstChild, { - tag:'img', src: Ext.BLANK_IMAGE_URL, cls:'x-panel-inline-icon '+this.iconCls - }); - } + //finally, calculate the left and top position of each item + for (i = 0; i < visibleCount; i++) { + child = visibleItems[i]; + calcs = boxes[i]; + + childMargins = child.margins; + topOffset += childMargins.top; + horizMargins = childMargins.left + childMargins.right; + + + 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; } - this.fireEvent('iconchange', this, cls, old); - }, + + return { + boxes: boxes, + meta : { + maxWidth : maxWidth, + nonFlexHeight: nonFlexHeight, + desiredHeight: desiredHeight, + minimumHeight: minimumHeight, + shortfall : desiredHeight - height, + tooNarrow : tooNarrow + } + }; + } +}); - // 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); - }, +Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout; +/** + * @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', /** - * Returns the {@link Ext.Toolbar toolbar} from the top ({@link #tbar}) section of the panel. - * @return {Ext.Toolbar} The toolbar + * @property triggerWidth + * @type Number + * The width allocated for the menu trigger at the extreme right end of the Toolbar */ - getTopToolbar : function(){ - return this.topToolbar; - }, + triggerWidth: 18, /** - * Returns the {@link Ext.Toolbar toolbar} from the bottom ({@link #bbar}) section of the panel. - * @return {Ext.Toolbar} The toolbar + * @property noItemsMenuText + * @type String + * HTML fragment to render into the toolbar overflow menu if there are no items to display */ - getBottomToolbar : function(){ - return this.bottomToolbar; - }, + noItemsMenuText : '
      (None)
      ', /** - * 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 + * @private + * @property lastOverflow + * @type Boolean + * Used internally to record whether the last layout caused an overflow or not */ - addButton : function(config, handler, scope){ - if(!this.fbar){ - this.createFbar([]); + 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 = []; + } } - if(handler){ - if(Ext.isString(config)){ - config = {text: config}; + + 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)); + this.configureItem(c); + } 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); + } } - config = Ext.apply({ - handler: handler, - scope: scope - }, config) } - return this.fbar.add(config); + + //strip extra empty cells + this.cleanup(this.leftTr); + this.cleanup(this.rightTr); + this.cleanup(this.extrasTr); + this.fitToSize(target); }, - // private - addTool : function(){ - if(!this.rendered){ - if(!this.tools){ - this.tools = []; + /** + * @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); } - Ext.each(arguments, function(arg){ - this.tools.push(arg) - }, this); - return; } - // nowhere to render tools! - if(!this[this.toolTarget]){ + }, + + /** + * @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; } - 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; + + 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); } } - t.addClassOnOver(overCls); } } - }, - onLayout : function(shallow, force){ - if(this.hasLayout && this.toolbars.length > 0){ - Ext.each(this.toolbars, function(tb){ - tb.doLayout(undefined, force); - }); - this.syncHeight(); + //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; + } } }, - syncHeight : function(){ - var h = this.toolbarHeight, - bd = this.body, - lsh = this.lastSize.height, - sz; + /** + * @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(this.autoHeight || !Ext.isDefined(lsh) || lsh == 'auto'){ - return; + 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; - if(h != this.getToolbarHeight()){ - h = Math.max(0, this.adjustBodyHeight(lsh - this.getFrameHeight())); - bd.setHeight(h); - sz = bd.getSize(); - this.toolbarHeight = this.getToolbarHeight(); - this.onBodyResize(sz.width, sz.height); - } + return config; }, - // private - onShow : function(){ - if(this.floating){ - return this.el.show(); + /** + * @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); + } } - Ext.Panel.superclass.onShow.call(this); }, - // private - onHide : function(){ - if(this.floating){ - return this.el.hide(); + /** + * @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; + }); } - 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 + * 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); }; - }, - // private - afterRender : function(){ - if(this.floating && !this.hidden){ - this.el.show(); - } - if(this.title){ - this.setTitle(this.title); - } - if(this.collapsed){ - this.collapsed = false; - this.collapse(false); + 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; + } } - Ext.Panel.superclass.afterRender.call(this); // do sizing calcs last - this.initEvents(); - }, - // private - getKeyMap : function(){ - if(!this.keyMap){ - this.keyMap = new Ext.KeyMap(this.el, this.keys); + // put something so the menu isn't empty if no compatible items found + if (menu.items.length < 1) { + menu.add(this.noItemsMenuText); } - 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); - if(!this.ownerCt){ - this.syncHeight(); - } - } - - }, + /** + * @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 - 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 + * @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 + }); - // private - beforeEffect : function(anim){ - if(this.floating){ - this.el.beforeAction(); - } - if(anim !== false){ - this.el.addClass('x-panel-animated'); + var td = this.insertCell(this.more, this.extrasTr, 100); + this.more.render(td); } }, - // private + 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( + '
    • ', + '', + '{altText}', + '', + '
    • ' + ); + } + + 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); + }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, + canHaveIcon = !(isMenuItem || c instanceof Ext.menu.Separator); + + return { + isMenuItem: isMenuItem, + needsIcon: canHaveIcon && (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 ', + altText: c.altText || '' + }; + }, + + // 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', alt: '', 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(); - if(anim !== false){ - this.el.removeClass('x-panel-animated'); + 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; }, - // 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); - }; + // 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 Ext.applyIf(o, a); + return w; }, /** - * 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 + * 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 */ - collapse : function(animate){ - if(this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforecollapse', this, animate) === false){ - return; + 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); + + 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); } - var doAnim = animate === true || (animate !== false && this.animCollapse); - this.beforeEffect(doAnim); - this.onCollapse(doAnim, animate); + 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 + */ + getInnerWidth : function(){ + return this.getSize().width - this.getFrameWidth(); + }, + + /** + * 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; + }, + + /** + *

        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; + }, + + /** + * Get the {@link Ext.Updater} for this panel. Enables you to perform Ajax updates of this panel's body. + * @return {Ext.Updater} The Updater + */ + getUpdater : function(){ + return this.body.getUpdater(); + }, + + /** + * 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; }, // private - onCollapse : function(doAnim, animArg){ - if(doAnim){ - this[this.collapseEl].slideOut(this.slideAnchor, - Ext.apply(this.createEffect(animArg||true, this.afterCollapse, this), - this.collapseDefaults)); + 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{ - this[this.collapseEl].hide(); - this.afterCollapse(false); + 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); } }, // private - afterCollapse : function(anim){ - this.collapsed = true; - this.el.addClass(this.collapsedCls); - this.afterEffect(anim); - this.fireEvent('collapse', this); + doAutoLoad : function(){ + var u = this.body.getUpdater(); + if(this.renderer){ + u.setRenderer(this.renderer); + } + u.update(Ext.isObject(this.autoLoad) ? this.autoLoad : {url: this.autoLoad}); }, /** - * 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 + * Retrieve a tool by id. + * @param {String} id + * @return {Object} tool */ - 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; + 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/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 {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 {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 {Boolean} hideEl + * False to keep the bound element visible while the editor is displayed (defaults to true) + */ + /** + * @cfg {Mixed} value + * The data value of the underlying field (defaults to "") + */ + value : "", + /** + * @cfg {String} alignment + * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "c-c?"). + */ + alignment: "c-c?", + /** + * @cfg {Array} offsets + * The offsets to use when aligning (see {@link Ext.Element#alignTo} for more details. Defaults to [0, 0]. + */ + offsets: [0, 0], + /** + * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop" + * for bottom-right shadow (defaults to "frame") + */ + shadow : "frame", + /** + * @cfg {Boolean} constrain True to constrain the editor to the viewport + */ + constrain : false, + /** + * @cfg {Boolean} swallowKeys Handle the keydown/keypress events so they don't propagate (defaults to true) + */ + swallowKeys : true, + /** + * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed. Defaults to true. + */ + completeOnEnter : true, + /** + * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed. Defaults to true. + */ + cancelOnEsc : true, + /** + * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false) + */ + updateEl : false, + + initComponent : function(){ + Ext.Editor.superclass.initComponent.call(this); + 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 + */ + "beforestartedit", + /** + * @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 + */ + "startedit", + /** + * @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 + */ + "beforecomplete", + /** + * @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 + */ + "complete", + /** + * @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 + */ + "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" + ); }, // 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(false); + 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 + ]); } }, // private - afterExpand : function(anim){ - this.collapsed = false; - this.afterEffect(anim); - if(Ext.isDefined(this.deferLayout)){ - this.doLayout(true); + 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(); + } + if(field.triggerBlur){ + field.triggerBlur(); + } } - this.fireEvent('expand', this); + this.fireEvent('specialkey', field, e); }, /** - * 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 + * 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. */ - toggleCollapse : function(animate){ - this[this.collapsed ? 'expand' : 'collapse'](animate); - return this; - }, - - // private - onDisable : function(){ - if(this.rendered && this.maskDisabled){ - this.el.mask(); + startEdit : function(el, value){ + if(this.editing){ + this.completeEdit(); } - Ext.Panel.superclass.onDisable.call(this); - }, - - // private - onEnable : function(){ - if(this.rendered && this.maskDisabled){ - this.el.unmask(); + 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(); } - Ext.Panel.superclass.onEnable.call(this); }, // private - onResize : function(w, h){ - 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, this.adjustBodyHeight(h - this.getFrameHeight())); - this.body.setHeight(h); - }else if(h == 'auto'){ - this.body.setHeight(h); - } + doAutoSize : function(){ + if(this.autoSize){ + var sz = this.boundEl.getSize(), + fs = this.field.getSize(); - 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, {single:true}); - } + 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); } - this.onBodyResize(w, h); } - this.syncShadow(); - Ext.Panel.superclass.onResize.call(this); - }, - - // 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); + /** + * 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(); } - return h; - }, - - // private - 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 + * 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. */ - 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'); + realign : function(autoSize){ + if(autoSize === true){ + this.doAutoSize(); } - return w; + this.el.alignTo(this.boundEl, this.alignment, this.offsets); }, /** - * 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 + * 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) */ - 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); - - 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); + completeEdit : function(remainVisible){ + if(!this.editing){ + return; + } + // 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); + } + 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); } - 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 - */ - getInnerWidth : function(){ - return this.getSize().width - this.getFrameWidth(); + // private + 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); }, /** - * 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 + * 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) */ - getInnerHeight : function(){ - return this.getSize().height - this.getFrameHeight(); + cancelEdit : function(remainVisible){ + if(this.editing){ + var v = this.getValue(); + this.setValue(this.startValue); + this.hideEdit(remainVisible); + this.fireEvent("canceledit", this, v, this.startValue); + } }, // private - syncShadow : function(){ - if(this.floating){ - this.el.sync(true); + hideEdit: function(remainVisible){ + if(remainVisible !== true){ + this.editing = false; + this.hide(); } }, // private - getLayoutTarget : function(){ - return this.body; + 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(); + } }, // private - getContentTarget : function(){ - return this.body; + 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 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 + * Sets the data value of the editor + * @param {Mixed} value Any valid value supported by the underlying field */ - 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; + setValue : function(v){ + this.field.setValue(v); }, /** - * Get the {@link Ext.Updater} for this panel. Enables you to perform Ajax updates of this panel's body. - * @return {Ext.Updater} The Updater + * Gets the data value of the editor + * @return {Mixed} The data value */ - getUpdater : function(){ - return this.body.getUpdater(); + getValue : function(){ + return this.field.getValue(); }, - /** - * 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
        +    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');
        +
        +cp.on('select', function(palette, selColor){
        +    // do something with selColor
         });
         
        - * 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 + * @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') */ - load : function(){ - var um = this.body.getUpdater(); - um.update.apply(um, arguments); - return this; - }, - + 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 - 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(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.toolbars, - this.tbar, - this.bbar, - this.body, - this.mc, - this.bwrap - ); - if (this.fbar) { - Ext.destroy( - this.fbar, - this.fbar.el - ); - } - }else{ - Ext.destroy( - this.topToolbar, - this.bottomToolbar - ); - } - }, + 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, + + /** + *

        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
        +
        + +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' + ], + /** + * @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 - 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'; + 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.handler){ + this.on('select', this.handler, this.scope, true); + } }, // 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); + 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(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); + }, + + // private + afterRender : function(){ + Ext.ColorPalette.superclass.afterRender.call(this); + if(this.value){ + var s = this.value; + this.value = null; + this.select(s, true); } }, // private - doAutoLoad : function(){ - var u = this.body.getUpdater(); - if(this.renderer){ - u.setRenderer(this.renderer); + handleClick : function(e, t){ + e.preventDefault(); + if(!this.disabled){ + var c = t.className.match(/(?:^|\s)color-(.{6})(?:\s|$)/)[1]; + this.select(c.toUpperCase()); } - u.update(Ext.isObject(this.autoLoad) ? this.autoLoad : {url: this.autoLoad}); }, /** - * Retrieve a tool by id. - * @param {String} id - * @return {Object} tool + * 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. */ - getTool : function(id) { - return this.tools[id]; + 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'); + } + el.child('a.color-'+color).addClass('x-color-palette-sel'); + this.value = color; + if(suppressEvent !== true){ + this.fireEvent('select', this, color); + } + } } -/** - * @cfg {String} autoEl @hide - */ + /** + * @cfg {String} autoEl @hide + */ }); -Ext.reg('panel', Ext.Panel); -/** - * @class Ext.Editor +Ext.reg('colorpalette', Ext.ColorPalette);/** + * @class Ext.DatePicker * @extends Ext.Component - * A base editor field that handles displaying/hiding on demand and has some built-in sizing and event handling logic. + *

        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 Editor + * Create a new DatePicker * @param {Object} config The config object - * @xtype editor + * @xtype datepicker */ -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, { +Ext.DatePicker = Ext.extend(Ext.BoxComponent, { /** - * @cfg {Ext.form.Field} field - * The Field object (or descendant) or config object for field - */ + * @cfg {String} todayText + * The text to display on the button that selects the current date (defaults to 'Today') + */ + todayText : 'Today', /** - * @cfg {Boolean} allowBlur - * True to {@link #completeEdit complete the editing process} if in edit mode when the - * field is blurred. Defaults to false. + * @cfg {String} okText + * The text to display on the ok button (defaults to ' OK ' to give the user extra clicking room) */ + okText : ' OK ', /** - * @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} cancelText + * The text to display on the cancel button (defaults to 'Cancel') */ + cancelText : 'Cancel', /** - * @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 {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 {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 {Object} scope + * The scope (this reference) in which the {@link #handler} + * function will be called. Defaults to this DatePicker instance. */ /** - * @cfg {Boolean} hideEl - * False to keep the bound element visible while the editor is displayed (defaults to true) + * @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 {Mixed} value - * The data value of the underlying field (defaults to "") + * @cfg {String} minText + * The error text to display if the minDate validation fails (defaults to 'This date is before the minimum date') */ - value : "", + minText : 'This date is before the minimum date', /** - * @cfg {String} alignment - * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "c-c?"). + * @cfg {String} maxText + * The error text to display if the maxDate validation fails (defaults to 'This date is after the maximum date') */ - alignment: "c-c?", + maxText : 'This date is after the maximum date', /** - * @cfg {Array} offsets - * The offsets to use when aligning (see {@link Ext.Element#alignTo} for more details. Defaults to [0, 0]. + * @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'). */ - offsets: [0, 0], + 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 + // Set by other components to stop the picker focus being updated when the value changes. + focusOnSelect: true, + + // 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 + + // private + initComponent : function(){ + Ext.DatePicker.superclass.initComponent.call(this); + + this.value = this.value ? + this.value.clearTime(true) : new Date().clearTime(); + + this.addEvents( + /** + * @event select + * Fires when a date is selected + * @param {DatePicker} this DatePicker + * @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 + ')'); + } + }, + /** - * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop" - * for bottom-right shadow (defaults to "frame") + * 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. */ - shadow : "frame", + setDisabledDates : function(dd){ + if(Ext.isArray(dd)){ + this.disabledDates = dd; + this.disabledDatesRE = null; + }else{ + this.disabledDatesRE = dd; + } + this.initDisabledDays(); + this.update(this.value, true); + }, + /** - * @cfg {Boolean} constrain True to constrain the editor to the viewport + * 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. */ - constrain : false, + setDisabledDays : function(dd){ + this.disabledDays = dd; + this.update(this.value, true); + }, + /** - * @cfg {Boolean} swallowKeys Handle the keydown/keypress events so they don't propagate (defaults to true) + * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker. + * @param {Date} value The minimum date that can be selected */ - swallowKeys : true, + setMinDate : function(dt){ + this.minDate = dt; + this.update(this.value, true); + }, + /** - * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed. Defaults to true. + * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker. + * @param {Date} value The maximum date that can be selected */ - completeOnEnter : true, + setMaxDate : function(dt){ + this.maxDate = dt; + this.update(this.value, true); + }, + /** - * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed. Defaults to true. + * Sets the value of the date field + * @param {Date} value The date to set */ - cancelOnEsc : true, + setValue : function(value){ + this.value = value.clearTime(true); + this.update(this.value); + }, + /** - * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false) + * Gets the current selected value of the date field + * @return {Date} The selected date */ - updateEl : false, + getValue : function(){ + return this.value; + }, - initComponent : function(){ - Ext.Editor.superclass.initComponent.call(this); - 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 - */ - "beforestartedit", - /** - * @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 - */ - "startedit", - /** - * @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 - */ - "beforecomplete", - /** - * @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 - */ - "complete", - /** - * @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 - */ - "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 + // private + focus : function(){ + 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. */ - "specialkey" - ); + Ext.each([].concat(this.textNodes, this.el.query('th span')), function(el){ + Ext.fly(el).repaint(); + }); + } }, // 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 + 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 }); - 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 + + 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) }); - 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 - ]); + 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 - 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(); - } - if(field.triggerBlur){ - field.triggerBlur(); + createMonthPicker : function(){ + if(!this.monthPicker.dom.firstChild){ + var buf = ['']; + for(var i = 0; i < 6; i++){ + buf.push( + '', + '', + i === 0 ? + '' : + '' + ); } - } - this.fireEvent('specialkey', field, e); - }, + buf.push( + '', + '
      ', Date.getShortMonthName(i), '', Date.getShortMonthName(i + 6), '
      ' + ); + this.monthPicker.update(buf.join('')); - /** - * 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(); - } - 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(); + 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 - doAutoSize : function(){ - if(this.autoSize){ - var sz = this.boundEl.getSize(), - fs = this.field.getSize(); + showMonthPicker : function(){ + if(!this.disabled){ + this.createMonthPicker(); + var size = this.el.getSize(); + this.monthPicker.setSize(size); + this.monthPicker.child('table').setSize(size); - 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); - } + 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}); } }, - /** - * 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){ - if(Ext.isGecko2 || Ext.isOpera){ - // prevent layer scrollbars - this.el.setSize(w, h); + // 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.el.sync(); + this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel'); } }, - /** - * 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 + updateMPMonth : function(sm){ + this.mpMonths.each(function(m, a, i){ + m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel'); + }); }, - /** - * 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; + // 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(); } - var v = this.getValue(); - if(!this.field.isValid()){ - if(this.revertInvalid !== false){ - this.cancelEdit(remainVisible); + 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(); } - return; + this.update(d); + this.hideMonthPicker(); } - if(String(v) === String(this.startValue) && this.ignoreNoChange){ - this.hideEdit(remainVisible); - return; + 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(); } - 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); + 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 - onShow : function(){ - this.el.show(); - if(this.hideEl !== false){ - this.boundEl.hide(); + hideMonthPicker : function(disableAnim){ + if(this.monthPicker){ + if(disableAnim === true){ + this.monthPicker.hide(); + }else{ + this.monthPicker.slideOut('t', {duration:0.2}); + } } - this.field.show().focus(false, true); - this.fireEvent("startedit", this.boundEl, this.startValue); }, - /** - * 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); - } + // private + showPrevMonth : function(e){ + this.update(this.activeDate.add('mo', -1)); }, - + // private - hideEdit: function(remainVisible){ - if(remainVisible !== true){ - this.editing = false; - this.hide(); - } + showNextMonth : function(e){ + this.update(this.activeDate.add('mo', 1)); }, // private - onBlur : function(){ - if(this.allowBlur !== true && this.editing){ - this.completeEdit(); - } + showPrevYear : function(){ + this.update(this.activeDate.add('y', -1)); }, // private - onHide : function(){ - if(this.editing){ - this.completeEdit(); - return; - } - this.field.blur(); - if(this.field.collapse){ - this.field.collapse(); + 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(); + } } - this.el.hide(); - if(this.hideEl !== false){ - this.boundEl.show(); + }, + + // private + 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); } }, - /** - * 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); + // private + selectToday : function(){ + if(this.todayBtn && !this.todayBtn.disabled){ + this.setValue(new Date().clearTime()); + this.fireEvent('select', this, this.value); + } }, - /** - * Gets the data value of the editor - * @return {Mixed} The data value - */ - getValue : function(){ - return this.field.getValue(); + // private + 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); + } + } + // 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, + 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]); + } + } + } }, - beforeDestroy : function(){ - Ext.destroyMembers(this, 'field'); - - delete this.parentEl; - delete this.boundEl; + // private + 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; + } } -}); -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
      +    /**
      +     * @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 ColorPalette + * Create a new LoadMask + * @param {Mixed} el The element or DOM node, or its id * @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', +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 {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. + * @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. */ - 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'. + * @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. */ - 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 + * @cfg {String} msg + * The text to display in a centered loading message box (defaults to 'Loading...') */ - allowReselect : false, - + msg : 'Loading...', /** - *

      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
      -
      - -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 + * @cfg {String} msgCls + * The CSS class to apply to the loading message element (defaults to "x-mask-loading") */ - 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' - ], + msgCls : 'x-mask-loading', /** - * @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).
      • - *
      + * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false) + * @type Boolean */ + disabled: false, + /** - * @cfg {Object} scope - * The scope (this reference) in which the {@link #handler} - * function will be called. Defaults to this ColorPalette instance. + * Disables the mask to prevent it from being displayed */ - - // 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.handler){ - this.on('select', this.handler, this.scope, true); - } + disable : function(){ + this.disabled = true; }, - // 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}); - } + /** + * Enables the mask so that it can be displayed + */ + enable : function(){ + this.disabled = false; }, // private - afterRender : function(){ - Ext.ColorPalette.superclass.afterRender.call(this); - if(this.value){ - var s = this.value; - this.value = null; - this.select(s); - } + onLoad : function(){ + this.el.unmask(this.removeMask); }, // private - handleClick : function(e, t){ - e.preventDefault(); + onBeforeLoad : function(){ if(!this.disabled){ - var c = t.className.match(/(?:^|\s)color-(.{6})(?:\s|$)/)[1]; - this.select(c.toUpperCase()); + this.el.mask(this.msg, this.msgCls); } }, /** - * 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) + * Show this LoadMask over the configured Element. */ - 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'); - } - el.child('a.color-'+color).addClass('x-color-palette-sel'); - this.value = color; - this.fireEvent('select', this, color); - } - } + show: function(){ + this.onBeforeLoad(); + }, /** - * @cfg {String} autoEl @hide + * Hide this LoadMask. */ -}); -Ext.reg('colorpalette', Ext.ColorPalette); + hide: function(){ + this.onLoad(); + }, + + // 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); + } + } +};Ext.ns('Ext.slider'); + /** - * @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 + * @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.DatePicker = Ext.extend(Ext.BoxComponent, { +Ext.slider.Thumb = Ext.extend(Object, { + /** - * @cfg {String} todayText - * The text to display on the button that selects the current date (defaults to 'Today') + * True while the thumb is in a drag operation + * @type Boolean */ - todayText : 'Today', + dragging: false, + /** - * @cfg {String} okText - * The text to display on the ok button (defaults to ' OK ' to give the user extra clicking room) + * @constructor + * @cfg {Ext.slider.MultiSlider} slider The Slider to render to (required) */ - okText : ' OK ', + 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); + } + }, + /** - * @cfg {String} cancelText - * The text to display on the cancel button (defaults to 'Cancel') + * Renders the thumb into a slider */ - cancelText : 'Cancel', + render: function() { + this.el = this.slider.innerEl.insertFirst({cls: this.cls}); + + this.initEvents(); + }, + /** - * @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.
      • - *
      + * Enables the thumb if it is currently disabled */ + enable: function() { + this.disabled = false; + this.el.removeClass(this.slider.disabledClass); + }, + /** - * @cfg {Object} scope - * The scope (this reference) in which the {@link #handler} - * function will be called. Defaults to this DatePicker instance. - */ + * Disables the thumb if it is currently enabled + */ + disable: function() { + this.disabled = true; + this.el.addClass(this.slider.disabledClass); + }, + /** - * @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. + * Sets up an Ext.dd.DragTracker for this thumb */ - todayTip : '{0} (Spacebar)', + 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); + }, + /** - * @cfg {String} minText - * The error text to display if the minDate validation fails (defaults to 'This date is before the minimum date') + * @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 */ - minText : 'This date is before the minimum date', + onBeforeDragStart : function(e) { + if (this.disabled) { + return false; + } else { + this.slider.promoteThumb(this); + return true; + } + }, + /** - * @cfg {String} maxText - * The error text to display if the maxDate validation fails (defaults to 'This date is after the maximum date') + * @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 */ - maxText : 'This date is after the maximum date', + 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); + }, + /** - * @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'). + * @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 */ - format : 'm/d/y', + onDrag: function(e) { + var slider = this.slider, + index = this.index, + newValue = this.getNewValue(); + + 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; + } + + slider.setValue(index, newValue, false); + slider.fireEvent('drag', slider, e, this); + }, + + getNewValue: function() { + var slider = this.slider, + pos = slider.innerEl.translatePoints(this.tracker.getXY()); + + return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision); + }, + /** - * @cfg {String} disabledDaysText - * The tooltip to display when the date falls on a disabled day (defaults to 'Disabled') + * @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 */ - disabledDaysText : 'Disabled', + 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); + } + }, + /** - * @cfg {String} disabledDatesText - * The tooltip text to display when the date falls on a disabled date (defaults to 'Disabled') + * @private + * Destroys the thumb */ - disabledDatesText : 'Disabled', + destroy: function(){ + Ext.destroyMembers(this, 'tracker', 'el'); + } +}); + +/** + * @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
      +});
      +
      + * 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,
      +
      +    //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 {Array} monthNames - * An array of textual month names which can be overriden for localization support (defaults to Date.monthNames) + * @cfg {Number} value The value to initialize the slider with. Defaults to minValue. */ - monthNames : Date.monthNames, /** - * @cfg {Array} dayNames - * An array of textual day names which can be overriden for localization support (defaults to Date.dayNames) + * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false. */ - dayNames : Date.dayNames, + vertical: false, /** - * @cfg {String} nextText - * The next month navigation button tooltip (defaults to 'Next Month (Control+Right)') + * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0. */ - nextText : 'Next Month (Control+Right)', + minValue: 0, /** - * @cfg {String} prevText - * The previous month navigation button tooltip (defaults to 'Previous Month (Control+Left)') + * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100. */ - prevText : 'Previous Month (Control+Left)', + maxValue: 100, /** - * @cfg {String} monthYearText - * The header month selector tooltip (defaults to 'Choose a month (Control+Up/Down to move years)') + * @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.

      */ - monthYearText : 'Choose a month (Control+Up/Down to move years)', + decimalPrecision: 0, /** - * @cfg {Number} startDay - * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday) + * @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. */ - startDay : 0, + keyIncrement: 1, /** - * @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). + * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'. */ - showToday : true, + increment: 0, + /** - * @cfg {Date} minDate - * Minimum allowable date (JavaScript date object, defaults to null) + * @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 */ + clickRange: [5,15], + /** - * @cfg {Date} maxDate - * Maximum allowable date (JavaScript date object, defaults to null) + * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true */ + clickToChange : true, /** - * @cfg {Array} disabledDays - * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null). + * @cfg {Boolean} animate Turn on or off animation. Defaults to true */ + animate: true, /** - * @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 {Boolean} constrainThumbs True to disallow thumbs from overlapping one another. Defaults to true */ + constrainThumbs: true, + /** - * @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 + * @property topThumbZIndex + * @type Number + * The number used internally to set the z index of the top thumb (see promoteThumb for details) */ - - // private - // Set by other components to stop the picker focus being updated when the value changes. - focusOnSelect: true, + topThumbZIndex: 10000, - // private + // private override initComponent : function(){ - Ext.DatePicker.superclass.initComponent.call(this); + if(!Ext.isDefined(this.value)){ + this.value = this.minValue; + } - this.value = this.value ? - this.value.clearTime(true) : new Date().clearTime(); + /** + * @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 select - * Fires when a date is selected - * @param {DatePicker} this DatePicker - * @param {Date} date The selected date + * @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.MultiSlider} 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. */ - 'select' + 'beforechange', + + /** + * @event change + * Fires when the slider value is changed. + * @param {Ext.slider.MultiSlider} 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 + */ + 'change', + + /** + * @event changecomplete + * Fires when the slider value is changed by the user and any drag operations have completed. + * @param {Ext.slider.MultiSlider} 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 + */ + 'changecomplete', + + /** + * @event dragstart + * Fires after a drag operation has started. + * @param {Ext.slider.MultiSlider} 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.MultiSlider} 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.MultiSlider} slider The slider + * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker + */ + 'dragend' ); - if(this.handler){ - this.on('select', this.handler, this.scope || this); - } + /** + * @property values + * @type Array + * Array of values to initalize the thumbs with + */ + if (this.values == undefined || Ext.isEmpty(this.values)) this.values = [0]; - this.initDisabledDays(); - }, + var values = this.values; - // 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 + ')'); + for (var i=0; i < values.length; i++) { + this.addThumb(values[i]); + } + + if(this.vertical){ + Ext.apply(this, Ext.slider.Vertical); } }, /** - * 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. + * Creates a new thumb and adds it to the slider + * @param {Number} value The initial value to set on the thumb. Defaults to 0 */ - setDisabledDates : function(dd){ - if(Ext.isArray(dd)){ - this.disabledDates = dd; - this.disabledDatesRE = null; - }else{ - this.disabledDatesRE = dd; - } - this.initDisabledDays(); - this.update(this.value, true); + 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(); }, /** - * 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. + * @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 */ - setDisabledDays : function(dd){ - this.disabledDays = dd; - this.update(this.value, true); + 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 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(); }, /** - * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker. - * @param {Date} value The minimum date that can be selected + * @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. */ - setMinDate : function(dt){ - this.minDate = dt; - this.update(this.value, true); + initEvents : function(){ + this.mon(this.el, { + scope : this, + mousedown: this.onMouseDown, + keydown : this.onKeyDown + }); + + this.focusEl.swallowEvent("click", true); }, /** - * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker. - * @param {Date} value The maximum date that can be selected + * @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 */ - setMaxDate : function(dt){ - this.maxDate = dt; - this.update(this.value, true); + onMouseDown : function(e){ + if(this.disabled){ + return; + } + + //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; + } + + if (this.clickToChange && !thumbClicked) { + var local = this.innerEl.translatePoints(e.getXY()); + this.onClickChange(local); + } + this.focus(); }, /** - * Sets the value of the date field - * @param {Date} value The date to set + * @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. */ - setValue : function(value){ - this.value = value.clearTime(true); - this.update(this.value); + 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); + } }, /** - * Gets the current selected value of the date field - * @return {Date} The selected date + * @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 */ - getValue : function(){ - return this.value; - }, + 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; - // private - focus : function(){ - 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(); + 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 - 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 + * 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; } - }, - - // 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); + 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(); } }, - // 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(''); + /** + * @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 + */ + doSnap : function(value){ + if (!(this.increment && value)) { + return value; } - m[m.length] = ''; - for(i = 0; i < 42; i++) { - if(i % 7 === 0 && i !== 0){ - m[m.length] = ''; + 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; } - 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 - }); + return newValue.constrain(this.minValue, this.maxValue); + }, - this.nextRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-right a'), { - handler: this.showNextMonth, - scope: this, - preventDefault:true, - stopDefault:true - }); + // private + afterRender : function(){ + Ext.slider.MultiSlider.superclass.afterRender.apply(this, arguments); - this.monthPicker = this.el.down('div.x-date-mp'); - this.monthPicker.enableDisplayMode('block'); + for (var i=0; i < this.thumbs.length; i++) { + var thumb = this.thumbs[i]; - this.keyNav = new Ext.KeyNav(this.eventEl, { - 'left' : function(e){ - if(e.ctrlKey){ - this.showPrevMonth(); - }else{ - this.update(this.activeDate.add('d', -1)); - } - }, + if (thumb.value !== undefined) { + var v = this.normalizeValue(thumb.value); - 'right' : function(e){ - if(e.ctrlKey){ - this.showNextMonth(); - }else{ - this.update(this.activeDate.add('d', 1)); + if (v !== thumb.value) { + // delete this.value; + this.setValue(i, v, false); + } else { + this.moveThumb(i, this.translateValue(v), false); } - }, + } + }; + }, - 'up' : function(e){ - if(e.ctrlKey){ - this.showNextYear(); - }else{ - this.update(this.activeDate.add('d', -7)); - } - }, + /** + * @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 + */ + getRatio : function(){ + var w = this.innerEl.getWidth(), + v = this.maxValue - this.minValue; + return v == 0 ? w : (w/v); + }, - 'down' : function(e){ - if(e.ctrlKey){ - this.showPrevYear(); - }else{ - this.update(this.activeDate.add('d', 7)); - } - }, + /** + * @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 + */ + normalizeValue : function(v){ + v = this.doSnap(v); + v = Ext.util.Format.round(v, this.decimalPrecision); + v = v.constrain(this.minValue, this.maxValue); + return v; + }, - 'pageUp' : function(e){ - this.showNextMonth(); - }, + /** + * 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 + */ + 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; + } + this.syncThumb(); + }, - 'pageDown' : function(e){ - this.showPrevMonth(); - }, + /** + * 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; + } + this.syncThumb(); + }, - 'enter' : function(e){ - e.stopPropagation(); - return true; - }, + /** + * 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; - scope : this - }); + v = this.normalizeValue(v); - this.el.unselectable(); + 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.cells = this.el.select('table.x-date-inner tbody td'); - this.textNodes = this.el.query('table.x-date-inner tbody span'); + /** + * @private + */ + translateValue : function(v) { + var ratio = this.getRatio(); + return (v * ratio) - (this.minValue * ratio) - this.halfThumb; + }, - 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'); + /** + * @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; + }, - 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 - }); + /** + * @private + * @param {Number} index Index of the thumb to move + */ + 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}); } - 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('')); + focus : function(){ + this.focusEl.focus(10); + }, - this.mon(this.monthPicker, 'click', this.onMonthClick, this); - this.mon(this.monthPicker, 'dblclick', this.onMonthDblClick, this); + // private + 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); + }, - this.mpMonths = this.monthPicker.select('td.x-date-mp-month'); - this.mpYears = this.monthPicker.select('td.x-date-mp-year'); + //private + onDisable: function(){ + Ext.slider.MultiSlider.superclass.onDisable.call(this); - 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); + 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); + } } }, - // private - showMonthPicker : function(){ - if(!this.disabled){ - this.createMonthPicker(); - var size = this.el.getSize(); - this.monthPicker.setSize(size); - this.monthPicker.child('table').setSize(size); + //private + onEnable: function(){ + Ext.slider.MultiSlider.superclass.onEnable.call(this); - this.mpSelMonth = (this.activeDate || this.value).getMonth(); - this.updateMPMonth(this.mpSelMonth); - this.mpSelYear = (this.activeDate || this.value).getFullYear(); - this.updateMPYear(this.mpSelYear); + for (var i=0; i < this.thumbs.length; i++) { + var thumb = this.thumbs[i], + el = thumb.el; - this.monthPicker.slideIn('t', {duration:0.2}); + thumb.enable(); + + if (Ext.isIE) { + this.innerEl.removeClass(this.disabledClass).dom.disabled = false; + + if (this.thumbHolder) this.thumbHolder.hide(); + + el.show(); + this.syncThumb(); + } } }, - // 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; + /** + * 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)); } - 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'); - }); + /** + * 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 + */ + getValue : function(index) { + return this.thumbs[index].value; }, - // private - selectMPMonth : function(m){ - - }, + /** + * Returns an array of values - one for the location of each thumb + * @return {Array} The set of thumb values + */ + getValues: function() { + var values = []; - // 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); + for (var i=0; i < this.thumbs.length; i++) { + values.push(this.thumbs[i].value); } + + return values; }, // 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(); + 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); }, - // private - hideMonthPicker : function(disableAnim){ - if(this.monthPicker){ - if(disableAnim === true){ - this.monthPicker.hide(); - }else{ - this.monthPicker.slideOut('t', {duration:0.2}); - } + /** + * 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(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); }, + /** + * 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 - showPrevMonth : function(e){ - this.update(this.activeDate.add('mo', -1)); + getNearest : function(){ + // Since there's only 1 thumb, it's always the nearest + return this.thumbs[0]; + } +}); + +//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(); }, - // private - showNextMonth : function(e){ - this.update(this.activeDate.add('mo', 1)); + getRatio : function(){ + var h = this.innerEl.getHeight(), + v = this.maxValue - this.minValue; + return h/v; }, - // private - showPrevYear : function(){ - this.update(this.activeDate.add('y', -1)); + moveThumb: function(index, v, animate) { + var thumb = this.thumbs[index], + el = thumb.el; + + 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); + } + } +}; + +//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', + + /** + * @cfg {Boolean} animate + * True to animate the progress bar during transitions (defaults to false) + */ + animate : false, + // private - showNextYear : function(){ - this.update(this.activeDate.add('y', 1)); - }, + waitTimer : null, // private - handleMouseWheel : function(e){ - e.stopEvent(); - if(!this.disabled){ - var delta = e.getWheelDelta(); - if(delta > 0){ - this.showPrevMonth(); - } else if(delta < 0){ - this.showNextMonth(); - } - } + 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 - 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); + 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); - // private - selectToday : function(){ - if(this.todayBtn && !this.todayBtn.disabled){ - this.setValue(new Date().clearTime()); - this.fireEvent('select', this, this.value); + 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 - 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 - day = 86400000, - d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime(), - 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.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, - 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]); - } - } + afterRender : function(){ + Ext.ProgressBar.superclass.afterRender.call(this); + if(this.value){ + this.updateProgress(this.value, this.text); + }else{ + this.updateText(this.text); } }, - // private - 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; + /** + * 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 && !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); + } + } + 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('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); - } -}; +//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!'); + } +}); -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', +//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; + }, /** - * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false) - * @type Boolean + * Returns true if the progress bar is currently in a {@link #wait} operation + * @return {Boolean} True if waiting, else false */ - disabled: false, + isWaiting : function(){ + return this.waitTimer !== null; + }, /** - * Disables the mask to prevent it from being 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 */ - disable : function(){ - this.disabled = true; + updateText : function(text){ + this.text = text || ' '; + if(this.rendered){ + this.textEl.update(this.text); + } + return this; }, - + /** - * Enables the mask so that it can be displayed + * 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. */ - enable : function(){ - this.disabled = false; - }, - - // private - onLoad : function(){ - this.el.unmask(this.removeMask); - }, - - // private - onBeforeLoad : function(){ - if(!this.disabled){ - this.el.mask(this.msg, this.msgCls); + 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); - }, - - // 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 && value)){ - return value; - } - 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; - } - } - 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(), - 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; - }, - - // private - beforeDestroy : function(){ - Ext.destroyMembers(this, 'endEl', 'innerEl', 'thumb', 'halfThumb', 'focusEl', 'tracker', 'thumbHolder'); - Ext.Slider.superclass.beforeDestroy.call(this); - } -}); -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(), - 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()), - 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; - 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; - }, - - /** - * 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; - }, - - onDestroy: function(){ - if(this.rendered){ - if(this.textEl.isComposite){ - this.textEl.clear(); - } - Ext.destroyMembers(this, 'textEl', 'progressBar', 'textTopEl'); - } - Ext.ProgressBar.superclass.onDestroy.call(this); - } -}); +}); Ext.reg('progress', Ext.ProgressBar); \ No newline at end of file