X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/2e847cf21b8ab9d15fa167b315ca5b2fa92638fc..0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6:/pkgs/cmp-foundation-debug.js diff --git a/pkgs/cmp-foundation-debug.js b/pkgs/cmp-foundation-debug.js index 2e29ba62..f6bcd75b 100644 --- a/pkgs/cmp-foundation-debug.js +++ b/pkgs/cmp-foundation-debug.js @@ -1,8 +1,8 @@ /*! - * Ext JS Library 3.1.1 - * Copyright(c) 2006-2010 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 @@ -226,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} @@ -266,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} @@ -1025,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 @@ -1079,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.

@@ -1454,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); @@ -1536,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(); @@ -1720,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. @@ -1775,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 @@ -1931,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 @@ -2201,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){ @@ -2302,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); } - } }, @@ -2420,6 +2476,10 @@ Ext.extend(Ext.Layer, Ext.Element, { } return this; }, + + getConstrainOffset : function(){ + return this.shadowOffset; + }, isVisible : function(){ return this.visible; @@ -2647,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; @@ -2668,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; @@ -2721,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"; }, @@ -2745,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; }, /** @@ -2757,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"; } } }, @@ -2783,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; @@ -2795,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); } }; @@ -3098,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; @@ -3114,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; } @@ -3144,8 +3217,8 @@ var myPanel = new Ext.Panel({ /** * Sets the width of the component. This method fires the {@link #resize} event. - * @param {Number} width The new width to setThis may be one of:
* @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) }; }, @@ -7175,478 +7351,483 @@ 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, - - type: 'accordion', - - renderItem : function(c){ - if(this.animate === false){ - c.animCollapse = false; - } - c.collapsible = true; - if(this.autoWidth){ - c.autoWidth = true; - } - if(this.titleCollapse){ - c.titleCollapse = true; - } - if(this.hideCollapseTool){ - c.hideCollapseTool = true; - } - if(this.collapseFirst !== undefined){ - c.collapseFirst = this.collapseFirst; - } - if(!this.activeItem && !c.collapsed){ - this.setActiveItem(c, true); - }else if(this.activeItem && this.activeItem != c){ - c.collapsed = true; - } - Ext.layout.AccordionLayout.superclass.renderItem.apply(this, arguments); - c.header.addClass('x-accordion-hd'); - c.on('beforeexpand', this.beforeExpand, this); - }, - - onRemove: function(c){ - Ext.layout.AccordionLayout.superclass.onRemove.call(this, c); - if(c.rendered){ - c.header.removeClass('x-accordion-hd'); - } - c.un('beforeexpand', this.beforeExpand, this); - }, - - // private - beforeExpand : function(p, anim){ - var ai = this.activeItem; - if(ai){ - if(this.sequence){ - delete this.activeItem; - if (!ai.collapsed){ - ai.collapse({callback:function(){ - p.expand(anim || true); - }, scope: this}); - return false; - } - }else{ - ai.collapse(this.animate); - } - } - this.setActive(p); - if(this.activeOnTop){ - p.el.dom.parentNode.insertBefore(p.el.dom, p.el.dom.parentNode.firstChild); - } - // Items have been hidden an possibly rearranged, we need to get the container size again. - this.layout(); - }, - - // private - setItemSize : function(item, size){ - if(this.fill && item){ - var hh = 0, i, ct = this.getRenderedItems(this.container), len = ct.length, p; - // Add up all the header heights - for (i = 0; i < len; i++) { - if((p = ct[i]) != item){ - 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); - } - }, - - /** - * 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:

- * - *

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, - - type: 'table', - - 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){ - target.addClass('x-table-layout-ct'); - - this.table = target.createChild( - Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true); - } - this.renderAll(ct, target); - }, - - // private - getRow : function(index){ - var row = this.table.tBodies[0].childNodes[index]; - if(!row){ - row = document.createElement('tr'); - this.table.tBodies[0].appendChild(row); - } - return row; - }, - - // private - getNextCell : function(c){ - var cell = this.getNextNonSpan(this.currentColumn, this.currentRow); - var curCol = this.currentColumn = cell[0], curRow = this.currentRow = cell[1]; - for(var rowIndex = curRow; rowIndex < curRow + (c.rowspan || 1); rowIndex++){ - if(!this.cells[rowIndex]){ - this.cells[rowIndex] = []; - } - for(var colIndex = curCol; colIndex < curCol + (c.colspan || 1); colIndex++){ - this.cells[rowIndex][colIndex] = true; - } - } - var td = document.createElement('td'); - if(c.cellId){ - td.id = c.cellId; - } - var cls = 'x-table-layout-cell'; - if(c.cellCls){ - cls += ' ' + c.cellCls; - } - td.className = cls; - if(c.colspan){ - td.colSpan = c.colspan; - } - if(c.rowspan){ - td.rowSpan = c.rowspan; - } - this.getRow(curRow).appendChild(td); - return td; - }, - - // private - getNextNonSpan: function(colIndex, rowIndex){ - var cols = this.columns; - while((cols && colIndex >= cols) || (this.cells[rowIndex] && this.cells[rowIndex][colIndex])) { - if(cols && colIndex >= cols){ - rowIndex++; - colIndex = 0; - }else{ - colIndex++; - } - } - return [colIndex, rowIndex]; - }, - - // private - renderItem : function(c, position, target){ - // Ensure we have our inner table to get cells to render into. - if(!this.table){ - this.table = target.createChild( - Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true); - } - if(c && !c.rendered){ - c.render(this.getNextCell(c)); - this.configureItem(c, position); - }else if(c && !this.isValidParent(c, target)){ - var container = this.getNextCell(c); - container.insertBefore(c.getPositionEl().dom, null); - c.container = Ext.get(container); - this.configureItem(c, position); - } - }, - - // private - isValidParent : function(c, target){ - return c.getPositionEl().up('table', 5).dom.parentNode === (target.dom || target); - } - - /** - * @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.

+/** + * @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 form = new Ext.form.FormPanel({
-    title: 'Absolute Layout',
-    layout:'absolute',
+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
-        extraCls: 'x-abs-layout-item',
+        titleCollapse: false,
+        animate: true,
+        activeOnTop: true
     },
-    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
+        title: 'Panel 1',
+        html: '<p>Panel content!</p>'
     },{
-        x: 0,
-        y: 35,
-        xtype:'label',
-        text: 'Subject:'
-    },{
-        x: 60,
-        y: 30,
-        name: 'subject',
-        anchor: '100%'  // anchor width by percentage
+        title: 'Panel 2',
+        html: '<p>Panel content!</p>'
     },{
-        x:0,
-        y: 60,
-        xtype: 'textarea',
-        name: 'msg',
-        anchor: '100% 100%'  // anchor width and height
+        title: 'Panel 3',
+        html: '<p>Panel content!</p>'
     }]
 });
 
*/ -Ext.layout.AbsoluteLayout = Ext.extend(Ext.layout.AnchorLayout, { +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, - extraCls: 'x-abs-layout-item', + type: 'accordion', - type: 'anchor', + 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); + }, - 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); + 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 - adjustWidthAnchor : function(value, comp){ - return value ? value - comp.getPosition(true)[0] + this.paddingLeft : value; + beforeExpand : function(p, anim){ + var ai = this.activeItem; + if(ai){ + if(this.sequence){ + delete this.activeItem; + if (!ai.collapsed){ + ai.collapse({callback:function(){ + p.expand(anim || true); + }, scope: this}); + return false; + } + }else{ + ai.collapse(this.animate); + } + } + this.setActive(p); + if(this.activeOnTop){ + p.el.dom.parentNode.insertBefore(p.el.dom, p.el.dom.parentNode.firstChild); + } + // Items have been hidden an possibly rearranged, we need to get the container size again. + this.layout(); }, // private - adjustHeightAnchor : function(value, comp){ - return value ? value - comp.getPosition(true)[1] + this.paddingTop : value; - } + 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); + } + }, + /** - * @property activeItem - * @hide + * 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['absolute'] = Ext.layout.AbsoluteLayout; -/** - * @class Ext.layout.BoxLayout +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:

+ * + *

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, + + type: 'table', + + 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){ + target.addClass('x-table-layout-ct'); + + this.table = target.createChild( + Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true); + } + this.renderAll(ct, target); + }, + + // private + getRow : function(index){ + var row = this.table.tBodies[0].childNodes[index]; + if(!row){ + row = document.createElement('tr'); + this.table.tBodies[0].appendChild(row); + } + return row; + }, + + // private + getNextCell : function(c){ + var cell = this.getNextNonSpan(this.currentColumn, this.currentRow); + var curCol = this.currentColumn = cell[0], curRow = this.currentRow = cell[1]; + for(var rowIndex = curRow; rowIndex < curRow + (c.rowspan || 1); rowIndex++){ + if(!this.cells[rowIndex]){ + this.cells[rowIndex] = []; + } + for(var colIndex = curCol; colIndex < curCol + (c.colspan || 1); colIndex++){ + this.cells[rowIndex][colIndex] = true; + } + } + var td = document.createElement('td'); + if(c.cellId){ + td.id = c.cellId; + } + var cls = 'x-table-layout-cell'; + if(c.cellCls){ + cls += ' ' + c.cellCls; + } + td.className = cls; + if(c.colspan){ + td.colSpan = c.colspan; + } + if(c.rowspan){ + td.rowSpan = c.rowspan; + } + this.getRow(curRow).appendChild(td); + return td; + }, + + // private + getNextNonSpan: function(colIndex, rowIndex){ + var cols = this.columns; + while((cols && colIndex >= cols) || (this.cells[rowIndex] && this.cells[rowIndex][colIndex])) { + if(cols && colIndex >= cols){ + rowIndex++; + colIndex = 0; + }else{ + colIndex++; + } + } + return [colIndex, rowIndex]; + }, + + // private + renderItem : function(c, position, target){ + // Ensure we have our inner table to get cells to render into. + if(!this.table){ + this.table = target.createChild( + Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true); + } + if(c && !c.rendered){ + c.render(this.getNextCell(c)); + this.configureItem(c); + }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); + } + }, + + // 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); + } + + /** + * @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', + + type: 'absolute', + + onLayout : function(ct, target){ + target.position(); + this.paddingLeft = target.getPadding('l'); + this.paddingTop = target.getPadding('t'); + Ext.layout.AbsoluteLayout.superclass.onLayout.call(this, ct, target); + }, + + // private + adjustWidthAnchor : function(value, comp){ + return value ? value - comp.getPosition(true)[0] + this.paddingLeft : value; + }, + + // private + adjustHeightAnchor : function(value, comp){ + return value ? value - comp.getPosition(true)[1] + this.paddingTop : value; + } + /** + * @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.

*/ @@ -7712,1013 +7893,2478 @@ Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, { constructor : function(config){ Ext.layout.BoxLayout.superclass.constructor.call(this, config); - if(Ext.isString(this.defaultMargins)){ + + 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); + }, + + /** + * @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); + }, + + /** + * 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); + } + }, + + /** + * @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); + }, + + /** + * @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){ + isValidParent : function(c, target) { return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom; }, + /** + * @private + * Returns all items that are both rendered and visible + * @return {Array} All matching items + */ + getVisibleItems: function(ct) { + var ct = ct || this.container, + t = ct.getLayoutTarget(), + cti = ct.items.items, + len = cti.length, + + i, c, items = []; + + for (i = 0; i < len; i++) { + if((c = cti[i]).rendered && this.isValidParent(c, t) && c.hidden !== true && c.collapsed !== true && 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 + 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); }, - onLayout : function(ct, target){ - this.renderAll(ct, target); - }, - - getLayoutTargetSize : function(){ + getLayoutTargetSize : function() { var target = this.container.getLayoutTarget(), ret; + if (target) { ret = target.getViewSize(); - ret.width -= target.getPadding('lr'); - ret.height -= target.getPadding('tb'); - } + + // 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){ + 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); + }, + + /** + * @private + */ + destroy: function() { + Ext.destroy(this.overflowHandler); + + Ext.layout.BoxLayout.superclass.destroy.apply(this, arguments); } }); + + +Ext.ns('Ext.layout.boxOverflow'); + /** - * @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. + * @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.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, { + +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 {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.
  • - *
+ * @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 */ - align : 'left', // left, center, stretch, strechmax - type: 'vbox', + afterCls: 'x-strip-right', + /** - * @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
  • - *
+ * @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 = []; + }, + + /** + * @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 {Number} flex - * This configuation option is to be applied to child items of the container managed - * by this layout. Each child item with a flex property will be flexed vertically - * according to each item's relative flex value compared to the sum of all items with - * a flex value specified. Any child items that have either a flex = 0 or - * flex = undefined will not be 'flexed' (the initial size will not be changed). + * @private */ - - // private - onLayout : function(ct, target){ - Ext.layout.VBoxLayout.superclass.onLayout.call(this, ct, target); - - var cs = this.getRenderedItems(ct), csLen = cs.length, - c, i, cm, ch, margin, cl, diff, aw, availHeight, - size = this.getLayoutTargetSize(), - w = size.width, - h = size.height - this.scrollOffset, - l = this.padding.left, - t = this.padding.top, - isStart = this.pack == 'start', - extraHeight = 0, - maxWidth = 0, - totalFlex = 0, - usedHeight = 0, - idx = 0, - heights = [], - restore = []; - - // 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; - // Max height for align - maxWidth = Math.max(maxWidth, c.getWidth() + cm.left + cm.right); + 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(); } - - 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; + + return { + targetSize: { + height: targetSize.height, + width : newWidth + } + }; + }, + + /** + * @private + */ + showTrigger: function() { + this.createMenu(); + this.menuTrigger.show(); + }, + + /** + * @private + */ + hideTrigger: function() { + if (this.menuTrigger != undefined) { + this.menuTrigger.hide(); } + }, + + /** + * @private + * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can. + */ + beforeMenuShow: function(menu) { + var items = this.menuItems, + len = items.length, + item, + prev; - 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(((w - (this.padding.left + this.padding.right)) - (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(); + 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; + } + // 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; - // Height calculations - for (i = 0 ; i < csLen; i++) { - c = cs[i]; - // Total of all the flex values - totalFlex += c.flex || 0; - // Don't run height calculations on flexed items - if (!c.flex) { - // Render and layout sub-containers without a flex or height, once - if (!c.height && !c.hasLayout && c.doLayout) { - c.doLayout(); - } - ch = c.getHeight(); - } else { - ch = 0; - } + Ext.copyTo(config, component, [ + 'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu' + ]); - cm = c.margins; - // Determine how much height is available to flex - extraHeight += ch + cm.top + cm.bottom; + Ext.apply(config, { + text : component.overflowText || component.text, + hideOnClick: hideOnClick + }); + + if (group || component.enableToggle) { + Ext.apply(config, { + group : group, + checked: component.pressed, + listeners: { + checkchange: function(item, checked){ + component.toggle(checked); + } + } + }); } - // Final avail height calc - availHeight = Math.max(0, (h - extraHeight - this.padding.top - this.padding.bottom)); - var 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); + delete config.ownerCt; + delete config.xtype; + delete config.id; + + return config; + }, + + /** + * @private + * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually. + * @param {Ext.menu.Menu} menu The menu to add to + * @param {Ext.Component} component The component to add + */ + addComponentToMenu : function(menu, component) { + if (component instanceof Ext.Toolbar.Separator) { + menu.add('-'); + + } else if (Ext.isFunction(component.isXType)) { + if (component.isXType('splitbutton')) { + menu.add(this.createMenuConfig(component, true)); + + } else if (component.isXType('button')) { + menu.add(this.createMenuConfig(component, !component.menu)); + + } else if (component.isXType('buttongroup')) { + component.items.each(function(item){ + this.addComponentToMenu(menu, item); + }, this); } } - if(this.pack == 'center'){ - t += availHeight ? availHeight / 2 : 0; - }else if(this.pack == 'end'){ - t += availHeight; + }, + + /** + * @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; + }); } - 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; + }, + + /** + * @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 + */ + 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 } - } + }); - c.setPosition(cl, t); - if(isStart && c.flex){ - ch = Math.max(0, heights[idx++] + (leftOver-- > 0 ? 1 : 0)); - c.setSize(aw, ch); - }else{ - ch = c.getHeight(); + /** + * @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 + }); + } + }, + + /** + * @private + */ + destroy: function() { + Ext.destroy(this.menu, this.menuTrigger); + } +}); + +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; } - t += ch + cm.bottom; } - // Putting a box layout into an overflowed container is NOT correct and will make a second layout pass necessary. - if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) { - var ts = this.getLayoutTargetSize(); - if (ts.width != size.width || ts.height != size.height){ - this.adjustmentPass = true; - this.onLayout(ct, target); + + 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; + } } } - delete this.adjustmentPass; + + if (this.menuItems.length == 0) { + this.hideTrigger(); + } + + return { + targetSize: { + height: targetSize.height, + width : newWidth + }, + recalculate: recalculate + }; } }); -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.boxOverflow.menu.hbox = Ext.layout.boxOverflow.HorizontalMenu;/** + * @class Ext.layout.boxOverflow.Scroller + * @extends Ext.layout.boxOverflow.None + * Description */ -Ext.layout.HBoxLayout = Ext.extend(Ext.layout.BoxLayout, { +Ext.layout.boxOverflow.Scroller = Ext.extend(Ext.layout.boxOverflow.None, { /** - * @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.
  • + * @cfg animateScroll + * @type Boolean + * True to animate the scrolling of items within the layout (defaults to true, ignored if enableScroll is false) */ - align : 'top', // top, middle, stretch, strechmax - type: 'hbox', + animateScroll: true, + /** - * @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 scrollIncrement + * @type Number + * The number of pixels to scroll by on scroller click (defaults to 100) */ + scrollIncrement: 100, + /** - * @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). + * @cfg wheelIncrement + * @type Number + * The number of pixels to increment on mouse wheel scrolling (defaults to 3). */ + wheelIncrement: 3, + + /** + * @cfg scrollRepeatInterval + * @type Number + * Number of milliseconds between each scroll while a scroller button is held down (defaults to 400) + */ + scrollRepeatInterval: 400, + + /** + * @cfg scrollDuration + * @type Number + * Number of seconds that each scroll animation lasts (defaults to 0.4) + */ + scrollDuration: 0.4, + + /** + * @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 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 scrollerCls + * @type String + * CSS class added to both scroller elements if enableScroll is used + */ + scrollerCls: 'x-strip-scroller', + + /** + * @cfg beforeScrollerCls + * @type String + * CSS class added to the left scroller element if enableScroll is used + */ + beforeScrollerCls: 'x-strip-scroller-left', + + /** + * @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(); - // private - onLayout : function(ct, target){ - Ext.layout.HBoxLayout.superclass.onLayout.call(this, ct, target); - - var cs = this.getRenderedItems(ct), csLen = cs.length, - c, i, cm, cw, ch, diff, availWidth, - size = this.getLayoutTargetSize(), - 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, - extraWidth = 0, - maxHeight = 0, - totalFlex = 0, - usedWidth = 0; - - for (i = 0 ; i < csLen; i++) { - c = cs[i]; - // Total of all the flex values - totalFlex += c.flex || 0; - // Don't run width calculations on flexed items - if (!c.flex) { - // Render and layout sub-containers without a flex or width, once - if (!c.width && !c.hasLayout && c.doLayout) { - c.doLayout(); - } - cw = c.getWidth(); - } else { - cw = 0; + this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false); } - cm = c.margins; - // Determine how much width is available to flex - extraWidth += cw + cm.left + cm.right; - // Max height for align - maxHeight = Math.max(maxHeight, c.getHeight() + cm.top + cm.bottom); - } - // Final avail width calc - availWidth = Math.max(0, (w - extraWidth - this.padding.left - this.padding.right)); - - var innerCtHeight = maxHeight + this.padding.top + this.padding.bottom; - switch(this.align){ - case 'stretch': - this.innerCt.setSize(w, h); - break; - case 'stretchmax': - case 'top': - this.innerCt.setSize(w, innerCtHeight); - break; - case 'middle': - this.innerCt.setSize(w, h = Math.max(h, innerCtHeight)); - break; + }); + }, + + /** + * @private + * Most of the heavy lifting is done in the subclasses + */ + handleOverflow: function(calculations, targetSize) { + this.createInnerElements(); + this.showScrollers(); + }, + + /** + * @private + */ + clearOverflow: function() { + this.hideScrollers(); + }, + + /** + * @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(); + }, + + /** + * @private + * Hides the scroller elements in the beforeCt and afterCt + */ + hideScrollers: function() { + if (this.beforeScroller != undefined) { + this.beforeScroller.hide(); + this.afterScroller.hide(); } - - var leftOver = availWidth, - widths = [], - restore = [], - idx = 0, - availableHeight = Math.max(0, h - this.padding.top - this.padding.bottom); - - for (i = 0 ; i < csLen; i++) { - c = cs[i]; - if(isStart && c.flex){ - cw = Math.floor(availWidth * (c.flex / totalFlex)); - leftOver -= cw; - widths.push(cw); - } + }, + + /** + * @private + * Creates the clickable scroller elements and places them into the beforeCt and afterCt + */ + 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; } - - if(this.pack == 'center'){ - l += availWidth ? availWidth / 2 : 0; - }else if(this.pack == 'end'){ - l += availWidth; + }, + + /** + * @private + */ + destroy: function() { + Ext.destroy(this.beforeScroller, this.afterScroller, this.beforeRepeater, this.afterRepeater, this.beforeCt, this.afterCt); + }, + + /** + * @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 + */ + scrollBy: function(delta, animate) { + this.scrollTo(this.getScrollPosition() + delta, animate); + }, + + /** + * @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 + */ + getItem: function(item) { + if (Ext.isString(item)) { + item = Ext.getCmp(item); + } else if (Ext.isNumber(item)) { + item = this.items[item]; } - for (i = 0 ; i < csLen; i++) { - c = cs[i]; - 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; + + return item; + }, + + /** + * @private + * @return {Object} Object passed to scrollTo when scrolling + */ + getScrollAnim: function() { + return { + duration: this.scrollDuration, + callback: this.updateScrollButtons, + scope : this + }; + }, + + /** + * @private + * Enables or disables each scroller button based on the current scroll position + */ + updateScrollButtons: function() { + if (this.beforeScroller == undefined || this.afterScroller == undefined) { + return; } - - idx = 0; - for (i = 0 ; i < csLen; i++) { - c = cs[i]; - cm = c.margins; - ch = c.getHeight(); - if(isStart && c.flex){ - ch = restore[idx++]; - } - if(this.align == 'stretch'){ - c.setHeight(((h - (this.padding.top + this.padding.bottom)) - (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); + + 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; + }, + + /** + * @private + * Returns true if the innerCt scroll is already at its left-most point + * @return {Boolean} True if already at furthest left point + */ + atExtremeBefore: function() { + return this.getScrollPosition() === 0; + }, + + /** + * @private + * Scrolls to the left by the configured amount + */ + scrollLeft: function(animate) { + this.scrollBy(-this.scrollIncrement, animate); + }, + + /** + * @private + * Scrolls to the right by the configured amount + */ + scrollRight: function(animate) { + this.scrollBy(this.scrollIncrement, animate); + }, + + /** + * 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 + */ + 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); } } - // Putting a box layout into an overflowed container is NOT correct and will make a second layout pass necessary. - if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) { - var ts = this.getLayoutTargetSize(); - if (ts.width != size.width || ts.height != size.height){ - this.adjustmentPass = true; - this.onLayout(ct, target); - } - } - delete this.adjustmentPass; + }, + + /** + * @private + * 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.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout; -/** - * @class Ext.layout.ToolbarLayout - * @extends Ext.layout.ContainerLayout - * Layout manager implicitly used by Ext.Toolbar. - */ -Ext.layout.ToolbarLayout = Ext.extend(Ext.layout.ContainerLayout, { - monitorResize : true, - triggerWidth : 18, - lastOverflow : false, - - noItemsMenuText : '
    (None)
    ', - - // private - onLayout : function(ct, target){ - if(!this.leftTr){ - var align = ct.buttonAlign == 'center' ? 'center' : 'left'; - target.addClass('x-toolbar-layout-ct'); - target.insertHtml('beforeEnd', - '
    '); - 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); - } +Ext.layout.boxOverflow.scroller = Ext.layout.boxOverflow.Scroller; - var side = ct.buttonAlign == 'right' ? this.rightTr : this.leftTr, - pos = 0, - items = ct.items.items; - for(var i = 0, len = items.length, c; i < len; i++, pos++) { - c = items[i]; - if(c.isFill){ - side = this.rightTr; - pos = -1; - }else if(!c.rendered){ - c.render(this.insertCell(c, side, pos)); - }else{ - if(!c.xtbHidden && !this.isValidParent(c, side.childNodes[pos])){ - var td = this.insertCell(c, side, pos); - td.appendChild(c.getPositionEl().dom); - c.container = Ext.get(td); - } +/** + * @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 } - } - //strip extra empty cells - this.cleanup(this.leftTr); - this.cleanup(this.rightTr); - this.cleanup(this.extrasTr); - this.fitToSize(target); + }; }, + + /** + * @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.beforeCt = target.insertSibling({cls: this.beforeCls}, 'before'); + this.afterCt = target.insertSibling({cls: this.afterCls}, 'after'); - cleanup : function(row){ - var cn = row.childNodes, i, c; - for(i = cn.length-1; i >= 0 && (c = cn[i]); i--){ - if(!c.firstChild){ - row.removeChild(c); - } + this.createWheelListener(); } }, - - insertCell : function(c, side, pos){ - var td = document.createElement('td'); - td.className='x-toolbar-cell'; - side.insertBefore(td, side.childNodes[pos]||null); - return td; - }, - - hideItem : function(item){ - var h = (this.hiddens = this.hiddens || []); - h.push(item); - item.xtbHidden = true; - item.xtbWidth = item.getPositionEl().dom.parentNode.offsetWidth; - item.hide(); - }, - - unhideItem : function(item){ - item.show(); - item.xtbHidden = false; - this.hiddens.remove(item); - if(this.hiddens.length < 1){ - delete this.hiddens; + + /** + * @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.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(); + } } }, - - getItemWidth : function(c){ - return c.hidden ? (c.xtbWidth || 0) : c.getPositionEl().dom.parentNode.offsetWidth; + + /** + * 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(); + } +}); - fitToSize : function(t){ - if(this.container.enableOverflow === false){ - return; - } - var w = t.dom.clientWidth, - lw = this.lastWidth || 0, - iw = t.dom.firstChild.offsetWidth, - clipWidth = w - this.triggerWidth, - hideIndex = -1; - - this.lastWidth = w; +Ext.layout.boxOverflow.scroller.vbox = Ext.layout.boxOverflow.VerticalScroller; - if(iw > w || (this.hiddens && w >= lw)){ - var i, items = this.container.items.items, - len = items.length, c, - loopWidth = 0; - for(i = 0; i < len; i++) { - c = items[i]; - if(!c.isFill){ - loopWidth += this.getItemWidth(c); - if(loopWidth > clipWidth){ - if(!(c.hidden || c.xtbHidden)){ - this.hideItem(c); - } - }else if(c.xtbHidden){ - this.unhideItem(c); - } - } +/** + * @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()) } - } - if(this.hiddens){ - this.initMore(); - if(!this.lastOverflow){ - this.container.fireEvent('overflowchange', this.container, true); - this.lastOverflow = true; - } - }else if(this.more){ - this.clearMenu(); - this.more.destroy(); - delete this.more; - if(this.lastOverflow){ - this.container.fireEvent('overflowchange', this.container, false); - this.lastOverflow = false; + }; + }, + + /** + * @private + * 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 - createMenuConfig : function(c, hideOnClick){ - var cfg = Ext.apply({}, c.initialConfig), - group = c.toggleGroup; + type : 'hbox', - Ext.apply(cfg, { - text: c.overflowText || c.text, - iconCls: c.iconCls, - icon: c.icon, - itemId: c.itemId, - disabled: c.disabled, - handler: c.handler, - scope: c.scope, - menu: c.menu, - hideOnClick: hideOnClick - }); - if(group || c.enableToggle){ - Ext.apply(cfg, { - group: group, - checked: c.pressed, - listeners: { - checkchange: function(item, checked){ - c.toggle(checked); + /** + * @cfg {String} pack + * Controls how the child items of the container are packed together. Acceptable configuration values + * for this property are: + *
        + *
      • start : Default
        child items are packed together at + * left side of container
      • + *
      • center :
        child items are packed together at + * mid-width of container
      • + *
      • end :
        child items are packed together at right + * side of container
      • + *
      + */ + /** + * @cfg {Number} flex + * This configuation option is to be applied to child items of the container managed + * by this layout. Each child item with a flex property will be flexed horizontally + * according to each item's relative flex value compared to the sum of all items with + * a flex value specified. Any child items that have either a flex = 0 or + * flex = undefined will not be 'flexed' (the initial size will not be changed). + */ + + /** + * @private + * 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(); } + + childSize = child.getSize(); + childWidth = childSize.width; + childHeight = childSize.height; } - }); - } - delete cfg.ownerCt; - delete cfg.xtype; - delete cfg.id; - return cfg; - }, + } - // private - addComponentToMenu : function(m, c){ - if(c instanceof Ext.Toolbar.Separator){ - m.add('-'); - }else if(Ext.isFunction(c.isXType)){ - if(c.isXType('splitbutton')){ - m.add(this.createMenuConfig(c, true)); - }else if(c.isXType('button')){ - m.add(this.createMenuConfig(c, !c.menu)); - }else if(c.isXType('buttongroup')){ - c.items.each(function(item){ - this.addComponentToMenu(m, item); - }, this); + childMargins = child.margins; + horizMargins = childMargins.left + childMargins.right; + + 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(); } - } - }, - clearMenu : function(){ - var m = this.moreMenu; - if(m && m.items){ - m.items.each(function(item){ - delete item.menu; + maxHeight = Math.max(maxHeight, childHeight + childMargins.top + childMargins.bottom); + + //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 }); } - }, + + 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 - beforeMoreShow : function(m){ - var h = this.container.items.items, - len = h.length, - c, - prev, - needsSep = function(group, item){ - return group.isXType('buttongroup') && !(item instanceof Ext.Toolbar.Separator); - }; + //calculate the widths of each flexed item + for (i = 0; i < visibleCount; i++) { + child = visibleItems[i]; + calcs = boxes[i]; - this.clearMenu(); - m.removeAll(); - for(var i = 0; i < len; i++){ - c = h[i]; - if(c.xtbHidden){ - if(prev && (needsSep(c, prev) || needsSep(prev, c))){ - m.add('-'); + 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; + + calcs.width = flexedWidth; + calcs.dirtySize = true; + } } - this.addComponentToMenu(m, c); - prev = c; } } - // put something so the menu isn't empty - // if no compatible items found - if(m.items.length < 1){ - m.add(this.noItemsMenuText); + + if (isCenter) { + leftOffset += availableWidth / 2; + } else if (isEnd) { + leftOffset += availableWidth; } - }, - - initMore : function(){ - if(!this.more){ - this.moreMenu = new Ext.menu.Menu({ - ownerCt : this.container, - listeners: { - beforeshow: this.beforeMoreShow, - scope: this - } - - }); - this.more = new Ext.Button({ - iconCls : 'x-toolbar-more-icon', - cls : 'x-toolbar-more', - menu : this.moreMenu, - ownerCt : this.container - }); - var td = this.insertCell(this.more, this.extrasTr, 100); - this.more.render(td); + + //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; } - }, - destroy : function(){ - Ext.destroy(this.more, this.moreMenu); - delete this.leftTr; - delete this.rightTr; - delete this.extrasTr; - Ext.layout.ToolbarLayout.superclass.destroy.call(this); + return { + boxes: boxes, + meta : { + maxHeight : maxHeight, + nonFlexWidth: nonFlexWidth, + desiredWidth: desiredWidth, + minimumWidth: minimumWidth, + shortfall : desiredWidth - width, + tooNarrow : tooNarrow + } + }; } }); -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.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.MenuLayout = Ext.extend(Ext.layout.ContainerLayout, { - monitorResize : true, +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', - 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); - }, + /** + * @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
      • + *
      + */ - renderItem : function(c, position, target){ - if (!this.itemTpl) { - this.itemTpl = Ext.layout.MenuLayout.prototype.itemTpl = new Ext.XTemplate( - '
    • ', - '', - '', - '', - '
    • ' - ); - } + /** + * @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). + */ - if(c && !c.rendered){ - if(Ext.isNumber(position)){ - position = target.dom.childNodes[position]; - } - var a = this.getItemArgs(c); + /** + * @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(); + } -// 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)); + childSize = child.getSize(); + childWidth = childSize.width; + childHeight = childSize.height; + } + } + + childMargins = child.margins; + vertMargins = childMargins.top + childMargins.bottom; -// Link the containing
    • to the item. - c.positionEl.menuItemId = c.getItemId(); + nonFlexHeight += vertMargins + (childHeight || 0); + desiredHeight += vertMargins + (child.flex ? child.minHeight || 0 : childHeight); + minimumHeight += vertMargins + (child.minHeight || childHeight || 0); -// If rendering a regular Component, and it needs an icon, -// move the Component rightwards. - if (!a.isMenuItem && a.needsIcon) { - c.positionEl.addClass('x-menu-list-item-indent'); - } - this.configureItem(c, position); - }else if(c && !this.isValidParent(c, target)){ - if(Ext.isNumber(position)){ - position = target.dom.childNodes[position]; + // 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(); } - target.dom.insertBefore(c.getActionEl().dom, position || null); - } - }, - getItemArgs : function(c) { - var isMenuItem = c instanceof Ext.menu.Item; - return { - isMenuItem: isMenuItem, - needsIcon: !isMenuItem && (c.icon || c.iconCls), - icon: c.icon || Ext.BLANK_IMAGE_URL, - iconCls: 'x-menu-item-icon ' + (c.iconCls || ''), - itemId: 'x-menu-el-' + c.id, - itemCls: 'x-menu-list-item ' - }; - }, + maxWidth = Math.max(maxWidth, childWidth + childMargins.left + childMargins.right); - // 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); - }, + //cache the size of each child component + boxes.push({ + component: child, + height : childHeight || undefined, + width : childWidth || undefined + }); + } + + var shortfall = desiredHeight - height, + tooNarrow = minimumHeight > height; - onLayout : function(ct, target){ - Ext.layout.MenuLayout.superclass.onLayout.call(this, ct, target); - this.doAutoSize(); - }, + //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 = []; - 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')); + /** + * 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 + }); + } + } + + //sort by descending minHeight value + minHeights.sort(function(a, b) { + return a.available > b.available ? 1 : -1; + }); + + /* + * 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; + + if (itemIndex == undefined) { + continue; + } + + 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]; + + childMargins = child.margins; + horizMargins = childMargins.left + childMargins.right; + + if (isStart && child.flex && !child.height) { + flexedHeight = Math.ceil((child.flex / remainingFlex) * remainingHeight); + remainingHeight -= flexedHeight; + remainingFlex -= child.flex; + + calcs.height = flexedHeight; + calcs.dirtySize = true; + } + } + } + } + + if (isCenter) { + topOffset += availableHeight / 2; + } else if (isEnd) { + topOffset += availableHeight; + } + + //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; } + + return { + boxes: boxes, + meta : { + maxWidth : maxWidth, + nonFlexHeight: nonFlexHeight, + desiredHeight: desiredHeight, + minimumHeight: minimumHeight, + shortfall : desiredHeight - height, + tooNarrow : tooNarrow + } + }; } }); -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); + +Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout; /** - * @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.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.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 - */ +Ext.layout.ToolbarLayout = Ext.extend(Ext.layout.ContainerLayout, { + monitorResize : true, + + type: 'toolbar', + /** - * 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 + * @property triggerWidth + * @type Number + * The width allocated for the menu trigger at the extreme right end of the Toolbar */ + triggerWidth: 18, + /** - * 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 + * @property noItemsMenuText + * @type String + * HTML fragment to render into the toolbar overflow menu if there are no items to display */ + noItemsMenuText : '
        (None)
        ', + /** - * True if this panel is collapsed. Read-only. + * @private + * @property lastOverflow * @type Boolean - * @property collapsed + * Used internally to record whether the last layout caused an overflow or not */ + lastOverflow: false, + /** - * @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.

        - */ + * @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(""), + /** - * @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 + * Create the wrapping Toolbar HTML and render/move all the items into the correct places */ + onLayout : function(ct, target) { + //render the Toolbar HTML if it's not already present + if (!this.leftTr) { + var align = ct.buttonAlign == 'center' ? 'center' : 'left'; + + target.addClass('x-toolbar-layout-ct'); + target.insertHtml('beforeEnd', String.format(this.tableHTML, align)); + + this.leftTr = target.child('tr.x-toolbar-left-row', true); + this.rightTr = target.child('tr.x-toolbar-right-row', true); + this.extrasTr = target.child('tr.x-toolbar-extras-row', true); + + if (this.hiddenItem == undefined) { + /** + * @property hiddenItems + * @type Array + * Holds all items that are currently hidden due to there not being enough space to render them + * These items will appear on the expand menu. + */ + this.hiddenItems = []; + } + } + + var side = ct.buttonAlign == 'right' ? this.rightTr : this.leftTr, + items = ct.items.items, + position = 0; + + //render each item if not already rendered, place it into the correct (left or right) target + for (var i = 0, len = items.length, c; i < len; i++, position++) { + c = items[i]; + + if (c.isFill) { + side = this.rightTr; + position = -1; + } else if (!c.rendered) { + c.render(this.insertCell(c, side, position)); + 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); + } + } + } + + //strip extra empty cells + this.cleanup(this.leftTr); + this.cleanup(this.rightTr); + this.cleanup(this.extrasTr); + this.fitToSize(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.

        + * @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); + } + } + }, + /** - * @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 + * 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; + }, + /** - * @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 + * 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(); + }, + /** - * @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 + * 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); + }, + /** - * @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. + * @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; + }, + /** - * 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 + * @private + * Called at the end of onLayout. At this point the Toolbar has already been resized, so we need + * to fit the items into the available width. We add up the width required by all of the items in + * the toolbar - if we don't have enough space we hide the extra items and render the expand menu + * trigger. + * @param {Ext.Element} target The Element the Toolbar is currently laid out within + */ + fitToSize : function(target) { + if (this.container.enableOverflow === false) { + return; + } + + var width = target.dom.clientWidth, + tableWidth = target.dom.firstChild.offsetWidth, + clipWidth = width - this.triggerWidth, + lastWidth = this.lastWidth || 0, + + hiddenItems = this.hiddenItems, + hasHiddens = hiddenItems.length != 0, + isLarger = width >= lastWidth; + + this.lastWidth = width; + + if (tableWidth > width || (hasHiddens && isLarger)) { + var items = this.container.items.items, + len = items.length, + loopWidth = 0, + item; + + for (var i = 0; i < len; i++) { + item = items[i]; + + if (!item.isFill) { + loopWidth += this.getItemWidth(item); + if (loopWidth > clipWidth) { + if (!(item.hidden || item.xtbHidden)) { + this.hideItem(item); + } + } else if (item.xtbHidden) { + this.unhideItem(item); + } + } + } + } + + //test for number of hidden items again here because they may have changed above + hasHiddens = hiddenItems.length != 0; + + if (hasHiddens) { + this.initMore(); + + if (!this.lastOverflow) { + this.container.fireEvent('overflowchange', this.container, true); + this.lastOverflow = true; + } + } else if (this.more) { + this.clearMenu(); + this.more.destroy(); + delete this.more; + + if (this.lastOverflow) { + this.container.fireEvent('overflowchange', this.container, false); + this.lastOverflow = false; + } + } + }, + + /** + * @private + * Returns a menu config for a given component. This config is used to create a menu item + * to be added to the expander menu + * @param {Ext.Component} component The component to create the config for + * @param {Boolean} hideOnClick Passed through to the menu item + */ + createMenuConfig : function(component, hideOnClick){ + var config = Ext.apply({}, component.initialConfig), + group = component.toggleGroup; + + Ext.copyTo(config, component, [ + 'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu' + ]); + + Ext.apply(config, { + text : component.overflowText || component.text, + hideOnClick: hideOnClick + }); + + if (group || component.enableToggle) { + Ext.apply(config, { + group : group, + checked: component.pressed, + listeners: { + checkchange: function(item, checked){ + component.toggle(checked); + } + } + }); + } + + delete config.ownerCt; + delete config.xtype; + delete config.id; + + return config; + }, + + /** + * @private + * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually. + * @param {Ext.menu.Menu} menu The menu to add to + * @param {Ext.Component} component The component to add + */ + addComponentToMenu : function(menu, component) { + if (component instanceof Ext.Toolbar.Separator) { + menu.add('-'); + + } else if (Ext.isFunction(component.isXType)) { + if (component.isXType('splitbutton')) { + menu.add(this.createMenuConfig(component, true)); + + } else if (component.isXType('button')) { + menu.add(this.createMenuConfig(component, !component.menu)); + + } else if (component.isXType('buttongroup')) { + component.items.each(function(item){ + this.addComponentToMenu(menu, item); + }, this); + } + } + }, + + /** + * @private + * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as + * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item + */ + clearMenu : function(){ + var menu = this.moreMenu; + if (menu && menu.items) { + menu.items.each(function(item){ + delete item.menu; + }); + } + }, + + /** + * @private + * Called before the expand menu is shown, this rebuilds the menu since it was last shown because + * it is possible that the items hidden due to space limitations on the Toolbar have changed since. + * @param {Ext.menu.Menu} m The menu + */ + beforeMoreShow : function(menu) { + var items = this.container.items.items, + len = items.length, + item, + prev; + + var needsSep = function(group, item){ + return group.isXType('buttongroup') && !(item instanceof Ext.Toolbar.Separator); + }; + + this.clearMenu(); + menu.removeAll(); + for (var i = 0; i < len; i++) { + item = items[i]; + if (item.xtbHidden) { + if (prev && (needsSep(item, prev) || needsSep(prev, item))) { + menu.add('-'); + } + this.addComponentToMenu(menu, item); + prev = item; + } + } + + // put something so the menu isn't empty if no compatible items found + if (menu.items.length < 1) { + menu.add(this.noItemsMenuText); + } + }, + + /** + * @private + * Creates the expand trigger and menu, adding them to the at the extreme right of the + * Toolbar table + */ + initMore : function(){ + if (!this.more) { + /** + * @private + * @property moreMenu + * @type Ext.menu.Menu + * The expand menu - holds items for every Toolbar item that cannot be shown + * because the Toolbar is currently not wide enough. + */ + this.moreMenu = new Ext.menu.Menu({ + ownerCt : this.container, + listeners: { + beforeshow: this.beforeMoreShow, + scope: this + } + }); + + /** + * @private + * @property more + * @type Ext.Button + * The expand button which triggers the overflow menu to be shown + */ + this.more = new Ext.Button({ + iconCls: 'x-toolbar-more-icon', + cls : 'x-toolbar-more', + menu : this.moreMenu, + ownerCt: this.container + }); + + var td = this.insertCell(this.more, this.extrasTr, 100); + this.more.render(td); + } + }, + + destroy : function(){ + Ext.destroy(this.more, this.moreMenu); + delete this.leftTr; + delete this.rightTr; + delete this.extrasTr; + Ext.layout.ToolbarLayout.superclass.destroy.call(this); + } +}); + +Ext.Container.LAYOUTS.toolbar = Ext.layout.ToolbarLayout; +/** + * @class Ext.layout.MenuLayout + * @extends Ext.layout.ContainerLayout + *

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

        + */ + Ext.layout.MenuLayout = Ext.extend(Ext.layout.ContainerLayout, { + monitorResize : true, + + type: 'menu', + + setContainer : function(ct){ + this.monitorResize = !ct.floating; + // This event is only fired by the menu in IE, used so we don't couple + // the menu with the layout. + ct.on('autosize', this.doAutoSize, this); + Ext.layout.MenuLayout.superclass.setContainer.call(this, ct); + }, + + renderItem : function(c, position, target){ + if (!this.itemTpl) { + this.itemTpl = Ext.layout.MenuLayout.prototype.itemTpl = new Ext.XTemplate( + '
      • ', + '', + '{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 @@ -9360,30 +11006,30 @@ new Ext.Panel({ if(this.tbar){ this.elements += ',tbar'; this.topToolbar = this.createToolbar(this.tbar); - delete this.tbar; + this.tbar = null; } if(this.bbar){ this.elements += ',bbar'; this.bottomToolbar = this.createToolbar(this.bbar); - delete this.bbar; + this.bbar = null; } if(this.header === true){ this.elements += ',header'; - delete this.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'; - delete this.footer; + this.footer = null; } if(this.buttons){ this.fbar = this.buttons; - delete this.buttons; + this.buttons = null; } if(this.fbar){ this.createFbar(this.fbar); @@ -9407,8 +11053,8 @@ new Ext.Panel({ }; } }); - //@compat addButton and buttons could possibly be removed - //@target 4.0 + // @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. @@ -9503,3062 +11149,3606 @@ new Ext.Panel({ el.addClass(this.cls); } - if(this.buttons){ - this.elements += ',footer'; + 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(); } + }, - // 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); + syncHeight : function(){ + var h = this.toolbarHeight, + bd = this.body, + lsh = this.lastSize.height, + sz; - 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.autoHeight || !Ext.isDefined(lsh) || lsh == 'auto'){ + return; + } - 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(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); + } + }, - if(!this.header){ - this.body.addClass(this.bodyCls + '-noheader'); - if(this.tbar){ - this.tbar.addClass(this.tbarCls + '-noheader'); - } - } + // private + onShow : function(){ + if(this.floating){ + return this.el.show(); } + Ext.Panel.superclass.onShow.call(this); + }, - if(Ext.isDefined(this.padding)){ - this.body.setStyle('padding', this.body.addUnits(this.padding)); + // private + onHide : function(){ + if(this.floating){ + return this.el.hide(); } + Ext.Panel.superclass.onHide.call(this); + }, - 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'); + // private + createToolHandler : function(t, tc, overCls, panel){ + return function(e){ + t.removeClass(overCls); + if(tc.stopEvent !== false){ + e.stopEvent(); } - if(this.bbar){ - this.bbar.addClass(this.bbarCls + '-noborder'); + if(tc.handler){ + tc.handler.call(tc.scope || t, e, t, panel, tc); } - } + }; + }, - if(this.bodyBorder === false){ - this.body.addClass(this.bodyCls + '-noborder'); + // 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(); + }, - this.bwrap.enableDisplayMode('block'); + // private + getKeyMap : function(){ + if(!this.keyMap){ + this.keyMap = new Ext.KeyMap(this.el, this.keys); + } + return this.keyMap; + }, - if(this.header){ - this.header.unselectable(); + // 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(); + } - // 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); - } - } - } + // 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.makeFloating(this.floating); + this.el.beforeAction(); } - - if(this.collapsible && this.titleCollapse && this.header){ - this.mon(this.header, 'click', this.toggleCollapse, this); - this.header.setStyle('cursor', 'pointer'); + if(anim !== false){ + this.el.addClass('x-panel-animated'); } - if(ts){ - this.addTool.apply(this, ts); + }, + + // private + afterEffect : function(anim){ + this.syncShadow(); + this.el.removeClass('x-panel-animated'); + }, + + // private - wraps up an animation param with internal callbacks + createEffect : function(a, cb, scope){ + var o = { + scope:scope, + block:true + }; + if(a === true){ + o.callback = cb; + return o; + }else if(!a.callback){ + o.callback = cb; + }else { // wrap it up + o.callback = function(){ + cb.call(scope); + Ext.callback(a.callback, a.scope); + }; } + return Ext.applyIf(o, a); + }, - // 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'}); + /** + * 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; } - if(this.tbar && this.topToolbar){ - this.topToolbar.ownerCt = this; - this.topToolbar.render(this.tbar); + 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); } - if(this.bbar && this.bottomToolbar){ - this.bottomToolbar.ownerCt = this; - this.bottomToolbar.render(this.bbar); + }, + + // 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); }, /** - * 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 + * 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 */ - 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 - }); - } - } + expand : function(animate){ + if(!this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforeexpand', this, animate) === false){ + return; } - this.fireEvent('iconchange', this, cls, old); + var doAnim = animate === true || (animate !== false && this.animCollapse); + this.el.removeClass(this.collapsedCls); + this.beforeEffect(doAnim); + this.onExpand(doAnim, animate); + return this; }, // 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); + 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); + } }, - /** - * 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; + // 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); }, /** - * Returns the {@link Ext.Toolbar toolbar} from the bottom ({@link #bbar}) section of the panel. - * @return {Ext.Toolbar} The toolbar + * 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 */ - getBottomToolbar : function(){ - return this.bottomToolbar; + toggleCollapse : function(animate){ + this[this.collapsed ? 'expand' : 'collapse'](animate); + return this; }, - /** - * 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) + // private + onDisable : function(){ + if(this.rendered && this.maskDisabled){ + this.el.mask(); } - return this.fbar.add(config); + Ext.Panel.superclass.onDisable.call(this); }, // 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; + onEnable : function(){ + if(this.rendered && this.maskDisabled){ + this.el.unmask(); } - 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); + 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(tc.hidden){ - t.hide(); + + if(this.tbar){ + this.tbar.setWidth(w); + if(this.topToolbar){ + this.topToolbar.setSize(w); + } } - if(tc.qtip){ - if(Ext.isObject(tc.qtip)){ - Ext.QuickTips.register(Ext.apply({ - target: t.id - }, tc.qtip)); - } else { - t.dom.qtip = tc.qtip; + 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'); } } - 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; + // 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.autoHeight || !Ext.isDefined(lsh) || lsh == 'auto'){ - return; + 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); + }, - 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); - } + // private + onBodyResize: function(w, h){ + this.fireEvent('bodyresize', this, w, h); }, // private - onShow : function(){ - if(this.floating){ - return this.el.show(); + getToolbarHeight: function(){ + var h = 0; + if(this.rendered){ + Ext.each(this.toolbars, function(tb){ + h += tb.getHeight(); + }, this); } - Ext.Panel.superclass.onShow.call(this); + return h; }, - // private - onHide : function(){ - if(this.floating){ - return this.el.hide(); - } - Ext.Panel.superclass.onHide.call(this); + // deprecate + adjustBodyHeight : function(h){ + return h; }, // 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); - } - }; + adjustBodyWidth : function(w){ + return w; }, // 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(); + onPosition : function(){ + this.syncShadow(); }, - // private - getKeyMap : function(){ - if(!this.keyMap){ - this.keyMap = new Ext.KeyMap(this.el, this.keys); + /** + * 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 this.keyMap; + return w; }, - // 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(); + /** + * Returns the height in pixels of the framing elements of this panel (including any top and bottom bars and + * header and footer elements, but not including the body height). To retrieve the body height see {@link #getInnerHeight}. + * @return {Number} The frame height + */ + getFrameHeight : function() { + var h = this.el.getFrameWidth('tb') + this.bwrap.getFrameWidth('tb'); + h += (this.tbar ? this.tbar.getHeight() : 0) + + (this.bbar ? this.bbar.getHeight() : 0); + + if(this.frame){ + h += this.el.dom.firstChild.offsetHeight + this.ft.dom.offsetHeight + this.mc.getFrameWidth('tb'); + }else{ + h += (this.header ? this.header.getHeight() : 0) + + (this.footer ? this.footer.getHeight() : 0); } + return h; + }, + /** + * 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 - 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); + /** + * 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 - beforeEffect : function(anim){ + syncShadow : function(){ if(this.floating){ - this.el.beforeAction(); - } - if(anim !== false){ - this.el.addClass('x-panel-animated'); + this.el.sync(true); } }, // private - afterEffect : function(anim){ - this.syncShadow(); - if(anim !== false){ - this.el.removeClass('x-panel-animated'); - } + getLayoutTarget : function(){ + return this.body; }, - // private - wraps up an animation param with internal callbacks - createEffect : function(a, cb, scope){ - var o = { - scope:scope, - block:true - }; - if(a === true){ - o.callback = cb; - return o; - }else if(!a.callback){ - o.callback = cb; - }else { // wrap it up - o.callback = function(){ - cb.call(scope); - Ext.callback(a.callback, a.scope); - }; + // private + 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); } - return Ext.applyIf(o, a); + if(iconCls){ + this.setIconClass(iconCls); + } + this.fireEvent('titlechange', this, title); + return this; }, /** - * 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) + * 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 */ - 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); + 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)); - }else{ - this[this.collapseEl].hide(); - this.afterCollapse(false); + 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 - afterCollapse : function(anim){ - this.collapsed = true; - this.el.addClass(this.collapsedCls); - this.afterEffect(anim); - 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; + 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 - onExpand : function(doAnim, animArg){ - if(doAnim){ - this[this.collapseEl].slideIn(this.slideAnchor, - Ext.apply(this.createEffect(animArg||true, this.afterExpand, this), - this.expandDefaults)); + 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].show(); - this.afterExpand(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 - afterExpand : function(anim){ - this.collapsed = false; - this.afterEffect(anim); - if (this.deferLayout) { - delete this.deferLayout; - this.doLayout(true); + doAutoLoad : function(){ + var u = this.body.getUpdater(); + if(this.renderer){ + u.setRenderer(this.renderer); } - this.fireEvent('expand', this); + u.update(Ext.isObject(this.autoLoad) ? this.autoLoad : {url: this.autoLoad}); }, /** - * 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 + * Retrieve a tool by id. + * @param {String} id + * @return {Object} tool */ - toggleCollapse : function(animate){ - this[this.collapsed ? 'expand' : 'collapse'](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 - onDisable : function(){ - if(this.rendered && this.maskDisabled){ - this.el.mask(); + 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}); } - Ext.Panel.superclass.onDisable.call(this); - }, - - // private - onEnable : function(){ - if(this.rendered && this.maskDisabled){ - this.el.unmask(); + this.field.render(this.el).show(); + this.field.getEl().dom.name = ''; + if(this.swallowKeys){ + this.field.el.swallowEvent([ + 'keypress', // *** Opera + 'keydown' // *** all other browsers + ]); } - 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); - } - - if(this.disabled && this.el._mask){ - this.el._mask.setSize(this.el.dom.clientWidth, this.el.getHeight()); - } + 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{ - // 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.cancelEdit(); + } + if(field.triggerBlur){ + field.triggerBlur(); } - this.onBodyResize(w, h); } - this.syncShadow(); - Ext.Panel.superclass.onResize.call(this); - }, - - // private - onBodyResize: function(w, h){ - this.fireEvent('bodyresize', this, w, h); + this.fireEvent('specialkey', field, e); }, - // private - getToolbarHeight: function(){ - var h = 0; - if(this.rendered){ - Ext.each(this.toolbars, function(tb){ - h += tb.getHeight(); - }, 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. + */ + 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(); } - return h; - }, - - // private - adjustBodyHeight : function(h){ - return h; }, // private - adjustBodyWidth : function(w){ - return w; - }, + doAutoSize : function(){ + if(this.autoSize){ + var sz = this.boundEl.getSize(), + fs = this.field.getSize(); - // private - onPosition : function(){ - this.syncShadow(); + 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); + } + } }, /** - * 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 + * Sets the height and width of this editor. + * @param {Number} width The new width + * @param {Number} height The new height */ - 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'); + 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 w; }, /** - * 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 + * 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. */ - 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); + realign : function(autoSize){ + if(autoSize === true){ + this.doAutoSize(); } - return h; + this.el.alignTo(this.boundEl, this.alignment, this.offsets); }, /** - * 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 + * 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) */ - getInnerWidth : function(){ - return this.getSize().width - this.getFrameWidth(); + 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); + } + }, + + // 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(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.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(){ - // 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(); - } + 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(); - } - this.el.hide(); - if(this.hideEl !== false){ - this.boundEl.show(); + 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(); + } } }, - /** - * 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 + 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); + } }, - /** - * Gets the data value of the editor - * @return {Mixed} The data value - */ - getValue : function(){ - return this.field.getValue(); + // private + selectToday : function(){ + if(this.todayBtn && !this.todayBtn.disabled){ + this.setValue(new Date().clearTime()); + this.fireEvent('select', this, this.value); + } }, - beforeDestroy : function(){ - Ext.destroyMembers(this, 'field'); + // 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'](); + } + } - delete this.parentEl; - delete this.boundEl; + 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]); + } + } + } + }, + + // 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; - }, - - /** - * 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; - this.syncThumb(); - if(this.value < val){ - this.setValue(val); - } - }, - - /** - * 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; - this.syncThumb(); - if(this.value > val){ - this.setValue(val); - } - }, - - /** - * 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.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(); - Ext.Slider.superclass.onResize.apply(this, arguments); - }, - - //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 && !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; - }, - - /** - * 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'); - } - this.clearTimer(); - if(hide === true){ - this.hide(); - } - return this; - }, - - // private - 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); - } -}); +}); Ext.reg('progress', Ext.ProgressBar); \ No newline at end of file