X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/25ef3491bd9ae007ff1fc2b0d7943e6eaaccf775..6b044c28b5f26fb99c86c237ffad19741c0f7f3d:/pkgs/cmp-foundation-debug.js?ds=inline
diff --git a/pkgs/cmp-foundation-debug.js b/pkgs/cmp-foundation-debug.js
index 22f6c94c..f6bcd75b 100644
--- a/pkgs/cmp-foundation-debug.js
+++ b/pkgs/cmp-foundation-debug.js
@@ -1,8 +1,8 @@
/*!
- * Ext JS Library 3.0.3
- * Copyright(c) 2006-2009 Ext JS, LLC
- * licensing@extjs.com
- * http://www.extjs.com/license
+ * Ext JS Library 3.3.1
+ * Copyright(c) 2006-2010 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
*/
/**
* @class Ext.ComponentMgr
@@ -53,10 +53,10 @@ Ext.ComponentMgr = function(){
},
/**
- * Registers a function that will be called when a specified component is added to ComponentMgr
+ * Registers a function that will be called when a Component with the specified id is added to ComponentMgr. This will happen on instantiation.
* @param {String} id The component {@link Ext.Component#id id}
* @param {Function} fn The callback function
- * @param {Object} scope The scope of the callback
+ * @param {Object} scope The scope (this
reference) in which the callback is executed. Defaults to the Component.
*/
onAvailable : function(id, fn, scope){
all.on("add", function(index, o){
@@ -74,6 +74,18 @@ Ext.ComponentMgr = function(){
*/
all : all,
+ /**
+ * The xtypes that have been registered with the component manager.
+ * @type {Object}
+ */
+ types : types,
+
+ /**
+ * The ptypes that have been registered with the component manager.
+ * @type {Object}
+ */
+ ptypes: ptypes,
+
/**
* Checks if a Component type is registered.
* @param {Ext.Component} xtype The mnemonic string by which the Component class may be looked up
@@ -82,6 +94,15 @@ Ext.ComponentMgr = function(){
isRegistered : function(xtype){
return types[xtype] !== undefined;
},
+
+ /**
+ * Checks if a Plugin type is registered.
+ * @param {Ext.Component} ptype The mnemonic string by which the Plugin class may be looked up
+ * @return {Boolean} Whether the type is registered.
+ */
+ isPluginRegistered : function(ptype){
+ return ptypes[ptype] !== undefined;
+ },
/**
*
Registers a new Component constructor, keyed by a new @@ -133,7 +154,12 @@ Ext.ComponentMgr = function(){ * @return {Ext.Component} The newly instantiated Plugin. */ createPlugin : function(config, defaultType){ - return new ptypes[config.ptype || defaultType](config); + var PluginCls = ptypes[config.ptype || defaultType]; + if (PluginCls.init) { + return PluginCls; + } else { + return new PluginCls(config); + } } }; }(); @@ -200,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} @@ -236,10 +263,11 @@ menutextitem {@link Ext.menu.TextItem} Form components --------------------------------------- -form {@link Ext.FormPanel} +form {@link Ext.form.FormPanel} checkbox {@link Ext.form.Checkbox} checkboxgroup {@link Ext.form.CheckboxGroup} combo {@link Ext.form.ComboBox} +compositefield {@link Ext.form.CompositeField} datefield {@link Ext.form.DateField} displayfield {@link Ext.form.DisplayField} field {@link Ext.form.Field} @@ -305,6 +333,14 @@ Ext.Component = function(config){ Ext.apply(this, config); this.addEvents( + /** + * @event added + * Fires when a component is added to an Ext.Container + * @param {Ext.Component} this + * @param {Ext.Container} ownerCt Container which holds the component + * @param {number} index Position at which the component was added + */ + 'added', /** * @event disable * Fires after the component is disabled. @@ -344,6 +380,13 @@ Ext.Component = function(config){ * @param {Ext.Component} this */ 'hide', + /** + * @event removed + * Fires when a component is removed from an Ext.Container + * @param {Ext.Component} this + * @param {Ext.Container} ownerCt Container which holds the component + */ + 'removed', /** * @event beforerender * Fires before the component is {@link #rendered}. Return false from an @@ -439,7 +482,7 @@ Ext.Component = function(config){ } if(this.stateful !== false){ - this.initState(config); + this.initState(); } if(this.applyTo){ @@ -588,17 +631,6 @@ new Ext.FormPanel({ */ - // Configs below are used for all Components when rendered by AnchorLayout. - /** - * @cfg {String} anchor
Note: this config is only used when this Component is rendered - * by a Container which has been configured to use an {@link Ext.layout.AnchorLayout AnchorLayout} - * based layout manager, for example:
layout: 'anchor' // or 'form', or 'absolute'
See {@link Ext.layout.AnchorLayout}.{@link Ext.layout.AnchorLayout#anchor anchor} also.
- */ - /** * @cfg {String} id *The unique id of this component (defaults to an {@link #getId auto-assigned id}). @@ -950,6 +982,61 @@ new Ext.Panel({ */ rendered : false, + /** + * @cfg {String} contentEl + *
Optional. Specify an existing HTML element, or the id
of an existing HTML element to use as the content
+ * for this component.
{@link Ext.Container#layout layout}
+ * scheme that the Component may use. It is just HTML. Layouts operate on child {@link Ext.Container#items items}
.x-hidden
or the x-hide-display
CSS class to
+ * prevent a brief flicker of the content before it is rendered to the panel.{@link #data}
and
+ * {@link #tplWriteMode}
configurations.
+ */
+
+ /**
+ * @cfg {String} tplWriteMode The Ext.(X)Template method to use when
+ * updating the content area of the Component. Defaults to 'overwrite'
+ * (see {@link Ext.XTemplate#overwrite}
).
+ */
+ tplWriteMode : 'overwrite',
+
+ /**
+ * @cfg {Mixed} data
+ * The initial set of data to apply to the {@link #tpl}
to
+ * update the content area of the Component.
+ */
+
+ /**
+ * @cfg {Array} bubbleEvents
+ * An array of events that, when fired, should be bubbled to any parent container. + * See {@link Ext.util.Observable#enableBubble}. + * Defaults to []. + */ + bubbleEvents: [], + + // private ctype : 'Ext.Component', @@ -1002,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.
@@ -1065,7 +1167,32 @@ Ext.Foo = Ext.extend(Ext.Bar, { this.el.addClassOnOver(this.overCls); } this.fireEvent('render', this); + + + // Populate content of the component with html, contentEl or + // a tpl. + var contentTarget = this.getContentTarget(); + if (this.html){ + contentTarget.update(Ext.DomHelper.markup(this.html)); + delete this.html; + } + if (this.contentEl){ + var ce = Ext.getDom(this.contentEl); + Ext.fly(ce).removeClass(['x-hidden', 'x-hide-display']); + contentTarget.appendChild(ce); + } + if (this.tpl) { + if (!this.tpl.compile) { + this.tpl = new Ext.XTemplate(this.tpl); + } + if (this.data) { + this.tpl[this.tplWriteMode](contentTarget, this.data); + delete this.data; + } + } this.afterRender(this.container); + + if(this.hidden){ // call this so we don't fire initial hide events. this.doHide(); @@ -1078,17 +1205,70 @@ Ext.Foo = Ext.extend(Ext.Bar, { if(this.stateful !== false){ this.initStateEvents(); } - this.initRef(); this.fireEvent('afterrender', this); } return this; }, - initRef : function(){ + + /** + * Update the content area of a component. + * @param {Mixed} htmlOrData + * If this component has been configured with a template via the tpl config + * then it will use this argument as data to populate the template. + * If this component was not configured with a template, the components + * content area will be updated via Ext.Element update + * @param {Boolean} loadScripts + * (optional) Only legitimate when using the html configuration. Defaults to false + * @param {Function} callback + * (optional) Only legitimate when using the html configuration. Callback to execute when scripts have finished loading + */ + update: function(htmlOrData, loadScripts, cb) { + var contentTarget = this.getContentTarget(); + if (this.tpl && typeof htmlOrData !== "string") { + this.tpl[this.tplWriteMode](contentTarget, htmlOrData || {}); + } else { + var html = Ext.isObject(htmlOrData) ? Ext.DomHelper.markup(htmlOrData) : htmlOrData; + contentTarget.update(html, loadScripts, cb); + } + }, + + + /** + * @private + * Method to manage awareness of when components are added to their + * respective Container, firing an added event. + * References are established at add time rather than at render time. + * @param {Ext.Container} container Container which holds the component + * @param {number} pos Position at which the component was added + */ + onAdded : function(container, pos) { + this.ownerCt = container; + this.initRef(); + this.fireEvent('added', this, container, pos); + }, + + /** + * @private + * Method to manage awareness of when components are removed from their + * respective Container, firing an removed event. References are properly + * cleaned up after removing a component from its owning container. + */ + onRemoved : function() { + this.removeRef(); + this.fireEvent('removed', this, this.ownerCt); + delete this.ownerCt; + }, + + /** + * @private + * Method to establish a reference to a component. + */ + initRef : function() { /** * @cfg {String} ref - *A path specification, relative to the Component's {@link #ownerCt} specifying into which - * ancestor Container to place a named reference to this Component.
+ *A path specification, relative to the Component's {@link #ownerCt}
+ * specifying into which ancestor Container to place a named reference to this Component.
The ancestor axis can be traversed by using '/' characters in the path. * For example, to put a reference to a Toolbar Button into the Panel which owns the Toolbar:
var myGrid = new Ext.grid.EditorGridPanel({
@@ -1109,33 +1289,50 @@ var myGrid = new Ext.grid.EditorGridPanel({
}
});
- * In the code above, if the ref had been 'saveButton'
the reference would
- * have been placed into the Toolbar. Each '/' in the ref moves up one level from the
- * Component's {@link #ownerCt}.
In the code above, if the ref
had been 'saveButton'
+ * the reference would have been placed into the Toolbar. Each '/' in the ref
+ * moves up one level from the Component's {@link #ownerCt}
.
Also see the {@link #added}
and {@link #removed}
events.
ref
.
+ */
+ this.refOwner = t;
}
- t[levels[--i]] = this;
+ }
+ },
+
+ removeRef : function() {
+ if (this.refOwner && this.refName) {
+ delete this.refOwner[this.refName];
+ delete this.refOwner;
}
},
// private
- initState : function(config){
+ initState : function(){
if(Ext.state.Manager){
var id = this.getStateId();
if(id){
var state = Ext.state.Manager.get(id);
if(state){
if(this.fireEvent('beforestaterestore', this, state) !== false){
- this.applyState(state);
+ this.applyState(Ext.apply({}, state));
this.fireEvent('staterestore', this, state);
}
}
@@ -1145,7 +1342,7 @@ var myGrid = new Ext.grid.EditorGridPanel({
// private
getStateId : function(){
- return this.stateId || ((this.id.indexOf('ext-comp-') == 0 || this.id.indexOf('ext-gen') == 0) ? null : this.id);
+ return this.stateId || ((/^(ext-comp-|ext-gen)/).test(String(this.id)) ? null : this.id);
},
// private
@@ -1240,6 +1437,10 @@ var myGrid = new Ext.grid.EditorGridPanel({
this.el = Ext.get(this.el);
if(this.allowDomMove !== false){
ct.dom.insertBefore(this.el.dom, position);
+ if (div) {
+ Ext.removeNode(div);
+ div = null;
+ }
}
}
},
@@ -1267,23 +1468,38 @@ var myGrid = new Ext.grid.EditorGridPanel({
destroy : function(){
if(!this.isDestroyed){
if(this.fireEvent('beforedestroy', this) !== false){
+ this.destroying = true;
this.beforeDestroy();
+ if(this.ownerCt && this.ownerCt.remove){
+ this.ownerCt.remove(this, false);
+ }
if(this.rendered){
- this.el.removeAllListeners();
this.el.remove();
if(this.actionMode == 'container' || this.removeMode == 'container'){
this.container.remove();
}
}
+ // Stop any buffered tasks
+ if(this.focusTask && this.focusTask.cancel){
+ this.focusTask.cancel();
+ }
this.onDestroy();
Ext.ComponentMgr.unregister(this);
this.fireEvent('destroy', this);
this.purgeListeners();
+ this.destroying = false;
this.isDestroyed = true;
}
}
},
+ deleteMembers : function(){
+ var args = arguments;
+ for(var i = 0, len = args.length; i < len; ++i){
+ delete this[args[i]];
+ }
+ },
+
// private
beforeDestroy : Ext.emptyFn,
@@ -1315,6 +1531,11 @@ new Ext.Panel({
return this.el;
},
+ // private
+ getContentTarget : function(){
+ return this.el;
+ },
+
/**
* Returns the id
of this component or automatically generates and
* returns an id
if an id
is not defined yet:
@@ -1344,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();
@@ -1528,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.
@@ -1583,21 +1811,42 @@ 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;
},
- getDomPositionEl : function(){
- return this.getPositionEl ? this.getPositionEl() : this.getEl();
+ // protected
+ getPositionEl : function(){
+ return this.positionEl || this.el;
},
// private
@@ -1615,7 +1864,7 @@ alert(t.getXTypes()); // alerts 'component/box/field/textfield'
}, this);
this.mons = [];
},
-
+
// private
createMons: function(){
if(!this.mons){
@@ -1624,7 +1873,26 @@ alert(t.getXTypes()); // alerts 'component/box/field/textfield'
}
},
- // internal function for auto removal of assigned event handlers on destruction
+ /**
+ * Adds listeners to any Observable object (or Elements) which are automatically removed when this Component + * is destroyed. Usage:
+myGridPanel.mon(myGridPanel.getSelectionModel(), 'selectionchange', handleSelectionChange, null, {buffer: 50});
+
+ * or:
+myGridPanel.mon(myGridPanel.getSelectionModel(), {
+ selectionchange: handleSelectionChange,
+ buffer: 50
+});
+
+ * @param {Observable|Element} item The item to which to add a listener/listeners.
+ * @param {Object|String} ename The event name, or an object containing event name properties.
+ * @param {Function} fn Optional. If the ename
parameter was an event name, this
+ * is the handler function.
+ * @param {Object} scope Optional. If the ename
parameter was an event name, this
+ * is the scope (this
reference) in which the handler function is executed.
+ * @param {Object} opt Optional. If the ename
parameter was an event name, this
+ * is the {@link Ext.util.Observable#addListener addListener} options.
+ */
mon : function(item, ename, fn, scope, opt){
this.createMons();
if(Ext.isObject(ename)){
@@ -1658,7 +1926,15 @@ alert(t.getXTypes()); // alerts 'component/box/field/textfield'
item.on(ename, fn, scope, opt);
},
- // protected, opposite of mon
+ /**
+ * Removes listeners that were added by the {@link #mon} method.
+ * @param {Observable|Element} item The item from which to remove a listener/listeners.
+ * @param {Object|String} ename The event name, or an object containing event name properties.
+ * @param {Function} fn Optional. If the ename
parameter was an event name, this
+ * is the handler function.
+ * @param {Object} scope Optional. If the ename
parameter was an event name, this
+ * is the scope (this
reference) in which the handler function is executed.
+ */
mun : function(item, ename, fn, scope){
var found, mon;
this.createMons();
@@ -1712,252 +1988,253 @@ alert(t.getXTypes()); // alerts 'component/box/field/textfield'
});
Ext.reg('component', Ext.Component);
-/**
- * @class Ext.Action
- * An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it - * can be usefully shared among multiple components. Actions let you share handlers, configuration options and UI - * updates across any components that support the Action interface (primarily {@link Ext.Toolbar}, {@link Ext.Button} - * and {@link Ext.menu.Menu} components).
- *Aside from supporting the config object interface, any component that needs to use Actions must also support - * the following method list, as these will be called as needed by the Action class: setText(string), setIconCls(string), - * setDisabled(boolean), setVisible(boolean) and setHandler(function).
- * Example usage:
-// Define the shared action. Each component below will have the same
-// display text and icon, and will display the same message on click.
-var action = new Ext.Action({
- {@link #text}: 'Do something',
- {@link #handler}: function(){
- Ext.Msg.alert('Click', 'You did something.');
- },
- {@link #iconCls}: 'do-something',
- {@link #itemId}: 'myAction'
-});
-
-var panel = new Ext.Panel({
- title: 'Actions',
- width: 500,
- height: 300,
- tbar: [
- // Add the action directly to a toolbar as a menu button
- action,
- {
- text: 'Action Menu',
- // Add the action to a menu as a text item
- menu: [action]
- }
- ],
- items: [
- // Add the action to the panel body as a standard button
- new Ext.Button(action)
- ],
- renderTo: Ext.getBody()
-});
-
-// Change the text for all components using the action
-action.setText('Something else');
-
-// Reference an action through a container using the itemId
-var btn = panel.getComponent('myAction');
-var aRef = btn.baseAction;
-aRef.setText('New text');
-
- * @constructor
- * @param {Object} config The configuration options
- */
-Ext.Action = function(config){
- this.initialConfig = config;
- this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
- this.items = [];
-}
-
-Ext.Action.prototype = {
- /**
- * @cfg {String} text The text to set for all components using this action (defaults to '').
- */
- /**
- * @cfg {String} iconCls
- * The CSS class selector that specifies a background image to be used as the header icon for
- * all components using this action (defaults to '').
- * An example of specifying a custom icon class would be something like: - *
-// specify the property in the config for the class:
- ...
- iconCls: 'do-something'
-
-// css class that specifies background image to be used as the icon image:
-.do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
-
- */
- /**
- * @cfg {Boolean} disabled True to disable all components using this action, false to enable them (defaults to false).
- */
- /**
- * @cfg {Boolean} hidden True to hide all components using this action, false to show them (defaults to false).
- */
- /**
- * @cfg {Function} handler The function that will be invoked by each component tied to this action
- * when the component's primary event is triggered (defaults to undefined).
- */
- /**
- * @cfg {String} itemId
- * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}.
- */
- /**
- * @cfg {Object} scope The scope in which the {@link #handler} function will execute.
- */
-
- // private
- isAction : true,
-
- /**
- * Sets the text to be displayed by all components using this action.
- * @param {String} text The text to display
- */
- setText : function(text){
- this.initialConfig.text = text;
- this.callEach('setText', [text]);
- },
-
- /**
- * Gets the text currently displayed by all components using this action.
- */
- getText : function(){
- return this.initialConfig.text;
- },
-
- /**
- * Sets the icon CSS class for all components using this action. The class should supply
- * a background image that will be used as the icon image.
- * @param {String} cls The CSS class supplying the icon image
- */
- setIconClass : function(cls){
- this.initialConfig.iconCls = cls;
- this.callEach('setIconClass', [cls]);
- },
-
- /**
- * Gets the icon CSS class currently used by all components using this action.
- */
- getIconClass : function(){
- return this.initialConfig.iconCls;
- },
-
- /**
- * Sets the disabled state of all components using this action. Shortcut method
- * for {@link #enable} and {@link #disable}.
- * @param {Boolean} disabled True to disable the component, false to enable it
- */
- setDisabled : function(v){
- this.initialConfig.disabled = v;
- this.callEach('setDisabled', [v]);
- },
-
- /**
- * Enables all components using this action.
- */
- enable : function(){
- this.setDisabled(false);
- },
-
- /**
- * Disables all components using this action.
- */
- disable : function(){
- this.setDisabled(true);
- },
-
- /**
- * Returns true if the components using this action are currently disabled, else returns false.
- */
- isDisabled : function(){
- return this.initialConfig.disabled;
- },
-
- /**
- * Sets the hidden state of all components using this action. Shortcut method
- * for {@link #hide}
and {@link #show}
.
- * @param {Boolean} hidden True to hide the component, false to show it
- */
- setHidden : function(v){
- this.initialConfig.hidden = v;
- this.callEach('setVisible', [!v]);
- },
-
- /**
- * Shows all components using this action.
- */
- show : function(){
- this.setHidden(false);
- },
-
- /**
- * Hides all components using this action.
- */
- hide : function(){
- this.setHidden(true);
- },
-
- /**
- * Returns true if the components using this action are currently hidden, else returns false.
- */
- isHidden : function(){
- return this.initialConfig.hidden;
- },
-
- /**
- * Sets the function that will be called by each component using this action when its primary event is triggered.
- * @param {Function} fn The function that will be invoked by the action's components. The function
- * will be called with no arguments.
- * @param {Object} scope The scope in which the function will execute
- */
- setHandler : function(fn, scope){
- this.initialConfig.handler = fn;
- this.initialConfig.scope = scope;
- this.callEach('setHandler', [fn, scope]);
- },
-
- /**
- * Executes the specified function once for each component currently tied to this action. The function passed
- * in should accept a single argument that will be an object that supports the basic Action config/method interface.
- * @param {Function} fn The function to execute for each component
- * @param {Object} scope The scope in which the function will execute
- */
- each : function(fn, scope){
- Ext.each(this.items, fn, scope);
- },
-
- // private
- callEach : function(fnName, args){
- var cs = this.items;
- for(var i = 0, len = cs.length; i < len; i++){
- cs[i][fnName].apply(cs[i], args);
- }
- },
-
- // private
- addComponent : function(comp){
- this.items.push(comp);
- comp.on('destroy', this.removeComponent, this);
- },
-
- // private
- removeComponent : function(comp){
- this.items.remove(comp);
- },
-
- /**
- * Executes this action manually using the handler function specified in the original config object
- * or the handler function set with {@link #setHandler}
. Any arguments passed to this
- * function will be passed on to the handler function.
- * @param {Mixed} arg1 (optional) Variable number of arguments passed to the handler function
- * @param {Mixed} arg2 (optional)
- * @param {Mixed} etc... (optional)
- */
- execute : function(){
- this.initialConfig.handler.apply(this.initialConfig.scope || window, arguments);
- }
-};
+/**
+ * @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
@@ -1981,9 +2258,10 @@ Ext.Action.prototype = {
(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){
@@ -2082,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);
}
-
}
},
@@ -2128,7 +2404,7 @@ Ext.extend(Ext.Layer, Ext.Element, {
}
this.removeAllListeners();
Ext.removeNode(this.dom);
- Ext.Element.uncache(this.id);
+ delete this.dom;
},
remove : function(){
@@ -2200,6 +2476,10 @@ Ext.extend(Ext.Layer, Ext.Element, {
}
return this;
},
+
+ getConstrainOffset : function(){
+ return this.shadowOffset;
+ },
isVisible : function(){
return this.visible;
@@ -2417,7 +2697,8 @@ Ext.extend(Ext.Layer, Ext.Element, {
return this;
}
});
-})();/**
+})();
+/**
* @class Ext.Shadow
* Simple class that can provide a shadow effect for any element. Note that the element MUST be absolutely positioned,
* and the shadow does not provide any shimming. This should be used only in simple cases -- for more advanced
@@ -2426,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;
@@ -2447,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;
@@ -2500,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";
},
@@ -2524,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;
},
/**
@@ -2536,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";
}
}
},
@@ -2562,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;
@@ -2574,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 ?
- '' :
- 'Note: this config is only used when this Component is rendered
+ * by a Container which has been configured to use a {@link Ext.layout.BoxLayout BoxLayout}.
+ * Each child Component with a Note: this config is only used when this Component is rendered
+ * by a Container which has been configured to use an {@link Ext.layout.AnchorLayout AnchorLayout} (or subclass thereof).
+ * based layout manager, for example:flex
property will be flexed either vertically (by a VBoxLayout)
+ * or horizontally (by an HBoxLayout) according to the item's relative flex
value
+ * compared to the sum of all Components with flex value specified. Any child items that have
+ * either a
flex = 0
or flex = undefined
will not be 'flexed' (the initial size will not be changed).
+ */
+ // Configs below are used for all Components when rendered by AnchorLayout.
+ /**
+ * @cfg {String} anchor
+ *
layout: 'anchor' // or 'form', or 'absolute'
See {@link Ext.layout.AnchorLayout}.{@link Ext.layout.AnchorLayout#anchor anchor} also.
+ */ // tabTip config is used when a BoxComponent is a child of a TabPanel /** * @cfg {String} tabTip @@ -2700,6 +3012,26 @@ Ext.BoxComponent = Ext.extend(Ext.Component, { * The width of this component in pixels (defaults to auto). * Note to express this dimension as a percentage or offset see {@link Ext.Component#anchor}. */ + /** + * @cfg {Number} boxMinHeight + *The minimum value in pixels which this BoxComponent will set its height to.
+ *Warning: This will override any size management applied by layout managers.
+ */ + /** + * @cfg {Number} boxMinWidth + *The minimum value in pixels which this BoxComponent will set its width to.
+ *Warning: This will override any size management applied by layout managers.
+ */ + /** + * @cfg {Number} boxMaxHeight + *The maximum value in pixels which this BoxComponent will set its height to.
+ *Warning: This will override any size management applied by layout managers.
+ */ + /** + * @cfg {Number} boxMaxWidth + *The maximum value in pixels which this BoxComponent will set its width to.
+ *Warning: This will override any size management applied by layout managers.
+ */ /** * @cfg {Boolean} autoHeight *True to use height:'auto', false to use fixed height (or allow it to be managed by its parent
@@ -2774,6 +3106,11 @@ var myPanel = new Ext.Panel({
});
*/
+ /**
+ * @cfg {Boolean} autoScroll
+ * true
to use overflow:'auto' on the components layout element and show scroll bars automatically when
+ * necessary, false
to clip any overflowing content (defaults to false
).
+ */
/* // private internal config
* {Boolean} deferHeight
@@ -2829,14 +3166,27 @@ var myPanel = new Ext.Panel({
* @return {Ext.BoxComponent} this
*/
setSize : function(w, h){
+
// support for standard size objects
if(typeof w == 'object'){
h = w.height;
w = w.width;
}
+ if (Ext.isDefined(w) && Ext.isDefined(this.boxMinWidth) && (w < this.boxMinWidth)) {
+ w = this.boxMinWidth;
+ }
+ if (Ext.isDefined(h) && Ext.isDefined(this.boxMinHeight) && (h < this.boxMinHeight)) {
+ h = this.boxMinHeight;
+ }
+ if (Ext.isDefined(w) && Ext.isDefined(this.boxMaxWidth) && (w > this.boxMaxWidth)) {
+ w = this.boxMaxWidth;
+ }
+ if (Ext.isDefined(h) && Ext.isDefined(this.boxMaxHeight) && (h > this.boxMaxHeight)) {
+ h = this.boxMaxHeight;
+ }
// not rendered
if(!this.boxReady){
- this.width = w;
+ this.width = w;
this.height = h;
return this;
}
@@ -2846,10 +3196,12 @@ var myPanel = new Ext.Panel({
return this;
}
this.lastSize = {width: w, height: h};
- var adj = this.adjustSize(w, h);
- var aw = adj.width, ah = adj.height;
+ var adj = this.adjustSize(w, h),
+ aw = adj.width,
+ ah = adj.height,
+ rz;
if(aw !== undefined || ah !== undefined){ // this code is nasty but performs better with floaters
- var rz = this.getResizeEl();
+ rz = this.getResizeEl();
if(!this.deferHeight && aw !== undefined && ah !== undefined){
rz.setSize(aw, ah);
}else if(!this.deferHeight && ah !== undefined){
@@ -2865,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:
<input>
Element (which is what would be returned
* by its {@link #getEl}
method, and the trigger button Element.
* This Element is returned as the resizeEl
.
+ * @return {Ext.Element} The Element which is to be resized by size managing layouts.
*/
getResizeEl : function(){
return this.resizeEl || this.el;
},
- // protected
- getPositionEl : function(){
- return this.positionEl || this.el;
+ /**
+ * Sets the overflow on the content element of the component.
+ * @param {Boolean} scroll True to allow the Component to auto scroll.
+ * @return {Ext.BoxComponent} this
+ */
+ setAutoScroll : function(scroll){
+ if(this.rendered){
+ this.getContentTarget().setOverflow(scroll ? 'auto' : '');
+ }
+ this.autoScroll = scroll;
+ return this;
},
/**
@@ -3048,6 +3409,7 @@ var myPanel = new Ext.Panel({
this.positionEl = Ext.get(this.positionEl);
}
this.boxReady = true;
+ Ext.isDefined(this.autoScroll) && this.setAutoScroll(this.autoScroll);
this.setSize(this.width, this.height);
if(this.x || this.y){
this.setPosition(this.x, this.y);
@@ -3075,7 +3437,6 @@ var myPanel = new Ext.Panel({
* @param {Number} rawHeight The height that was originally specified
*/
onResize : function(adjWidth, adjHeight, rawWidth, rawHeight){
-
},
/* // protected
@@ -3117,3515 +3478,2371 @@ Ext.reg('box', Ext.BoxComponent);
Ext.Spacer = Ext.extend(Ext.BoxComponent, {
autoEl:'div'
});
-Ext.reg('spacer', Ext.Spacer);/**
- * @class Ext.SplitBar
- * @extends Ext.util.Observable
- * Creates draggable splitter bar functionality from two elements (element to be dragged and element to be resized).
- *
-var split = new Ext.SplitBar("elementToDrag", "elementToSize",
- Ext.SplitBar.HORIZONTAL, Ext.SplitBar.LEFT);
-split.setAdapter(new Ext.SplitBar.AbsoluteLayoutAdapter("container"));
-split.minSize = 100;
-split.maxSize = 600;
-split.animate = true;
-split.on('moved', splitterMoved);
-
- * @constructor
- * Create a new SplitBar
- * @param {Mixed} dragElement The element to be dragged and act as the SplitBar.
- * @param {Mixed} resizingElement The element to be resized based on where the SplitBar element is dragged
- * @param {Number} orientation (optional) Either Ext.SplitBar.HORIZONTAL or Ext.SplitBar.VERTICAL. (Defaults to HORIZONTAL)
- * @param {Number} placement (optional) Either Ext.SplitBar.LEFT or Ext.SplitBar.RIGHT for horizontal or
- Ext.SplitBar.TOP or Ext.SplitBar.BOTTOM for vertical. (By default, this is determined automatically by the initial
- position of the SplitBar).
- */
-Ext.SplitBar = function(dragElement, resizingElement, orientation, placement, existingProxy){
-
- /** @private */
- this.el = Ext.get(dragElement, true);
- this.el.dom.unselectable = "on";
- /** @private */
- this.resizingEl = Ext.get(resizingElement, true);
-
- /**
- * @private
- * The orientation of the split. Either Ext.SplitBar.HORIZONTAL or Ext.SplitBar.VERTICAL. (Defaults to HORIZONTAL)
- * Note: If this is changed after creating the SplitBar, the placement property must be manually updated
- * @type Number
- */
- this.orientation = orientation || Ext.SplitBar.HORIZONTAL;
-
- /**
- * The increment, in pixels by which to move this SplitBar. When undefined, the SplitBar moves smoothly.
- * @type Number
- * @property tickSize
- */
- /**
- * The minimum size of the resizing element. (Defaults to 0)
- * @type Number
- */
- this.minSize = 0;
-
- /**
- * The maximum size of the resizing element. (Defaults to 2000)
- * @type Number
- */
- this.maxSize = 2000;
-
- /**
- * Whether to animate the transition to the new size
- * @type Boolean
- */
- this.animate = false;
-
- /**
- * Whether to create a transparent shim that overlays the page when dragging, enables dragging across iframes.
- * @type Boolean
- */
- this.useShim = false;
-
- /** @private */
- this.shim = null;
-
- if(!existingProxy){
- /** @private */
- this.proxy = Ext.SplitBar.createProxy(this.orientation);
- }else{
- this.proxy = Ext.get(existingProxy).dom;
- }
- /** @private */
- this.dd = new Ext.dd.DDProxy(this.el.dom.id, "XSplitBars", {dragElId : this.proxy.id});
-
- /** @private */
- this.dd.b4StartDrag = this.onStartProxyDrag.createDelegate(this);
-
- /** @private */
- this.dd.endDrag = this.onEndProxyDrag.createDelegate(this);
-
- /** @private */
- this.dragSpecs = {};
-
- /**
- * @private The adapter to use to positon and resize elements
- */
- this.adapter = new Ext.SplitBar.BasicLayoutAdapter();
- this.adapter.init(this);
-
- if(this.orientation == Ext.SplitBar.HORIZONTAL){
- /** @private */
- this.placement = placement || (this.el.getX() > this.resizingEl.getX() ? Ext.SplitBar.LEFT : Ext.SplitBar.RIGHT);
- this.el.addClass("x-splitbar-h");
- }else{
- /** @private */
- this.placement = placement || (this.el.getY() > this.resizingEl.getY() ? Ext.SplitBar.TOP : Ext.SplitBar.BOTTOM);
- this.el.addClass("x-splitbar-v");
- }
-
- this.addEvents(
- /**
- * @event resize
- * Fires when the splitter is moved (alias for {@link #moved})
- * @param {Ext.SplitBar} this
- * @param {Number} newSize the new width or height
- */
- "resize",
- /**
- * @event moved
- * Fires when the splitter is moved
- * @param {Ext.SplitBar} this
- * @param {Number} newSize the new width or height
- */
- "moved",
- /**
- * @event beforeresize
- * Fires before the splitter is dragged
- * @param {Ext.SplitBar} this
- */
- "beforeresize",
-
- "beforeapply"
- );
-
- Ext.SplitBar.superclass.constructor.call(this);
-};
-
-Ext.extend(Ext.SplitBar, Ext.util.Observable, {
- onStartProxyDrag : function(x, y){
- this.fireEvent("beforeresize", this);
- this.overlay = Ext.DomHelper.append(document.body, {cls: "x-drag-overlay", html: " "}, true);
- this.overlay.unselectable();
- this.overlay.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
- this.overlay.show();
- Ext.get(this.proxy).setDisplayed("block");
- var size = this.adapter.getElementSize(this);
- this.activeMinSize = this.getMinimumSize();
- this.activeMaxSize = this.getMaximumSize();
- var c1 = size - this.activeMinSize;
- var c2 = Math.max(this.activeMaxSize - size, 0);
- if(this.orientation == Ext.SplitBar.HORIZONTAL){
- this.dd.resetConstraints();
- this.dd.setXConstraint(
- this.placement == Ext.SplitBar.LEFT ? c1 : c2,
- this.placement == Ext.SplitBar.LEFT ? c2 : c1,
- this.tickSize
- );
- this.dd.setYConstraint(0, 0);
- }else{
- this.dd.resetConstraints();
- this.dd.setXConstraint(0, 0);
- this.dd.setYConstraint(
- this.placement == Ext.SplitBar.TOP ? c1 : c2,
- this.placement == Ext.SplitBar.TOP ? c2 : c1,
- this.tickSize
- );
- }
- this.dragSpecs.startSize = size;
- this.dragSpecs.startPoint = [x, y];
- Ext.dd.DDProxy.prototype.b4StartDrag.call(this.dd, x, y);
- },
-
- /**
- * @private Called after the drag operation by the DDProxy
- */
- onEndProxyDrag : function(e){
- Ext.get(this.proxy).setDisplayed(false);
- var endPoint = Ext.lib.Event.getXY(e);
- if(this.overlay){
- Ext.destroy(this.overlay);
- delete this.overlay;
- }
- var newSize;
- if(this.orientation == Ext.SplitBar.HORIZONTAL){
- newSize = this.dragSpecs.startSize +
- (this.placement == Ext.SplitBar.LEFT ?
- endPoint[0] - this.dragSpecs.startPoint[0] :
- this.dragSpecs.startPoint[0] - endPoint[0]
- );
- }else{
- newSize = this.dragSpecs.startSize +
- (this.placement == Ext.SplitBar.TOP ?
- endPoint[1] - this.dragSpecs.startPoint[1] :
- this.dragSpecs.startPoint[1] - endPoint[1]
- );
- }
- newSize = Math.min(Math.max(newSize, this.activeMinSize), this.activeMaxSize);
- if(newSize != this.dragSpecs.startSize){
- if(this.fireEvent('beforeapply', this, newSize) !== false){
- this.adapter.setElementSize(this, newSize);
- this.fireEvent("moved", this, newSize);
- this.fireEvent("resize", this, newSize);
- }
- }
- },
-
- /**
- * Get the adapter this SplitBar uses
- * @return The adapter object
- */
- getAdapter : function(){
- return this.adapter;
- },
-
- /**
- * Set the adapter this SplitBar uses
- * @param {Object} adapter A SplitBar adapter object
- */
- setAdapter : function(adapter){
- this.adapter = adapter;
- this.adapter.init(this);
- },
-
- /**
- * Gets the minimum size for the resizing element
- * @return {Number} The minimum size
- */
- getMinimumSize : function(){
- return this.minSize;
- },
-
- /**
- * Sets the minimum size for the resizing element
- * @param {Number} minSize The minimum size
- */
- setMinimumSize : function(minSize){
- this.minSize = minSize;
- },
-
- /**
- * Gets the maximum size for the resizing element
- * @return {Number} The maximum size
- */
- getMaximumSize : function(){
- return this.maxSize;
- },
-
- /**
- * Sets the maximum size for the resizing element
- * @param {Number} maxSize The maximum size
- */
- setMaximumSize : function(maxSize){
- this.maxSize = maxSize;
- },
-
- /**
- * Sets the initialize size for the resizing element
- * @param {Number} size The initial size
- */
- setCurrentSize : function(size){
- var oldAnimate = this.animate;
- this.animate = false;
- this.adapter.setElementSize(this, size);
- this.animate = oldAnimate;
- },
-
- /**
- * Destroy this splitbar.
- * @param {Boolean} removeEl True to remove the element
- */
- destroy : function(removeEl){
- Ext.destroy(this.shim, Ext.get(this.proxy));
- this.dd.unreg();
- if(removeEl){
- this.el.remove();
- }
- this.purgeListeners();
- }
-});
-
-/**
- * @private static Create our own proxy element element. So it will be the same same size on all browsers, we won't use borders. Instead we use a background color.
- */
-Ext.SplitBar.createProxy = function(dir){
- var proxy = new Ext.Element(document.createElement("div"));
- proxy.unselectable();
- var cls = 'x-splitbar-proxy';
- proxy.addClass(cls + ' ' + (dir == Ext.SplitBar.HORIZONTAL ? cls +'-h' : cls + '-v'));
- document.body.appendChild(proxy.dom);
- return proxy.dom;
-};
-
-/**
- * @class Ext.SplitBar.BasicLayoutAdapter
- * Default Adapter. It assumes the splitter and resizing element are not positioned
- * elements and only gets/sets the width of the element. Generally used for table based layouts.
- */
-Ext.SplitBar.BasicLayoutAdapter = function(){
-};
-
-Ext.SplitBar.BasicLayoutAdapter.prototype = {
- // do nothing for now
- init : function(s){
-
- },
- /**
- * Called before drag operations to get the current size of the resizing element.
- * @param {Ext.SplitBar} s The SplitBar using this adapter
- */
- getElementSize : function(s){
- if(s.orientation == Ext.SplitBar.HORIZONTAL){
- return s.resizingEl.getWidth();
- }else{
- return s.resizingEl.getHeight();
- }
- },
-
- /**
- * Called after drag operations to set the size of the resizing element.
- * @param {Ext.SplitBar} s The SplitBar using this adapter
- * @param {Number} newSize The new size to set
- * @param {Function} onComplete A function to be invoked when resizing is complete
- */
- setElementSize : function(s, newSize, onComplete){
- if(s.orientation == Ext.SplitBar.HORIZONTAL){
- if(!s.animate){
- s.resizingEl.setWidth(newSize);
- if(onComplete){
- onComplete(s, newSize);
- }
- }else{
- s.resizingEl.setWidth(newSize, true, .1, onComplete, 'easeOut');
- }
- }else{
-
- if(!s.animate){
- s.resizingEl.setHeight(newSize);
- if(onComplete){
- onComplete(s, newSize);
- }
- }else{
- s.resizingEl.setHeight(newSize, true, .1, onComplete, 'easeOut');
- }
- }
- }
-};
-
-/**
- *@class Ext.SplitBar.AbsoluteLayoutAdapter
- * @extends Ext.SplitBar.BasicLayoutAdapter
- * Adapter that moves the splitter element to align with the resized sizing element.
- * Used with an absolute positioned SplitBar.
- * @param {Mixed} container The container that wraps around the absolute positioned content. If it's
- * document.body, make sure you assign an id to the body element.
- */
-Ext.SplitBar.AbsoluteLayoutAdapter = function(container){
- this.basic = new Ext.SplitBar.BasicLayoutAdapter();
- this.container = Ext.get(container);
-};
-
-Ext.SplitBar.AbsoluteLayoutAdapter.prototype = {
- init : function(s){
- this.basic.init(s);
- },
-
- getElementSize : function(s){
- return this.basic.getElementSize(s);
- },
-
- setElementSize : function(s, newSize, onComplete){
- this.basic.setElementSize(s, newSize, this.moveSplitter.createDelegate(this, [s]));
- },
-
- moveSplitter : function(s){
- var yes = Ext.SplitBar;
- switch(s.placement){
- case yes.LEFT:
- s.el.setX(s.resizingEl.getRight());
- break;
- case yes.RIGHT:
- s.el.setStyle("right", (this.container.getWidth() - s.resizingEl.getLeft()) + "px");
- break;
- case yes.TOP:
- s.el.setY(s.resizingEl.getBottom());
- break;
- case yes.BOTTOM:
- s.el.setY(s.resizingEl.getTop() - s.el.getHeight());
- break;
- }
- }
-};
-
-/**
- * Orientation constant - Create a vertical SplitBar
- * @static
- * @type Number
- */
-Ext.SplitBar.VERTICAL = 1;
-
-/**
- * Orientation constant - Create a horizontal SplitBar
- * @static
- * @type Number
- */
-Ext.SplitBar.HORIZONTAL = 2;
-
-/**
- * Placement constant - The resizing element is to the left of the splitter element
- * @static
- * @type Number
- */
-Ext.SplitBar.LEFT = 1;
-
-/**
- * Placement constant - The resizing element is to the right of the splitter element
- * @static
- * @type Number
- */
-Ext.SplitBar.RIGHT = 2;
-
-/**
- * Placement constant - The resizing element is positioned above the splitter element
- * @static
- * @type Number
- */
-Ext.SplitBar.TOP = 3;
-
-/**
- * Placement constant - The resizing element is positioned under splitter element
- * @static
- * @type Number
- */
-Ext.SplitBar.BOTTOM = 4;
-/**
- * @class Ext.Container
- * @extends Ext.BoxComponent
- * Base class for any {@link Ext.BoxComponent} that may contain other Components. Containers handle the - * basic behavior of containing items, namely adding, inserting and removing items.
- * - *The most commonly used Container classes are {@link Ext.Panel}, {@link Ext.Window} and {@link Ext.TabPanel}. - * If you do not need the capabilities offered by the aforementioned classes you can create a lightweight - * Container to be encapsulated by an HTML element to your specifications by using the - * {@link Ext.Component#autoEl autoEl} config option. This is a useful technique when creating - * embedded {@link Ext.layout.ColumnLayout column} layouts inside {@link Ext.form.FormPanel FormPanels} - * for example.
- * - *The code below illustrates both how to explicitly create a Container, and how to implicitly - * create one using the 'container' xtype:
-// explicitly create a Container
-var embeddedColumns = new Ext.Container({
- autoEl: 'div', // This is the default
- layout: 'column',
- defaults: {
- // implicitly create Container by specifying xtype
- xtype: 'container',
- autoEl: 'div', // This is the default.
- layout: 'form',
- columnWidth: 0.5,
- style: {
- padding: '10px'
- }
- },
-// The two items below will be Ext.Containers, each encapsulated by a <DIV> element.
- items: [{
- items: {
- xtype: 'datefield',
- name: 'startDate',
- fieldLabel: 'Start date'
- }
- }, {
- items: {
- xtype: 'datefield',
- name: 'endDate',
- fieldLabel: 'End date'
- }
- }]
-});
- *
- * Layout
- *Container classes delegate the rendering of child Components to a layout
- * manager class which must be configured into the Container using the
- * {@link #layout}
configuration property.
When either specifying child {@link #items}
of a Container,
- * or dynamically {@link #add adding} Components to a Container, remember to
- * consider how you wish the Container to arrange those child elements, and
- * whether those child elements need to be sized using one of Ext's built-in
- * {@link #layout}
schemes. By default, Containers use the
- * {@link Ext.layout.ContainerLayout ContainerLayout} scheme which only
- * renders child components, appending them one after the other inside the
- * Container, and does not apply any sizing at all.
A common mistake is when a developer neglects to specify a
- * {@link #layout}
(e.g. widgets like GridPanels or
- * TreePanels are added to Containers for which no {@link #layout}
- * has been specified). If a Container is left to use the default
- * {@link Ext.layout.ContainerLayout ContainerLayout} scheme, none of its
- * child components will be resized, or changed in any way when the Container
- * is resized.
Certain layout managers allow dynamic addition of child components. - * Those that do include {@link Ext.layout.CardLayout}, - * {@link Ext.layout.AnchorLayout}, {@link Ext.layout.FormLayout}, and - * {@link Ext.layout.TableLayout}. For example:
-// Create the GridPanel.
-var myNewGrid = new Ext.grid.GridPanel({
- store: myStore,
- columns: myColumnModel,
- title: 'Results', // the title becomes the title of the tab
-});
-
-myTabPanel.add(myNewGrid); // {@link Ext.TabPanel} implicitly uses {@link Ext.layout.CardLayout CardLayout}
-myTabPanel.{@link Ext.TabPanel#setActiveTab setActiveTab}(myNewGrid);
- *
- * The example above adds a newly created GridPanel to a TabPanel. Note that - * a TabPanel uses {@link Ext.layout.CardLayout} as its layout manager which - * means all its child items are sized to {@link Ext.layout.FitLayout fit} - * exactly into its client area. - *
Overnesting is a common problem. - * An example of overnesting occurs when a GridPanel is added to a TabPanel - * by wrapping the GridPanel inside a wrapping Panel (that has no - * {@link #layout} specified) and then add that wrapping Panel - * to the TabPanel. The point to realize is that a GridPanel is a - * Component which can be added directly to a Container. If the wrapping Panel - * has no {@link #layout} configuration, then the overnested - * GridPanel will not be sized as expected.
- * - *
Adding via remote configuration
- * - *A server side script can be used to add Components which are generated dynamically on the server. - * An example of adding a GridPanel to a TabPanel where the GridPanel is generated by the server - * based on certain parameters: - *
-// execute an Ajax request to invoke server side script:
-Ext.Ajax.request({
- url: 'gen-invoice-grid.php',
- // send additional parameters to instruct server script
- params: {
- startDate: Ext.getCmp('start-date').getValue(),
- endDate: Ext.getCmp('end-date').getValue()
- },
- // process the response object to add it to the TabPanel:
- success: function(xhr) {
- var newComponent = eval(xhr.responseText); // see discussion below
- myTabPanel.add(newComponent); // add the component to the TabPanel
- myTabPanel.setActiveTab(newComponent);
- },
- failure: function() {
- Ext.Msg.alert("Grid create failed", "Server communication failure");
- }
-});
-
- * The server script needs to return an executable Javascript statement which, when processed - * using eval(), will return either a config object with an {@link Ext.Component#xtype xtype}, - * or an instantiated Component. The server might return this for example:
-(function() {
- function formatDate(value){
- return value ? value.dateFormat('M d, Y') : '';
- };
-
- var store = new Ext.data.Store({
- url: 'get-invoice-data.php',
- baseParams: {
- startDate: '01/01/2008',
- endDate: '01/31/2008'
- },
- reader: new Ext.data.JsonReader({
- record: 'transaction',
- idProperty: 'id',
- totalRecords: 'total'
- }, [
- 'customer',
- 'invNo',
- {name: 'date', type: 'date', dateFormat: 'm/d/Y'},
- {name: 'value', type: 'float'}
- ])
- });
-
- var grid = new Ext.grid.GridPanel({
- title: 'Invoice Report',
- bbar: new Ext.PagingToolbar(store),
- store: store,
- columns: [
- {header: "Customer", width: 250, dataIndex: 'customer', sortable: true},
- {header: "Invoice Number", width: 120, dataIndex: 'invNo', sortable: true},
- {header: "Invoice Date", width: 100, dataIndex: 'date', renderer: formatDate, sortable: true},
- {header: "Value", width: 120, dataIndex: 'value', renderer: 'usMoney', sortable: true}
- ],
- });
- store.load();
- return grid; // return instantiated component
-})();
-
- * When the above code fragment is passed through the eval function in the success handler - * of the Ajax request, the code is executed by the Javascript processor, and the anonymous function - * runs, and returns the instantiated grid component.
- *Note: since the code above is generated by a server script, the baseParams for - * the Store, the metadata to allow generation of the Record layout, and the ColumnModel - * can all be generated into the code since these are all known on the server.
- * - * @xtype container - */ -Ext.Container = Ext.extend(Ext.BoxComponent, { - /** - * @cfg {Boolean} monitorResize - * True to automatically monitor window resize events to handle anything that is sensitive to the current size - * of the viewport. This value is typically managed by the chosen{@link #layout}
and should not need
- * to be set manually.
- */
- /**
- * @cfg {String/Object} layout
- * *Important: In order for child items to be correctly sized and
- * positioned, typically a layout manager must be specified through
- * the layout
configuration option.
The sizing and positioning of child {@link items} is the responsibility of - * the Container's layout manager which creates and manages the type of layout - * you have in mind. For example:
-new Ext.Window({
- width:300, height: 300,
- layout: 'fit', // explicitly set layout manager: override the default (layout:'auto')
- items: [{
- title: 'Panel inside a Window'
- }]
-}).show();
- *
- * If the {@link #layout} configuration is not explicitly specified for
- * a general purpose container (e.g. Container or Panel) the
- * {@link Ext.layout.ContainerLayout default layout manager} will be used
- * which does nothing but render child components sequentially into the
- * Container (no sizing or positioning will be performed in this situation).
- * Some container classes implicitly specify a default layout
- * (e.g. FormPanel specifies layout:'form'
). Other specific
- * purpose classes internally specify/manage their internal layout (e.g.
- * GridPanel, TabPanel, TreePanel, Toolbar, Menu, etc.).
layout
may be specified as either as an Object or
- * as a String:
-layout: {
- type: 'vbox',
- padding: '5',
- align: 'left'
-}
-
- *
- * The layout type to be used for this container. If not specified, - * a default {@link Ext.layout.ContainerLayout} will be created and used.
- *Valid layout type values are:
- *Additional layout specific configuration properties may also be - * specified. For complete details regarding the valid config options for - * each layout type, see the layout class corresponding to the type - * specified.
- * - *
-layout: 'vbox',
-layoutConfig: {
- padding: '5',
- align: 'left'
-}
-
- * The layout type to be used for this container (see list - * of valid layout type values above).
Additional layout specific configuration properties. For complete - * details regarding the valid config options for each layout type, see the - * layout class corresponding to the layout specified.
- *{@link #layout}
if {@link #layout}
- * has been specified as a string.
- */
- /**
- * @cfg {Boolean/Number} bufferResize
- * When set to true (50 milliseconds) or a number of milliseconds, the layout assigned for this container will buffer
- * the frequency it calculates and does a re-layout of components. This is useful for heavy containers or containers
- * with a large quantity of sub-components for which frequent layout calls would be expensive. Defaults to 50.
- */
- bufferResize: 50,
-
- /**
- * @cfg {String/Number} activeItem
- * A string component id or the numeric index of the component that should be initially activated within the
- * container's layout on render. For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
- * item in the container's collection). activeItem only applies to layout styles that can display
- * items one at a time (like {@link Ext.layout.AccordionLayout}, {@link Ext.layout.CardLayout} and
- * {@link Ext.layout.FitLayout}). Related to {@link Ext.layout.ContainerLayout#activeItem}.
- */
- /**
- * @cfg {Object/Array} items
- * ** IMPORTANT: be sure to {@link #layout specify a layout
} if needed ! **
- * A single item, or an array of child Components to be added to this container, - * for example:
- *
-// specifying a single item
-items: {...},
-layout: 'fit', // specify a layout!
-
-// specifying multiple items
-items: [{...}, {...}],
-layout: 'anchor', // specify a layout!
- *
- * Each item may be:
- *{@link Ext.Component#xtype xtype}
{@link Ext.Component#xtype xtype}
is not explicitly
- * specified, the {@link #defaultType} for that Container is used.Notes:
- *{@link Ext.Panel#contentEl contentEl}
/
- * {@link Ext.Panel#html html}
with items
.A config object that will be applied to all components added to this container either via the {@link #items} - * config or via the {@link #add} or {@link #insert} methods. The defaults config can contain any - * number of name/value property pairs to be added to each item, and should be valid for the types of items - * being added to the container. For example, to automatically apply padding to the body of each of a set of - * contained {@link Ext.Panel} items, you could pass: defaults: {bodyStyle:'padding:15px'}.
Note: defaults will not be applied to config objects if the option is already specified. - * For example:
-defaults: { // defaults are applied to items, not the container
- autoScroll:true
-},
-items: [
- {
- xtype: 'panel', // defaults do not have precedence over
- id: 'panel1', // options in config objects, so the defaults
- autoScroll: false // will not be applied here, panel1 will be autoScroll:false
- },
- new Ext.Panel({ // defaults do have precedence over options
- id: 'panel2', // options in components, so the defaults
- autoScroll: false // will be applied here, panel2 will be autoScroll:true.
- })
-]
- *
- */
-
-
- /** @cfg {Boolean} autoDestroy
- * If true the container will automatically destroy any contained component that is removed from it, else
- * destruction must be handled manually (defaults to true).
- */
- autoDestroy : true,
-
- /** @cfg {Boolean} forceLayout
- * If true the container will force a layout initially even if hidden or collapsed. This option
- * is useful for forcing forms to render in collapsed or hidden containers. (defaults to false).
- */
- forceLayout: false,
-
- /** @cfg {Boolean} hideBorders
- * True to hide the borders of each contained component, false to defer to the component's existing
- * border settings (defaults to false).
- */
- /** @cfg {String} defaultType
- * The default {@link Ext.Component xtype} of child Components to create in this Container when - * a child item is specified as a raw configuration object, rather than as an instantiated Component.
- *Defaults to 'panel', except {@link Ext.menu.Menu} which defaults to 'menuitem', - * and {@link Ext.Toolbar} and {@link Ext.ButtonGroup} which default to 'button'.
- */ - defaultType : 'panel', - - /** @cfg {String} resizeEvent - * The event to listen to for resizing in layouts. Defaults to 'resize'. - */ - resizeEvent: 'resize', - - /** - * @cfg {Array} bubbleEvents - *An array of events that, when fired, should be bubbled to any parent container. - * Defaults to ['add', 'remove']. - */ - bubbleEvents: ['add', 'remove'], - - // private - initComponent : function(){ - Ext.Container.superclass.initComponent.call(this); - - this.addEvents( - /** - * @event afterlayout - * Fires when the components in this container are arranged by the associated layout manager. - * @param {Ext.Container} this - * @param {ContainerLayout} layout The ContainerLayout implementation for this container - */ - 'afterlayout', - /** - * @event beforeadd - * Fires before any {@link Ext.Component} is added or inserted into the container. - * A handler can return false to cancel the add. - * @param {Ext.Container} this - * @param {Ext.Component} component The component being added - * @param {Number} index The index at which the component will be added to the container's items collection - */ - 'beforeadd', - /** - * @event beforeremove - * Fires before any {@link Ext.Component} is removed from the container. A handler can return - * false to cancel the remove. - * @param {Ext.Container} this - * @param {Ext.Component} component The component being removed - */ - 'beforeremove', - /** - * @event add - * @bubbles - * Fires after any {@link Ext.Component} is added or inserted into the container. - * @param {Ext.Container} this - * @param {Ext.Component} component The component that was added - * @param {Number} index The index at which the component was added to the container's items collection - */ - 'add', - /** - * @event remove - * @bubbles - * Fires after any {@link Ext.Component} is removed from the container. - * @param {Ext.Container} this - * @param {Ext.Component} component The component that was removed - */ - 'remove' - ); - - this.enableBubble(this.bubbleEvents); - - /** - * The collection of components in this container as a {@link Ext.util.MixedCollection} - * @type MixedCollection - * @property items - */ - var items = this.items; - if(items){ - delete this.items; - this.add(items); - } - }, - - // private - initItems : function(){ - if(!this.items){ - this.items = new Ext.util.MixedCollection(false, this.getComponentId); - this.getLayout(); // initialize the layout - } - }, - - // private - setLayout : function(layout){ - if(this.layout && this.layout != layout){ - this.layout.setContainer(null); - } - this.initItems(); - this.layout = layout; - layout.setContainer(this); - }, - - afterRender: function(){ - Ext.Container.superclass.afterRender.call(this); - if(!this.layout){ - this.layout = 'auto'; - } - if(Ext.isObject(this.layout) && !this.layout.layout){ - this.layoutConfig = this.layout; - this.layout = this.layoutConfig.type; - } - if(Ext.isString(this.layout)){ - this.layout = new Ext.Container.LAYOUTS[this.layout.toLowerCase()](this.layoutConfig); - } - this.setLayout(this.layout); - - if(this.activeItem !== undefined){ - var item = this.activeItem; - delete this.activeItem; - this.layout.setActiveItem(item); - } - if(!this.ownerCt){ - // force a layout if no ownerCt is set - this.doLayout(false, true); - } - if(this.monitorResize === true){ - Ext.EventManager.onWindowResize(this.doLayout, this, [false]); - } - }, - - /** - *
Returns the Element to be used to contain the child Components of this Container.
- *An implementation is provided which returns the Container's {@link #getEl Element}, but - * if there is a more complex structure to a Container, this may be overridden to return - * the element into which the {@link #layout layout} renders child Components.
- * @return {Ext.Element} The Element to render child Components into. - */ - getLayoutTarget : function(){ - return this.el; - }, - - // private - used as the key lookup function for the items collection - getComponentId : function(comp){ - return comp.getItemId(); - }, - - /** - *Adds {@link Ext.Component Component}(s) to this Container.
- *Description : - *
{@link #defaults}
for details).Notes : - *
-var tb = new {@link Ext.Toolbar}();
-tb.render(document.body); // toolbar is rendered
-tb.add({text:'Button 1'}); // add multiple items ({@link #defaultType} for {@link Ext.Toolbar Toolbar} is 'button')
-tb.add({text:'Button 2'});
-tb.{@link #doLayout}(); // refresh the layout
- *
Either a single component or an Array of components to add. See
- * {@link #items}
for additional information.
{@link #items}
property
- * and gets a direct child component of this container.
- * @param {String/Number} comp This parameter may be any of the following:
- * {@link Ext.Component#itemId itemId}
- * or {@link Ext.Component#id id}
of the child component {@link #items}
propertyFor additional information see {@link Ext.util.MixedCollection#get}. - * @return Ext.Component The component (if found). - */ - getComponent : function(comp){ - if(Ext.isObject(comp)){ - comp = comp.getItemId(); - } - return this.items.get(comp); - }, - - // private - lookupComponent : function(comp){ - if(Ext.isString(comp)){ - return Ext.ComponentMgr.get(comp); - }else if(!comp.events){ - return this.createComponent(comp); - } - return comp; - }, - - // private - createComponent : function(config){ - return Ext.create(config, this.defaultType); - }, - - // private - canLayout: function() { - var el = this.getVisibilityEl(); - return el && !el.isStyle("display", "none"); - }, - - - /** - * Force this container's layout to be recalculated. A call to this function is required after adding a new component - * to an already rendered container, or possibly after changing sizing/position properties of child components. - * @param {Boolean} shallow (optional) True to only calc the layout of this component, and let child components auto - * calc layouts as required (defaults to false, which calls doLayout recursively for each subcontainer) - * @param {Boolean} force (optional) True to force a layout to occur, even if the item is hidden. - * @return {Ext.Container} this - */ - doLayout: function(shallow, force){ - var rendered = this.rendered; - forceLayout = force || this.forceLayout; - - if(!this.canLayout() || this.collapsed){ - this.deferLayout = this.deferLayout || !shallow; - if(!forceLayout){ - return; - } - shallow = shallow && !this.deferLayout; - } else { - delete this.deferLayout; - } - if(rendered && this.layout){ - this.layout.layout(); - } - if(shallow !== true && this.items){ - var cs = this.items.items; - for(var i = 0, len = cs.length; i < len; i++){ - var c = cs[i]; - if(c.doLayout){ - c.doLayout(false, forceLayout); - } - } - } - if(rendered){ - this.onLayout(shallow, forceLayout); - } - // Initial layout completed - this.hasLayout = true; - delete this.forceLayout; - }, - - //private - onLayout : Ext.emptyFn, - - // private - shouldBufferLayout: function(){ - /* - * Returns true if the container should buffer a layout. - * This is true only if the container has previously been laid out - * and has a parent container that is pending a layout. - */ - var hl = this.hasLayout; - if(this.ownerCt){ - // Only ever buffer if we've laid out the first time and we have one pending. - return hl ? !this.hasLayoutPending() : false; - } - // Never buffer initial layout - return hl; - }, - - // private - hasLayoutPending: function(){ - // Traverse hierarchy to see if any parent container has a pending layout. - var pending = false; - this.ownerCt.bubble(function(c){ - if(c.layoutPending){ - pending = true; - return false; - } - }); - return pending; - }, - - onShow : function(){ - Ext.Container.superclass.onShow.call(this); - if(this.deferLayout !== undefined){ - this.doLayout(true); - } - }, - - /** - * Returns the layout currently in use by the container. If the container does not currently have a layout - * set, a default {@link Ext.layout.ContainerLayout} will be created and set as the container's layout. - * @return {ContainerLayout} layout The container's layout - */ - getLayout : function(){ - if(!this.layout){ - var layout = new Ext.layout.ContainerLayout(this.layoutConfig); - this.setLayout(layout); - } - return this.layout; - }, - - // private - beforeDestroy : function(){ - if(this.items){ - Ext.destroy.apply(Ext, this.items.items); - } - if(this.monitorResize){ - Ext.EventManager.removeResizeListener(this.doLayout, this); - } - Ext.destroy(this.layout); - Ext.Container.superclass.beforeDestroy.call(this); - }, - - /** - * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope (this) of - * function call will be the scope provided or the current component. The arguments to the function - * will be the args provided or the current component. If the function returns false at any point, - * the bubble is stopped. - * @param {Function} fn The function to call - * @param {Object} scope (optional) The scope of the function (defaults to current node) - * @param {Array} args (optional) The args to call the function with (default to passing the current component) - * @return {Ext.Container} this - */ - bubble : function(fn, scope, args){ - var p = this; - while(p){ - if(fn.apply(scope || p, args || [p]) === false){ - break; - } - p = p.ownerCt; - } - return this; - }, - - /** - * Cascades down the component/container heirarchy from this component (called first), calling the specified function with - * each component. The scope (this) of - * function call will be the scope provided or the current component. The arguments to the function - * will be the args provided or the current component. If the function returns false at any point, - * the cascade is stopped on that branch. - * @param {Function} fn The function to call - * @param {Object} scope (optional) The scope of the function (defaults to current component) - * @param {Array} args (optional) The args to call the function with (defaults to passing the current component) - * @return {Ext.Container} this - */ - cascade : function(fn, scope, args){ - if(fn.apply(scope || this, args || [this]) !== false){ - if(this.items){ - var cs = this.items.items; - for(var i = 0, len = cs.length; i < len; i++){ - if(cs[i].cascade){ - cs[i].cascade(fn, scope, args); - }else{ - fn.apply(scope || cs[i], args || [cs[i]]); - } - } - } - } - return this; - }, - - /** - * Find a component under this container at any level by id - * @param {String} id - * @return Ext.Component - */ - findById : function(id){ - var m, ct = this; - this.cascade(function(c){ - if(ct != c && c.id === id){ - m = c; - return false; - } - }); - return m || null; - }, - - /** - * Find a component under this container at any level by xtype or class - * @param {String/Class} xtype The xtype string for a component, or the class of the component directly - * @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is - * the default), or true to check whether this Component is directly of the specified xtype. - * @return {Array} Array of Ext.Components - */ - findByType : function(xtype, shallow){ - return this.findBy(function(c){ - return c.isXType(xtype, shallow); - }); - }, - - /** - * Find a component under this container at any level by property - * @param {String} prop - * @param {String} value - * @return {Array} Array of Ext.Components - */ - find : function(prop, value){ - return this.findBy(function(c){ - return c[prop] === value; - }); - }, - - /** - * Find a component under this container at any level by a custom function. If the passed function returns - * true, the component will be included in the results. The passed function is called with the arguments (component, this container). - * @param {Function} fn The function to call - * @param {Object} scope (optional) - * @return {Array} Array of Ext.Components - */ - findBy : function(fn, scope){ - var m = [], ct = this; - this.cascade(function(c){ - if(ct != c && fn.call(scope || c, c, ct) === true){ - m.push(c); - } - }); - return m; - }, - - /** - * Get a component contained by this container (alias for items.get(key)) - * @param {String/Number} key The index or id of the component - * @return {Ext.Component} Ext.Component - */ - get : function(key){ - return this.items.get(key); - } -}); - -Ext.Container.LAYOUTS = {}; -Ext.reg('container', Ext.Container); -/** - * @class Ext.layout.ContainerLayout - *
The ContainerLayout class is the default layout manager delegated by {@link Ext.Container} to - * render any child Components when no {@link Ext.Container#layout layout} is configured into - * a {@link Ext.Container Container}. ContainerLayout provides the basic foundation for all other layout - * classes in Ext. It simply renders all child Components into the Container, performing no sizing or - * positioning services. To utilize a layout that provides sizing and positioning of child Components, - * specify an appropriate {@link Ext.Container#layout layout}.
- *This class is intended to be extended or created via the {@link Ext.Container#layout layout} - * configuration property. See {@link Ext.Container#layout} for additional details.
+Ext.reg('spacer', Ext.Spacer);/** + * @class Ext.SplitBar + * @extends Ext.util.Observable + * Creates draggable splitter bar functionality from two elements (element to be dragged and element to be resized). + *
+var split = new Ext.SplitBar("elementToDrag", "elementToSize",
+ Ext.SplitBar.HORIZONTAL, Ext.SplitBar.LEFT);
+split.setAdapter(new Ext.SplitBar.AbsoluteLayoutAdapter("container"));
+split.minSize = 100;
+split.maxSize = 600;
+split.animate = true;
+split.on('moved', splitterMoved);
+
+ * @constructor
+ * Create a new SplitBar
+ * @param {Mixed} dragElement The element to be dragged and act as the SplitBar.
+ * @param {Mixed} resizingElement The element to be resized based on where the SplitBar element is dragged
+ * @param {Number} orientation (optional) Either Ext.SplitBar.HORIZONTAL or Ext.SplitBar.VERTICAL. (Defaults to HORIZONTAL)
+ * @param {Number} placement (optional) Either Ext.SplitBar.LEFT or Ext.SplitBar.RIGHT for horizontal or
+ Ext.SplitBar.TOP or Ext.SplitBar.BOTTOM for vertical. (By default, this is determined automatically by the initial
+ position of the SplitBar).
*/
-Ext.layout.ContainerLayout = function(config){
- Ext.apply(this, config);
-};
+Ext.SplitBar = function(dragElement, resizingElement, orientation, placement, existingProxy){
+
+ /** @private */
+ this.el = Ext.get(dragElement, true);
+ this.el.dom.unselectable = "on";
+ /** @private */
+ this.resizingEl = Ext.get(resizingElement, true);
-Ext.layout.ContainerLayout.prototype = {
/**
- * @cfg {String} extraCls
- * An optional extra CSS class that will be added to the container. This can be useful for adding - * customized styles to the container or any of its children using standard CSS rules. See - * {@link Ext.Component}.{@link Ext.Component#ctCls ctCls} also.
- *Note: extraCls defaults to '' except for the following classes - * which assign a value by default: - *
- * extraCls: 'x-column custom-class'
- *
- *
+ * @private
+ * The orientation of the split. Either Ext.SplitBar.HORIZONTAL or Ext.SplitBar.VERTICAL. (Defaults to HORIZONTAL)
+ * Note: If this is changed after creating the SplitBar, the placement property must be manually updated
+ * @type Number
*/
+ this.orientation = orientation || Ext.SplitBar.HORIZONTAL;
+
/**
- * @cfg {Boolean} renderHidden
- * True to hide each contained item on render (defaults to false).
+ * The increment, in pixels by which to move this SplitBar. When undefined, the SplitBar moves smoothly.
+ * @type Number
+ * @property tickSize
+ */
+ /**
+ * The minimum size of the resizing element. (Defaults to 0)
+ * @type Number
*/
+ this.minSize = 0;
/**
- * A reference to the {@link Ext.Component} that is active. For example,
- * if(myPanel.layout.activeItem.id == 'item-1') { ... }
- *
- * activeItem only applies to layout styles that can display items one at a time
- * (like {@link Ext.layout.AccordionLayout}, {@link Ext.layout.CardLayout}
- * and {@link Ext.layout.FitLayout}). Read-only. Related to {@link Ext.Container#activeItem}.
- * @type {Ext.Component}
- * @property activeItem
+ * The maximum size of the resizing element. (Defaults to 2000)
+ * @type Number
*/
+ this.maxSize = 2000;
- // private
- monitorResize:false,
- // private
- activeItem : null,
+ /**
+ * Whether to animate the transition to the new size
+ * @type Boolean
+ */
+ this.animate = false;
- // private
- layout : function(){
- var target = this.container.getLayoutTarget();
- this.onLayout(this.container, target);
- this.container.fireEvent('afterlayout', this.container, this);
- },
+ /**
+ * Whether to create a transparent shim that overlays the page when dragging, enables dragging across iframes.
+ * @type Boolean
+ */
+ this.useShim = false;
- // private
- onLayout : function(ct, target){
- this.renderAll(ct, target);
- },
+ /** @private */
+ this.shim = null;
- // private
- isValidParent : function(c, target){
- return target && c.getDomPositionEl().dom.parentNode == (target.dom || target);
- },
+ if(!existingProxy){
+ /** @private */
+ this.proxy = Ext.SplitBar.createProxy(this.orientation);
+ }else{
+ this.proxy = Ext.get(existingProxy).dom;
+ }
+ /** @private */
+ this.dd = new Ext.dd.DDProxy(this.el.dom.id, "XSplitBars", {dragElId : this.proxy.id});
- // private
- renderAll : function(ct, target){
- var items = ct.items.items;
- for(var i = 0, len = items.length; i < len; i++) {
- var c = items[i];
- if(c && (!c.rendered || !this.isValidParent(c, target))){
- this.renderItem(c, i, target);
- }
- }
- },
+ /** @private */
+ this.dd.b4StartDrag = this.onStartProxyDrag.createDelegate(this);
- // private
- renderItem : function(c, position, target){
- if(c && !c.rendered){
- c.render(target, position);
- this.configureItem(c, position);
- }else if(c && !this.isValidParent(c, target)){
- if(Ext.isNumber(position)){
- position = target.dom.childNodes[position];
- }
- target.dom.insertBefore(c.getDomPositionEl().dom, position || null);
- c.container = target;
- this.configureItem(c, position);
- }
+ /** @private */
+ this.dd.endDrag = this.onEndProxyDrag.createDelegate(this);
+
+ /** @private */
+ this.dragSpecs = {};
+
+ /**
+ * @private The adapter to use to positon and resize elements
+ */
+ this.adapter = new Ext.SplitBar.BasicLayoutAdapter();
+ this.adapter.init(this);
+
+ if(this.orientation == Ext.SplitBar.HORIZONTAL){
+ /** @private */
+ this.placement = placement || (this.el.getX() > this.resizingEl.getX() ? Ext.SplitBar.LEFT : Ext.SplitBar.RIGHT);
+ this.el.addClass("x-splitbar-h");
+ }else{
+ /** @private */
+ this.placement = placement || (this.el.getY() > this.resizingEl.getY() ? Ext.SplitBar.TOP : Ext.SplitBar.BOTTOM);
+ this.el.addClass("x-splitbar-v");
+ }
+
+ this.addEvents(
+ /**
+ * @event resize
+ * Fires when the splitter is moved (alias for {@link #moved})
+ * @param {Ext.SplitBar} this
+ * @param {Number} newSize the new width or height
+ */
+ "resize",
+ /**
+ * @event moved
+ * Fires when the splitter is moved
+ * @param {Ext.SplitBar} this
+ * @param {Number} newSize the new width or height
+ */
+ "moved",
+ /**
+ * @event beforeresize
+ * Fires before the splitter is dragged
+ * @param {Ext.SplitBar} this
+ */
+ "beforeresize",
+
+ "beforeapply"
+ );
+
+ Ext.SplitBar.superclass.constructor.call(this);
+};
+
+Ext.extend(Ext.SplitBar, Ext.util.Observable, {
+ onStartProxyDrag : function(x, y){
+ this.fireEvent("beforeresize", this);
+ this.overlay = Ext.DomHelper.append(document.body, {cls: "x-drag-overlay", html: " "}, true);
+ this.overlay.unselectable();
+ this.overlay.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
+ this.overlay.show();
+ Ext.get(this.proxy).setDisplayed("block");
+ var size = this.adapter.getElementSize(this);
+ this.activeMinSize = this.getMinimumSize();
+ this.activeMaxSize = this.getMaximumSize();
+ var c1 = size - this.activeMinSize;
+ var c2 = Math.max(this.activeMaxSize - size, 0);
+ if(this.orientation == Ext.SplitBar.HORIZONTAL){
+ this.dd.resetConstraints();
+ this.dd.setXConstraint(
+ this.placement == Ext.SplitBar.LEFT ? c1 : c2,
+ this.placement == Ext.SplitBar.LEFT ? c2 : c1,
+ this.tickSize
+ );
+ this.dd.setYConstraint(0, 0);
+ }else{
+ this.dd.resetConstraints();
+ this.dd.setXConstraint(0, 0);
+ this.dd.setYConstraint(
+ this.placement == Ext.SplitBar.TOP ? c1 : c2,
+ this.placement == Ext.SplitBar.TOP ? c2 : c1,
+ this.tickSize
+ );
+ }
+ this.dragSpecs.startSize = size;
+ this.dragSpecs.startPoint = [x, y];
+ Ext.dd.DDProxy.prototype.b4StartDrag.call(this.dd, x, y);
},
-
- // private
- configureItem: function(c, position){
- if(this.extraCls){
- var t = c.getPositionEl ? c.getPositionEl() : c;
- t.addClass(this.extraCls);
+
+ /**
+ * @private Called after the drag operation by the DDProxy
+ */
+ onEndProxyDrag : function(e){
+ Ext.get(this.proxy).setDisplayed(false);
+ var endPoint = Ext.lib.Event.getXY(e);
+ if(this.overlay){
+ Ext.destroy(this.overlay);
+ delete this.overlay;
}
- if (this.renderHidden && c != this.activeItem) {
- c.hide();
+ var newSize;
+ if(this.orientation == Ext.SplitBar.HORIZONTAL){
+ newSize = this.dragSpecs.startSize +
+ (this.placement == Ext.SplitBar.LEFT ?
+ endPoint[0] - this.dragSpecs.startPoint[0] :
+ this.dragSpecs.startPoint[0] - endPoint[0]
+ );
+ }else{
+ newSize = this.dragSpecs.startSize +
+ (this.placement == Ext.SplitBar.TOP ?
+ endPoint[1] - this.dragSpecs.startPoint[1] :
+ this.dragSpecs.startPoint[1] - endPoint[1]
+ );
}
- if(c.doLayout && this.forceLayout){
- c.doLayout(false, true);
+ newSize = Math.min(Math.max(newSize, this.activeMinSize), this.activeMaxSize);
+ if(newSize != this.dragSpecs.startSize){
+ if(this.fireEvent('beforeapply', this, newSize) !== false){
+ this.adapter.setElementSize(this, newSize);
+ this.fireEvent("moved", this, newSize);
+ this.fireEvent("resize", this, newSize);
+ }
}
},
-
- onRemove: function(c){
- if(this.activeItem == c){
- delete this.activeItem;
- }
- if(c.rendered && this.extraCls){
- var t = c.getPositionEl ? c.getPositionEl() : c;
- t.removeClass(this.extraCls);
- }
+
+ /**
+ * Get the adapter this SplitBar uses
+ * @return The adapter object
+ */
+ getAdapter : function(){
+ return this.adapter;
},
- // private
- onResize: function(){
- var ct = this.container,
- b;
-
- if(ct.collapsed){
- return;
- }
- if(b = ct.bufferResize){
- // Only allow if we should buffer the layout
- if(ct.shouldBufferLayout()){
- if(!this.resizeTask){
- this.resizeTask = new Ext.util.DelayedTask(this.runLayout, this);
- this.resizeBuffer = Ext.isNumber(b) ? b : 50;
- }
- ct.layoutPending = true;
- this.resizeTask.delay(this.resizeBuffer);
- }
- }else{
- ct.doLayout();
- }
+ /**
+ * Set the adapter this SplitBar uses
+ * @param {Object} adapter A SplitBar adapter object
+ */
+ setAdapter : function(adapter){
+ this.adapter = adapter;
+ this.adapter.init(this);
},
-
- // private
- runLayout: function(){
- var ct = this.container;
- ct.doLayout();
- delete ct.layoutPending;
+
+ /**
+ * Gets the minimum size for the resizing element
+ * @return {Number} The minimum size
+ */
+ getMinimumSize : function(){
+ return this.minSize;
},
- // private
- setContainer : function(ct){
- if(this.monitorResize && ct != this.container){
- var old = this.container;
- if(old){
- old.un(old.resizeEvent, this.onResize, this);
- }
- if(ct){
- ct.on(ct.resizeEvent, this.onResize, this);
- }
- }
- this.container = ct;
+ /**
+ * Sets the minimum size for the resizing element
+ * @param {Number} minSize The minimum size
+ */
+ setMinimumSize : function(minSize){
+ this.minSize = minSize;
},
- // private
- parseMargins : function(v){
- if(Ext.isNumber(v)){
- v = v.toString();
- }
- var ms = v.split(' ');
- var len = ms.length;
- if(len == 1){
- ms[1] = ms[0];
- ms[2] = ms[0];
- ms[3] = ms[0];
- }
- if(len == 2){
- ms[2] = ms[0];
- ms[3] = ms[1];
- }
- if(len == 3){
- ms[3] = ms[1];
- }
- return {
- top:parseInt(ms[0], 10) || 0,
- right:parseInt(ms[1], 10) || 0,
- bottom:parseInt(ms[2], 10) || 0,
- left:parseInt(ms[3], 10) || 0
- };
+ /**
+ * Gets the maximum size for the resizing element
+ * @return {Number} The maximum size
+ */
+ getMaximumSize : function(){
+ return this.maxSize;
},
/**
- * The {@link Ext.Template Ext.Template} used by Field rendering layout classes (such as
- * {@link Ext.layout.FormLayout}) to create the DOM structure of a fully wrapped,
- * labeled and styled form Field. A default Template is supplied, but this may be
- * overriden to create custom field structures. The template processes values returned from
- * {@link Ext.layout.FormLayout#getTemplateArgs}.
- * @property fieldTpl
- * @type Ext.Template
+ * Sets the maximum size for the resizing element
+ * @param {Number} maxSize The maximum size
*/
- fieldTpl: (function() {
- var t = new Ext.Template(
- 'This is a base class for layouts that contain a single item that automatically expands to fill the layout's - * container. This class is intended to be extended or created via the layout:'fit' {@link Ext.Container#layout} - * config, and should generally not need to be created directly via the new keyword.
- *FitLayout does not have any direct config options (other than inherited ones). To fit a panel to a container - * using FitLayout, simply set layout:'fit' on the container and add a single panel to it. If the container has - * multiple panels, only the first one will be displayed. Example usage:
- *
-var p = new Ext.Panel({
- title: 'Fit Layout',
- layout:'fit',
- items: {
- title: 'Inner Panel',
- html: '<p>This is the inner panel content</p>',
- border: false
- }
-});
-
- */
-Ext.layout.FitLayout = Ext.extend(Ext.layout.ContainerLayout, {
- // private
- monitorResize:true,
-
- // private
- onLayout : function(ct, target){
- Ext.layout.FitLayout.superclass.onLayout.call(this, ct, target);
- if(!this.container.collapsed){
- var sz = (Ext.isIE6 && Ext.isStrict && target.dom == document.body) ? target.getViewSize() : target.getStyleSize();
- this.setItemSize(this.activeItem || ct.items.itemAt(0), sz);
- }
- },
-
- // private
- setItemSize : function(item, size){
- if(item && size.height > 0){ // display none?
- item.setSize(size);
- }
- }
-});
-Ext.Container.LAYOUTS['fit'] = Ext.layout.FitLayout;/**
- * @class Ext.layout.CardLayout
- * @extends Ext.layout.FitLayout
- * This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be - * visible at any given time. This layout style is most commonly used for wizards, tab implementations, etc. - * This class is intended to be extended or created via the layout:'card' {@link Ext.Container#layout} config, - * and should generally not need to be created directly via the new keyword.
- *The CardLayout's focal method is {@link #setActiveItem}. Since only one panel is displayed at a time, - * the only way to move from one Component to the next is by calling setActiveItem, passing the id or index of - * the next panel to display. The layout itself does not provide a user interface for handling this navigation, - * so that functionality must be provided by the developer.
- *In the following example, a simplistic wizard setup is demonstrated. A button bar is added - * to the footer of the containing panel to provide navigation buttons. The buttons will be handled by a - * common navigation routine -- for this example, the implementation of that routine has been ommitted since - * it can be any type of custom logic. Note that other uses of a CardLayout (like a tab control) would require a - * completely different implementation. For serious implementations, a better approach would be to extend - * CardLayout to provide the custom functionality needed. Example usage:
- *
-var navHandler = function(direction){
- // This routine could contain business logic required to manage the navigation steps.
- // It would call setActiveItem as needed, manage navigation button state, handle any
- // branching logic that might be required, handle alternate actions like cancellation
- // or finalization, etc. A complete wizard implementation could get pretty
- // sophisticated depending on the complexity required, and should probably be
- // done as a subclass of CardLayout in a real-world implementation.
-};
-
-var card = new Ext.Panel({
- title: 'Example Wizard',
- layout:'card',
- activeItem: 0, // make sure the active item is set on the container config!
- bodyStyle: 'padding:15px',
- defaults: {
- // applied to each contained panel
- border:false
- },
- // just an example of one possible navigation scheme, using buttons
- bbar: [
- {
- id: 'move-prev',
- text: 'Back',
- handler: navHandler.createDelegate(this, [-1]),
- disabled: true
- },
- '->', // greedy spacer so that the buttons are aligned to each side
- {
- id: 'move-next',
- text: 'Next',
- handler: navHandler.createDelegate(this, [1])
- }
- ],
- // the panels (or "cards") within the layout
- items: [{
- id: 'card-0',
- html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
- },{
- id: 'card-1',
- html: '<p>Step 2 of 3</p>'
- },{
- id: 'card-2',
- html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
- }]
-});
-
- */
-Ext.layout.CardLayout = Ext.extend(Ext.layout.FitLayout, {
- /**
- * @cfg {Boolean} deferredRender
- * True to render each contained item at the time it becomes active, false to render all contained items
- * as soon as the layout is rendered (defaults to false). If there is a significant amount of content or
- * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
- * true might improve performance.
- */
- deferredRender : false,
-
- /**
- * @cfg {Boolean} layoutOnCardChange
- * True to force a layout of the active item when the active card is changed. Defaults to false.
- */
- layoutOnCardChange : false,
-
- /**
- * @cfg {Boolean} renderHidden @hide
- */
- // private
- renderHidden : true,
-
- constructor: function(config){
- Ext.layout.CardLayout.superclass.constructor.call(this, config);
- this.forceLayout = (this.deferredRender === false);
- },
-
- /**
- * Sets the active (visible) item in the layout.
- * @param {String/Number} item The string component id or numeric index of the item to activate
- */
- setActiveItem : function(item){
- item = this.container.getComponent(item);
- if(this.activeItem != item){
- if(this.activeItem){
- this.activeItem.hide();
- }
- var layout = item.doLayout && (this.layoutOnCardChange || !item.rendered);
- this.activeItem = item;
- item.show();
- this.layout();
- if(layout){
- item.doLayout();
- }
- }
- },
-
- // private
- renderAll : function(ct, target){
- if(this.deferredRender){
- this.renderItem(this.activeItem, undefined, target);
- }else{
- Ext.layout.CardLayout.superclass.renderAll.call(this, ct, target);
- }
- }
-});
-Ext.Container.LAYOUTS['card'] = Ext.layout.CardLayout;/**
- * @class Ext.layout.AnchorLayout
- * @extends Ext.layout.ContainerLayout
- * This is a layout that enables anchoring of contained elements relative to the container's dimensions. - * If the container is resized, all anchored items are automatically rerendered according to their - * {@link #anchor} rules.
- *This class is intended to be extended or created via the layout:'anchor' {@link Ext.Container#layout} - * config, and should generally not need to be created directly via the new keyword.
- *AnchorLayout does not have any direct config options (other than inherited ones). By default, - * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the - * container using the AnchorLayout can supply an anchoring-specific config property of anchorSize. - * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating - * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring - * logic if necessary. For example:
- *
-var viewport = new Ext.Viewport({
- layout:'anchor',
- anchorSize: {width:800, height:600},
- items:[{
- title:'Item 1',
- html:'Content 1',
- width:800,
- anchor:'right 20%'
- },{
- title:'Item 2',
- html:'Content 2',
- width:300,
- anchor:'50% 30%'
- },{
- title:'Item 3',
- html:'Content 3',
- width:600,
- anchor:'-100 50%'
- }]
-});
- *
- */
-Ext.layout.AnchorLayout = Ext.extend(Ext.layout.ContainerLayout, {
- /**
- * @cfg {String} anchor
- * This configuation option is to be applied to child items of a container managed by - * this layout (ie. configured with layout:'anchor').
This value is what tells the layout how an item should be anchored to the container. items - * added to an AnchorLayout accept an anchoring-specific config property of anchor which is a string - * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%'). - * The following types of anchor values are supported:
-// two values specified
-anchor: '100% 50%' // render item complete width of the container and
- // 1/2 height of the container
-// one value specified
-anchor: '100%' // the width value; the height will default to auto
- *
-// two values specified
-anchor: '-50 -100' // render item the complete width of the container
- // minus 50 pixels and
- // the complete height minus 100 pixels.
-// one value specified
-anchor: '-50' // anchor value is assumed to be the right offset value
- // bottom offset will default to 0
- *
-anchor: '-50 75%'
- *
This is the layout style of choice for creating structural layouts in a multi-column format where the width of - * each column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content. - * This class is intended to be extended or created via the layout:'column' {@link Ext.Container#layout} config, - * and should generally not need to be created directly via the new keyword.
- *ColumnLayout does not have any direct config options (other than inherited ones), but it does support a - * specific config property of columnWidth that can be included in the config of any panel added to it. The - * layout will use the columnWidth (if present) or width of each panel during layout to determine how to size each panel. - * If width or columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).
- *The width property is always evaluated as pixels, and must be a number greater than or equal to 1. - * The columnWidth property is always evaluated as a percentage, and must be a decimal value greater than 0 and - * less than 1 (e.g., .25).
- *The basic rules for specifying column widths are pretty simple. The logic makes two passes through the - * set of contained panels. During the first layout pass, all panels that either have a fixed width or none - * specified (auto) are skipped, but their widths are subtracted from the overall container width. During the second - * pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages based on - * the total remaining container width. In other words, percentage width panels are designed to fill the space - * left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any number of columns - * with different percentages, the columnWidths must always add up to 1 (or 100%) when added together, otherwise your - * layout may not render as expected. Example usage:
- *
-// All columns are percentages -- they must add up to 1
-var p = new Ext.Panel({
- title: 'Column Layout - Percentage Only',
- layout:'column',
- items: [{
- title: 'Column 1',
- columnWidth: .25
- },{
- title: 'Column 2',
- columnWidth: .6
- },{
- title: 'Column 3',
- columnWidth: .15
- }]
-});
-
-// Mix of width and columnWidth -- all columnWidth values must add up
-// to 1. The first column will take up exactly 120px, and the last two
-// columns will fill the remaining container width.
-var p = new Ext.Panel({
- title: 'Column Layout - Mixed',
- layout:'column',
- items: [{
- title: 'Column 1',
- width: 120
- },{
- title: 'Column 2',
- columnWidth: .8
- },{
- title: 'Column 3',
- columnWidth: .2
- }]
-});
-
- */
-Ext.layout.ColumnLayout = Ext.extend(Ext.layout.ContainerLayout, {
- // private
- monitorResize:true,
-
- extraCls: 'x-column',
-
- scrollOffset : 0,
-
- // private
- isValidParent : function(c, target){
- return (c.getPositionEl ? c.getPositionEl() : c.getEl()).dom.parentNode == this.innerCt.dom;
- },
-
- // private
- onLayout : function(ct, target){
- var cs = ct.items.items, len = cs.length, c, i;
-
- if(!this.innerCt){
- target.addClass('x-column-layout-ct');
-
- // the innerCt prevents wrapping and shuffling while
- // the container is resizing
- this.innerCt = target.createChild({cls:'x-column-inner'});
- this.innerCt.createChild({cls:'x-clear'});
- }
- this.renderAll(ct, this.innerCt);
-
- var size = Ext.isIE && target.dom != Ext.getBody().dom ? target.getStyleSize() : target.getViewSize();
-
- if(size.width < 1 && size.height < 1){ // display none?
- return;
- }
-
- var w = size.width - target.getPadding('lr') - this.scrollOffset,
- h = size.height - target.getPadding('tb'),
- pw = w;
-
- this.innerCt.setWidth(w);
-
- // some columns can be percentages while others are fixed
- // so we need to make 2 passes
-
- for(i = 0; i < len; i++){
- c = cs[i];
- if(!c.columnWidth){
- pw -= (c.getSize().width + c.getEl().getMargins('lr'));
- }
- }
-
- pw = pw < 0 ? 0 : pw;
-
- for(i = 0; i < len; i++){
- c = cs[i];
- if(c.columnWidth){
- c.setSize(Math.floor(c.columnWidth*pw) - c.getEl().getMargins('lr'));
- }
- }
- }
-
- /**
- * @property activeItem
- * @hide
- */
-});
-
-Ext.Container.LAYOUTS['column'] = Ext.layout.ColumnLayout;/**
- * @class Ext.layout.BorderLayout
- * @extends Ext.layout.ContainerLayout
- * This is a multi-pane, application-oriented UI layout style that supports multiple - * nested panels, automatic {@link Ext.layout.BorderLayout.Region#split split} bars between - * {@link Ext.layout.BorderLayout.Region#BorderLayout.Region regions} and built-in - * {@link Ext.layout.BorderLayout.Region#collapsible expanding and collapsing} of regions.
- *This class is intended to be extended or created via the layout:'border' - * {@link Ext.Container#layout} config, and should generally not need to be created directly - * via the new keyword.
- *BorderLayout does not have any direct config options (other than inherited ones). - * All configuration options available for customizing the BorderLayout are at the - * {@link Ext.layout.BorderLayout.Region} and {@link Ext.layout.BorderLayout.SplitRegion} - * levels.
- *Example usage:
- *
-var myBorderPanel = new Ext.Panel({
- {@link Ext.Component#renderTo renderTo}: document.body,
- {@link Ext.BoxComponent#width width}: 700,
- {@link Ext.BoxComponent#height height}: 500,
- {@link Ext.Panel#title title}: 'Border Layout',
- {@link Ext.Container#layout layout}: 'border',
- {@link Ext.Container#items items}: [{
- {@link Ext.Panel#title title}: 'South Region is resizable',
- {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'south', // position for region
- {@link Ext.BoxComponent#height height}: 100,
- {@link Ext.layout.BorderLayout.Region#split split}: true, // enable resizing
- {@link Ext.SplitBar#minSize minSize}: 75, // defaults to {@link Ext.layout.BorderLayout.Region#minHeight 50}
- {@link Ext.SplitBar#maxSize maxSize}: 150,
- {@link Ext.layout.BorderLayout.Region#margins margins}: '0 5 5 5'
- },{
- // xtype: 'panel' implied by default
- {@link Ext.Panel#title title}: 'West Region is collapsible',
- {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}:'west',
- {@link Ext.layout.BorderLayout.Region#margins margins}: '5 0 0 5',
- {@link Ext.BoxComponent#width width}: 200,
- {@link Ext.layout.BorderLayout.Region#collapsible collapsible}: true, // make collapsible
- {@link Ext.layout.BorderLayout.Region#cmargins cmargins}: '5 5 0 5', // adjust top margin when collapsed
- {@link Ext.Component#id id}: 'west-region-container',
- {@link Ext.Container#layout layout}: 'fit',
- {@link Ext.Panel#unstyled unstyled}: true
- },{
- {@link Ext.Panel#title title}: 'Center Region',
- {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'center', // center region is required, no width/height specified
- {@link Ext.Component#xtype xtype}: 'container',
- {@link Ext.Container#layout layout}: 'fit',
- {@link Ext.layout.BorderLayout.Region#margins margins}: '5 5 0 0'
- }]
-});
-
- * Notes:
-wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
-wrc.{@link Ext.Panel#removeAll removeAll}();
-wrc.{@link Ext.Container#add add}({
- title: 'Added Panel',
- html: 'Some content'
+ setCurrentSize : function(size){
+ var oldAnimate = this.animate;
+ this.animate = false;
+ this.adapter.setElementSize(this, size);
+ this.animate = oldAnimate;
+ },
+
+ /**
+ * Destroy this splitbar.
+ * @param {Boolean} removeEl True to remove the element
+ */
+ destroy : function(removeEl){
+ Ext.destroy(this.shim, Ext.get(this.proxy));
+ this.dd.unreg();
+ if(removeEl){
+ this.el.remove();
+ }
+ this.purgeListeners();
+ }
});
-wrc.{@link Ext.Container#doLayout doLayout}();
- *
-wr = myBorderPanel.layout.west;
- *
This is a region of a {@link Ext.layout.BorderLayout BorderLayout} that acts as a subcontainer - * within the layout. Each region has its own {@link Ext.layout.ContainerLayout layout} that is - * independent of other regions and the containing BorderLayout, and can be any of the - * {@link Ext.layout.ContainerLayout valid Ext layout types}.
- *Region size is managed automatically and cannot be changed by the user -- for - * {@link #split resizable regions}, see {@link Ext.layout.BorderLayout.SplitRegion}.
- * @constructor - * Create a new Region. - * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region. - * @param {Object} config The configuration options - * @param {String} position The region position. Valid values are: north, south, - * east, west and center. Every {@link Ext.layout.BorderLayout BorderLayout} - * must have a center region for the primary content -- all other regions are optional. + * Orientation constant - Create a horizontal SplitBar + * @static + * @type Number */ -Ext.layout.BorderLayout.Region = function(layout, config, pos){ - Ext.apply(this, config); - this.layout = layout; - this.position = pos; - this.state = {}; - if(typeof this.margins == 'string'){ - this.margins = this.layout.parseMargins(this.margins); - } - this.margins = Ext.applyIf(this.margins || {}, this.defaultMargins); - if(this.collapsible){ - if(typeof this.cmargins == 'string'){ - this.cmargins = this.layout.parseMargins(this.cmargins); +Ext.SplitBar.HORIZONTAL = 2; + +/** + * Placement constant - The resizing element is to the left of the splitter element + * @static + * @type Number + */ +Ext.SplitBar.LEFT = 1; + +/** + * Placement constant - The resizing element is to the right of the splitter element + * @static + * @type Number + */ +Ext.SplitBar.RIGHT = 2; + +/** + * Placement constant - The resizing element is positioned above the splitter element + * @static + * @type Number + */ +Ext.SplitBar.TOP = 3; + +/** + * Placement constant - The resizing element is positioned under splitter element + * @static + * @type Number + */ +Ext.SplitBar.BOTTOM = 4; +/** + * @class Ext.Container + * @extends Ext.BoxComponent + *Base class for any {@link Ext.BoxComponent} that may contain other Components. Containers handle the + * basic behavior of containing items, namely adding, inserting and removing items.
+ * + *The most commonly used Container classes are {@link Ext.Panel}, {@link Ext.Window} and {@link Ext.TabPanel}.
+ * If you do not need the capabilities offered by the aforementioned classes you can create a lightweight
+ * Container to be encapsulated by an HTML element to your specifications by using the
+ * {@link Ext.Component#autoEl autoEl}
config option. This is a useful technique when creating
+ * embedded {@link Ext.layout.ColumnLayout column} layouts inside {@link Ext.form.FormPanel FormPanels}
+ * for example.
The code below illustrates both how to explicitly create a Container, and how to implicitly
+ * create one using the 'container'
xtype:
+// explicitly create a Container
+var embeddedColumns = new Ext.Container({
+ autoEl: 'div', // This is the default
+ layout: 'column',
+ defaults: {
+ // implicitly create Container by specifying xtype
+ xtype: 'container',
+ autoEl: 'div', // This is the default.
+ layout: 'form',
+ columnWidth: 0.5,
+ style: {
+ padding: '10px'
}
- if(this.collapseMode == 'mini' && !this.cmargins){
- this.cmargins = {left:0,top:0,right:0,bottom:0};
- }else{
- this.cmargins = Ext.applyIf(this.cmargins || {},
- pos == 'north' || pos == 'south' ? this.defaultNSCMargins : this.defaultEWCMargins);
+ },
+// The two items below will be Ext.Containers, each encapsulated by a <DIV> element.
+ items: [{
+ items: {
+ xtype: 'datefield',
+ name: 'startDate',
+ fieldLabel: 'Start date'
+ }
+ }, {
+ items: {
+ xtype: 'datefield',
+ name: 'endDate',
+ fieldLabel: 'End date'
}
+ }]
+});
+ *
+ * Layout
+ *Container classes delegate the rendering of child Components to a layout
+ * manager class which must be configured into the Container using the
+ * {@link #layout}
configuration property.
When either specifying child {@link #items}
of a Container,
+ * or dynamically {@link #add adding} Components to a Container, remember to
+ * consider how you wish the Container to arrange those child elements, and
+ * whether those child elements need to be sized using one of Ext's built-in
+ * {@link #layout}
schemes. By default, Containers use the
+ * {@link Ext.layout.ContainerLayout ContainerLayout} scheme which only
+ * renders child components, appending them one after the other inside the
+ * Container, and does not apply any sizing at all.
A common mistake is when a developer neglects to specify a
+ * {@link #layout}
(e.g. widgets like GridPanels or
+ * TreePanels are added to Containers for which no {@link #layout}
+ * has been specified). If a Container is left to use the default
+ * {@link Ext.layout.ContainerLayout ContainerLayout} scheme, none of its
+ * child components will be resized, or changed in any way when the Container
+ * is resized.
Certain layout managers allow dynamic addition of child components. + * Those that do include {@link Ext.layout.CardLayout}, + * {@link Ext.layout.AnchorLayout}, {@link Ext.layout.FormLayout}, and + * {@link Ext.layout.TableLayout}. For example:
+// Create the GridPanel.
+var myNewGrid = new Ext.grid.GridPanel({
+ store: myStore,
+ columns: myColumnModel,
+ title: 'Results', // the title becomes the title of the tab
+});
+
+myTabPanel.add(myNewGrid); // {@link Ext.TabPanel} implicitly uses {@link Ext.layout.CardLayout CardLayout}
+myTabPanel.{@link Ext.TabPanel#setActiveTab setActiveTab}(myNewGrid);
+ *
+ * The example above adds a newly created GridPanel to a TabPanel. Note that + * a TabPanel uses {@link Ext.layout.CardLayout} as its layout manager which + * means all its child items are sized to {@link Ext.layout.FitLayout fit} + * exactly into its client area. + *
Overnesting is a common problem.
+ * An example of overnesting occurs when a GridPanel is added to a TabPanel
+ * by wrapping the GridPanel inside a wrapping Panel (that has no
+ * {@link #layout}
specified) and then add that wrapping Panel
+ * to the TabPanel. The point to realize is that a GridPanel is a
+ * Component which can be added directly to a Container. If the wrapping Panel
+ * has no {@link #layout}
configuration, then the overnested
+ * GridPanel will not be sized as expected.
+ * + *
Adding via remote configuration
+ * + *A server side script can be used to add Components which are generated dynamically on the server. + * An example of adding a GridPanel to a TabPanel where the GridPanel is generated by the server + * based on certain parameters: + *
+// execute an Ajax request to invoke server side script:
+Ext.Ajax.request({
+ url: 'gen-invoice-grid.php',
+ // send additional parameters to instruct server script
+ params: {
+ startDate: Ext.getCmp('start-date').getValue(),
+ endDate: Ext.getCmp('end-date').getValue()
+ },
+ // process the response object to add it to the TabPanel:
+ success: function(xhr) {
+ var newComponent = eval(xhr.responseText); // see discussion below
+ myTabPanel.add(newComponent); // add the component to the TabPanel
+ myTabPanel.setActiveTab(newComponent);
+ },
+ failure: function() {
+ Ext.Msg.alert("Grid create failed", "Server communication failure");
}
-};
+});
+
+ * The server script needs to return an executable Javascript statement which, when processed
+ * using eval()
, will return either a config object with an {@link Ext.Component#xtype xtype},
+ * or an instantiated Component. The server might return this for example:
+(function() {
+ function formatDate(value){
+ return value ? value.dateFormat('M d, Y') : '';
+ };
-Ext.layout.BorderLayout.Region.prototype = {
- /**
- * @cfg {Boolean} animFloat
- * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
- * panel that will close again once the user mouses out of that panel (or clicks out if
- * {@link #autoHide} = false). Setting {@link #animFloat} = false will
- * prevent the open and close of these floated panels from being animated (defaults to true).
- */
- /**
- * @cfg {Boolean} autoHide
- * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
- * panel. If autoHide = true, the panel will automatically hide after the user mouses
- * out of the panel. If autoHide = false, the panel will continue to display until the
- * user clicks outside of the panel (defaults to true).
- */
- /**
- * @cfg {String} collapseMode
- * collapseMode supports two configuration values:
- * - undefined (default)By default, {@link #collapsible}
- * regions are collapsed by clicking the expand/collapse tool button that renders into the region's
- * title bar.
- * - 'mini'Optionally, when collapseMode is set to
- * 'mini' the region's split bar will also display a small collapse button in the center of
- * the bar. In 'mini' mode the region will collapse to a thinner bar than in normal mode.
- *
- *
- * Note: if a collapsible region does not have a title bar, then set collapseMode =
- * 'mini' and {@link #split} = true in order for the region to be {@link #collapsible}
- * by the user as the expand/collapse tool button (that would go in the title bar) will not be rendered.
- * See also {@link #cmargins}.
- */
- /**
- * @cfg {Object} margins
- * An object containing margins to apply to the region when in the expanded state in the
- * format:
-{
- top: (top margin),
- right: (right margin),
- bottom: (bottom margin),
- left: (left margin)
-}
- * May also be a string containing space-separated, numeric margin values. The order of the
- * sides associated with each value matches the way CSS processes margin values:
- *
- * - If there is only one value, it applies to all sides.
- * - If there are two values, the top and bottom borders are set to the first value and the
- * right and left are set to the second.
- * - If there are three values, the top is set to the first value, the left and right are set
- * to the second, and the bottom is set to the third.
- * - If there are four values, they apply to the top, right, bottom, and left, respectively.
- *
- * Defaults to:
- * {top:0, right:0, bottom:0, left:0}
+ var store = new Ext.data.Store({
+ url: 'get-invoice-data.php',
+ baseParams: {
+ startDate: '01/01/2008',
+ endDate: '01/31/2008'
+ },
+ reader: new Ext.data.JsonReader({
+ record: 'transaction',
+ idProperty: 'id',
+ totalRecords: 'total'
+ }, [
+ 'customer',
+ 'invNo',
+ {name: 'date', type: 'date', dateFormat: 'm/d/Y'},
+ {name: 'value', type: 'float'}
+ ])
+ });
+
+ var grid = new Ext.grid.GridPanel({
+ title: 'Invoice Report',
+ bbar: new Ext.PagingToolbar(store),
+ store: store,
+ columns: [
+ {header: "Customer", width: 250, dataIndex: 'customer', sortable: true},
+ {header: "Invoice Number", width: 120, dataIndex: 'invNo', sortable: true},
+ {header: "Invoice Date", width: 100, dataIndex: 'date', renderer: formatDate, sortable: true},
+ {header: "Value", width: 120, dataIndex: 'value', renderer: 'usMoney', sortable: true}
+ ],
+ });
+ store.load();
+ return grid; // return instantiated component
+})();
+
+ * When the above code fragment is passed through the eval
function in the success handler
+ * of the Ajax request, the code is executed by the Javascript processor, and the anonymous function
+ * runs, and returns the instantiated grid component.
+ * Note: since the code above is generated by a server script, the baseParams
for
+ * the Store, the metadata to allow generation of the Record layout, and the ColumnModel
+ * can all be generated into the code since these are all known on the server.
+ *
+ * @xtype container
+ */
+Ext.Container = Ext.extend(Ext.BoxComponent, {
+ /**
+ * @cfg {Boolean} monitorResize
+ * True to automatically monitor window resize events to handle anything that is sensitive to the current size
+ * of the viewport. This value is typically managed by the chosen {@link #layout}
and should not need
+ * to be set manually.
+ */
+ /**
+ * @cfg {String/Object} layout
+ * *Important: In order for child items to be correctly sized and
+ * positioned, typically a layout manager must be specified through
+ * the layout
configuration option.
+ *
The sizing and positioning of child {@link items} is the responsibility of
+ * the Container's layout manager which creates and manages the type of layout
+ * you have in mind. For example:
+new Ext.Window({
+ width:300, height: 300,
+ layout: 'fit', // explicitly set layout manager: override the default (layout:'auto')
+ items: [{
+ title: 'Panel inside a Window'
+ }]
+}).show();
*
+ * If the {@link #layout} configuration is not explicitly specified for
+ * a general purpose container (e.g. Container or Panel) the
+ * {@link Ext.layout.ContainerLayout default layout manager} will be used
+ * which does nothing but render child components sequentially into the
+ * Container (no sizing or positioning will be performed in this situation).
+ * Some container classes implicitly specify a default layout
+ * (e.g. FormPanel specifies layout:'form'
). Other specific
+ * purpose classes internally specify/manage their internal layout (e.g.
+ * GridPanel, TabPanel, TreePanel, Toolbar, Menu, etc.).
+ *
layout
may be specified as either as an Object or
+ * as a String:
+ *
+ * - Specify as an Object
+ *
+ * - Example usage:
+
+layout: {
+ type: 'vbox',
+ padding: '5',
+ align: 'left'
+}
+
+ *
+ * type
+ *
The layout type to be used for this container. If not specified,
+ * a default {@link Ext.layout.ContainerLayout} will be created and used.
+ *
Valid layout type
values are:
+ *
+ * {@link Ext.layout.AbsoluteLayout absolute}
+ * {@link Ext.layout.AccordionLayout accordion}
+ * {@link Ext.layout.AnchorLayout anchor}
+ * {@link Ext.layout.ContainerLayout auto}
Default
+ * {@link Ext.layout.BorderLayout border}
+ * {@link Ext.layout.CardLayout card}
+ * {@link Ext.layout.ColumnLayout column}
+ * {@link Ext.layout.FitLayout fit}
+ * {@link Ext.layout.FormLayout form}
+ * {@link Ext.layout.HBoxLayout hbox}
+ * {@link Ext.layout.MenuLayout menu}
+ * {@link Ext.layout.TableLayout table}
+ * {@link Ext.layout.ToolbarLayout toolbar}
+ * {@link Ext.layout.VBoxLayout vbox}
+ *
+ *
+ * - Layout specific configuration properties
+ *
Additional layout specific configuration properties may also be
+ * specified. For complete details regarding the valid config options for
+ * each layout type, see the layout class corresponding to the type
+ * specified.
+ *
+ *
+ *
+ * - Specify as a String
+ *
+ * - Example usage:
+
+layout: 'vbox',
+layoutConfig: {
+ padding: '5',
+ align: 'left'
+}
+
+ * layout
+ *
The layout type
to be used for this container (see list
+ * of valid layout type values above).
+ * {@link #layoutConfig}
+ *
Additional layout specific configuration properties. For complete
+ * details regarding the valid config options for each layout type, see the
+ * layout class corresponding to the layout
specified.
+ *
*/
/**
- * @cfg {Object} cmargins
- * An object containing margins to apply to the region when in the collapsed state in the
- * format:
-{
- top: (top margin),
- right: (right margin),
- bottom: (bottom margin),
- left: (left margin)
-}
- * May also be a string containing space-separated, numeric margin values. The order of the
- * sides associated with each value matches the way CSS processes margin values.
- *
- * - If there is only one value, it applies to all sides.
- * - If there are two values, the top and bottom borders are set to the first value and the
- * right and left are set to the second.
- * - If there are three values, the top is set to the first value, the left and right are set
- * to the second, and the bottom is set to the third.
- * - If there are four values, they apply to the top, right, bottom, and left, respectively.
- *
- */
- /**
- * @cfg {Boolean} collapsible
- * true to allow the user to collapse this region (defaults to false). If
- * true, an expand/collapse tool button will automatically be rendered into the title
- * bar of the region, otherwise the button will not be shown.
- * Note: that a title bar is required to display the collapse/expand toggle button -- if
- * no title is specified for the region's panel, the region will only be collapsible if
- * {@link #collapseMode} = 'mini' and {@link #split} = true.
+ * @cfg {Object} layoutConfig
+ * This is a config object containing properties specific to the chosen
+ * {@link #layout}
if {@link #layout}
+ * has been specified as a string.
*/
- collapsible : false,
/**
- * @cfg {Boolean} split
- * true to create a {@link Ext.layout.BorderLayout.SplitRegion SplitRegion} and
- * display a 5px wide {@link Ext.SplitBar} between this region and its neighbor, allowing the user to
- * resize the regions dynamically. Defaults to false creating a
- * {@link Ext.layout.BorderLayout.Region Region}.
- * Notes:
- * - this configuration option is ignored if region='center'
- * - when split == true, it is common to specify a
- * {@link Ext.SplitBar#minSize minSize} and {@link Ext.SplitBar#maxSize maxSize}
- * for the {@link Ext.BoxComponent BoxComponent} representing the region. These are not native
- * configs of {@link Ext.BoxComponent BoxComponent}, and are used only by this class.
- * - if {@link #collapseMode} = 'mini' requires split = true to reserve space
- * for the collapse tool
- *
+ * @cfg {Boolean/Number} bufferResize
+ * When set to true (50 milliseconds) or a number of milliseconds, the layout assigned for this container will buffer
+ * the frequency it calculates and does a re-layout of components. This is useful for heavy containers or containers
+ * with a large quantity of sub-components for which frequent layout calls would be expensive. Defaults to 50
.
*/
- split:false,
+ bufferResize: 50,
+
/**
- * @cfg {Boolean} floatable
- * true to allow clicking a collapsed region's bar to display the region's panel floated
- * above the layout, false to force the user to fully expand a collapsed region by
- * clicking the expand button to see it again (defaults to true).
+ * @cfg {String/Number} activeItem
+ * A string component id or the numeric index of the component that should be initially activated within the
+ * container's layout on render. For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
+ * item in the container's collection). activeItem only applies to layout styles that can display
+ * items one at a time (like {@link Ext.layout.AccordionLayout}, {@link Ext.layout.CardLayout} and
+ * {@link Ext.layout.FitLayout}). Related to {@link Ext.layout.ContainerLayout#activeItem}.
*/
- floatable: true,
/**
- * @cfg {Number} minWidth
- * The minimum allowable width in pixels for this region (defaults to 50).
- * maxWidth may also be specified.
- * Note: setting the {@link Ext.SplitBar#minSize minSize} /
- * {@link Ext.SplitBar#maxSize maxSize} supersedes any specified
- * minWidth / maxWidth.
+ * @cfg {Object/Array} items
+ * ** IMPORTANT: be sure to {@link #layout specify a layout
} if needed ! **
+ * A single item, or an array of child Components to be added to this container,
+ * for example:
+ *
+// specifying a single item
+items: {...},
+layout: 'fit', // specify a layout!
+
+// specifying multiple items
+items: [{...}, {...}],
+layout: 'anchor', // specify a layout!
+ *
+ * Each item may be:
+ *
+ * - any type of object based on {@link Ext.Component}
+ * - a fully instanciated object or
+ * - an object literal that:
+ *
+ * - has a specified
{@link Ext.Component#xtype xtype}
+ * - the {@link Ext.Component#xtype} specified is associated with the Component
+ * desired and should be chosen from one of the available xtypes as listed
+ * in {@link Ext.Component}.
+ * - If an
{@link Ext.Component#xtype xtype}
is not explicitly
+ * specified, the {@link #defaultType} for that Container is used.
+ * - will be "lazily instanciated", avoiding the overhead of constructing a fully
+ * instanciated Component object
+ *
+ * Notes:
+ *
+ * - Ext uses lazy rendering. Child Components will only be rendered
+ * should it become necessary. Items are automatically laid out when they are first
+ * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.
+ * - Do not specify
{@link Ext.Panel#contentEl contentEl}
/
+ * {@link Ext.Panel#html html}
with items
.
+ *
*/
- minWidth:50,
/**
- * @cfg {Number} minHeight
- * The minimum allowable height in pixels for this region (defaults to 50)
- * maxHeight may also be specified.
- * Note: setting the {@link Ext.SplitBar#minSize minSize} /
- * {@link Ext.SplitBar#maxSize maxSize} supersedes any specified
- * minHeight / maxHeight.
+ * @cfg {Object|Function} defaults
+ * This option is a means of applying default settings to all added items whether added through the {@link #items}
+ * config or via the {@link #add} or {@link #insert} methods.
+ * If an added item is a config object, and not an instantiated Component, then the default properties are
+ * unconditionally applied. If the added item is an instantiated Component, then the default properties are
+ * applied conditionally so as not to override existing properties in the item.
+ * If the defaults option is specified as a function, then the function will be called using this Container as the
+ * scope (this
reference) and passing the added item as the first parameter. Any resulting object
+ * from that call is then applied to the item as default properties.
+ * For example, to automatically apply padding to the body of each of a set of
+ * contained {@link Ext.Panel} items, you could pass: defaults: {bodyStyle:'padding:15px'}
.
+ * Usage:
+defaults: { // defaults are applied to items, not the container
+ autoScroll:true
+},
+items: [
+ {
+ xtype: 'panel', // defaults do not have precedence over
+ id: 'panel1', // options in config objects, so the defaults
+ autoScroll: false // will not be applied here, panel1 will be autoScroll:false
+ },
+ new Ext.Panel({ // defaults do have precedence over options
+ id: 'panel2', // options in components, so the defaults
+ autoScroll: false // will be applied here, panel2 will be autoScroll:true.
+ })
+]
+ *
*/
- minHeight:50,
- // private
- defaultMargins : {left:0,top:0,right:0,bottom:0},
- // private
- defaultNSCMargins : {left:5,top:5,right:5,bottom:5},
- // private
- defaultEWCMargins : {left:5,top:0,right:5,bottom:0},
- floatingZIndex: 100,
- /**
- * True if this region is collapsed. Read-only.
- * @type Boolean
- * @property
+ /** @cfg {Boolean} autoDestroy
+ * If true the container will automatically destroy any contained component that is removed from it, else
+ * destruction must be handled manually (defaults to true).
*/
- isCollapsed : false,
+ autoDestroy : true,
- /**
- * This region's panel. Read-only.
- * @type Ext.Panel
- * @property panel
+ /** @cfg {Boolean} forceLayout
+ * If true the container will force a layout initially even if hidden or collapsed. This option
+ * is useful for forcing forms to render in collapsed or hidden containers. (defaults to false).
*/
- /**
- * This region's layout. Read-only.
- * @type Layout
- * @property layout
+ forceLayout: false,
+
+ /** @cfg {Boolean} hideBorders
+ * True to hide the borders of each contained component, false to defer to the component's existing
+ * border settings (defaults to false).
+ */
+ /** @cfg {String} defaultType
+ * The default {@link Ext.Component xtype} of child Components to create in this Container when
+ * a child item is specified as a raw configuration object, rather than as an instantiated Component.
+ * Defaults to 'panel'
, except {@link Ext.menu.Menu} which defaults to 'menuitem'
,
+ * and {@link Ext.Toolbar} and {@link Ext.ButtonGroup} which default to 'button'
.
+ */
+ defaultType : 'panel',
+
+ /** @cfg {String} resizeEvent
+ * The event to listen to for resizing in layouts. Defaults to 'resize'
.
*/
+ resizeEvent: 'resize',
+
/**
- * This region's layout position (north, south, east, west or center). Read-only.
- * @type String
- * @property position
+ * @cfg {Array} bubbleEvents
+ * An array of events that, when fired, should be bubbled to any parent container.
+ * See {@link Ext.util.Observable#enableBubble}.
+ * Defaults to ['add', 'remove']
.
*/
+ bubbleEvents: ['add', 'remove'],
// private
- render : function(ct, p){
- this.panel = p;
- p.el.enableDisplayMode();
- this.targetEl = ct;
- this.el = p.el;
+ initComponent : function(){
+ Ext.Container.superclass.initComponent.call(this);
- var gs = p.getState, ps = this.position;
- p.getState = function(){
- return Ext.apply(gs.call(p) || {}, this.state);
- }.createDelegate(this);
+ this.addEvents(
+ /**
+ * @event afterlayout
+ * Fires when the components in this container are arranged by the associated layout manager.
+ * @param {Ext.Container} this
+ * @param {ContainerLayout} layout The ContainerLayout implementation for this container
+ */
+ 'afterlayout',
+ /**
+ * @event beforeadd
+ * Fires before any {@link Ext.Component} is added or inserted into the container.
+ * A handler can return false to cancel the add.
+ * @param {Ext.Container} this
+ * @param {Ext.Component} component The component being added
+ * @param {Number} index The index at which the component will be added to the container's items collection
+ */
+ 'beforeadd',
+ /**
+ * @event beforeremove
+ * Fires before any {@link Ext.Component} is removed from the container. A handler can return
+ * false to cancel the remove.
+ * @param {Ext.Container} this
+ * @param {Ext.Component} component The component being removed
+ */
+ 'beforeremove',
+ /**
+ * @event add
+ * @bubbles
+ * Fires after any {@link Ext.Component} is added or inserted into the container.
+ * @param {Ext.Container} this
+ * @param {Ext.Component} component The component that was added
+ * @param {Number} index The index at which the component was added to the container's items collection
+ */
+ 'add',
+ /**
+ * @event remove
+ * @bubbles
+ * Fires after any {@link Ext.Component} is removed from the container.
+ * @param {Ext.Container} this
+ * @param {Ext.Component} component The component that was removed
+ */
+ 'remove'
+ );
- if(ps != 'center'){
- p.allowQueuedExpand = false;
- p.on({
- beforecollapse: this.beforeCollapse,
- collapse: this.onCollapse,
- beforeexpand: this.beforeExpand,
- expand: this.onExpand,
- hide: this.onHide,
- show: this.onShow,
- scope: this
- });
- if(this.collapsible || this.floatable){
- p.collapseEl = 'el';
- p.slideAnchor = this.getSlideAnchor();
- }
- if(p.tools && p.tools.toggle){
- p.tools.toggle.addClass('x-tool-collapse-'+ps);
- p.tools.toggle.addClassOnOver('x-tool-collapse-'+ps+'-over');
- }
+ /**
+ * The collection of components in this container as a {@link Ext.util.MixedCollection}
+ * @type MixedCollection
+ * @property items
+ */
+ var items = this.items;
+ if(items){
+ delete this.items;
+ this.add(items);
}
},
// private
- getCollapsedEl : function(){
- if(!this.collapsedEl){
- if(!this.toolTemplate){
- var tt = new Ext.Template(
- '
'
- );
- tt.disableFormats = true;
- tt.compile();
- Ext.layout.BorderLayout.Region.prototype.toolTemplate = tt;
- }
- this.collapsedEl = this.targetEl.createChild({
- cls: "x-layout-collapsed x-layout-collapsed-"+this.position,
- id: this.panel.id + '-xcollapsed'
- });
- this.collapsedEl.enableDisplayMode('block');
-
- if(this.collapseMode == 'mini'){
- this.collapsedEl.addClass('x-layout-cmini-'+this.position);
- this.miniCollapsedEl = this.collapsedEl.createChild({
- cls: "x-layout-mini x-layout-mini-"+this.position, html: " "
- });
- this.miniCollapsedEl.addClassOnOver('x-layout-mini-over');
- this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
- this.collapsedEl.on('click', this.onExpandClick, this, {stopEvent:true});
- }else {
- if(this.collapsible !== false && !this.hideCollapseTool) {
- var t = this.toolTemplate.append(
- this.collapsedEl.dom,
- {id:'expand-'+this.position}, true);
- t.addClassOnOver('x-tool-expand-'+this.position+'-over');
- t.on('click', this.onExpandClick, this, {stopEvent:true});
- }
- if(this.floatable !== false || this.titleCollapse){
- this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
- this.collapsedEl.on("click", this[this.floatable ? 'collapseClick' : 'onExpandClick'], this);
- }
- }
+ initItems : function(){
+ if(!this.items){
+ this.items = new Ext.util.MixedCollection(false, this.getComponentId);
+ this.getLayout(); // initialize the layout
}
- return this.collapsedEl;
},
// private
- onExpandClick : function(e){
- if(this.isSlid){
- this.afterSlideIn();
- this.panel.expand(false);
- }else{
- this.panel.expand();
+ setLayout : function(layout){
+ if(this.layout && this.layout != layout){
+ this.layout.setContainer(null);
}
+ this.layout = layout;
+ this.initItems();
+ layout.setContainer(this);
},
- // private
- onCollapseClick : function(e){
- this.panel.collapse();
- },
+ afterRender: function(){
+ // Render this Container, this should be done before setLayout is called which
+ // will hook onResize
+ Ext.Container.superclass.afterRender.call(this);
+ if(!this.layout){
+ this.layout = 'auto';
+ }
+ if(Ext.isObject(this.layout) && !this.layout.layout){
+ this.layoutConfig = this.layout;
+ this.layout = this.layoutConfig.type;
+ }
+ if(Ext.isString(this.layout)){
+ this.layout = new Ext.Container.LAYOUTS[this.layout.toLowerCase()](this.layoutConfig);
+ }
+ this.setLayout(this.layout);
- // private
- beforeCollapse : function(p, animate){
- this.lastAnim = animate;
- if(this.splitEl){
- this.splitEl.hide();
+ // If a CardLayout, the active item set
+ if(this.activeItem !== undefined && this.layout.setActiveItem){
+ var item = this.activeItem;
+ delete this.activeItem;
+ this.layout.setActiveItem(item);
+ }
+
+ // If we have no ownerCt, render and size all children
+ if(!this.ownerCt){
+ this.doLayout(false, true);
+ }
+
+ // This is a manually configured flag set by users in conjunction with renderTo.
+ // Not to be confused with the flag by the same name used in Layouts.
+ if(this.monitorResize === true){
+ Ext.EventManager.onWindowResize(this.doLayout, this, [false]);
}
- this.getCollapsedEl().show();
- this.panel.el.setStyle('z-index', 100);
- this.isCollapsed = true;
- this.layout.layout();
},
- // private
- onCollapse : function(animate){
- this.panel.el.setStyle('z-index', 1);
- if(this.lastAnim === false || this.panel.animCollapse === false){
- this.getCollapsedEl().dom.style.visibility = 'visible';
- }else{
- this.getCollapsedEl().slideIn(this.panel.slideAnchor, {duration:.2});
+ /**
+ * Returns the Element to be used to contain the child Components of this Container.
+ * An implementation is provided which returns the Container's {@link #getEl Element}, but
+ * if there is a more complex structure to a Container, this may be overridden to return
+ * the element into which the {@link #layout layout} renders child Components.
+ * @return {Ext.Element} The Element to render child Components into.
+ */
+ getLayoutTarget : function(){
+ return this.el;
+ },
+
+ // private - used as the key lookup function for the items collection
+ getComponentId : function(comp){
+ return comp.getItemId();
+ },
+
+ /**
+ * Adds {@link Ext.Component Component}(s) to this Container.
+ *
Description :
+ *
+ * - Fires the {@link #beforeadd} event before adding
+ * - The Container's {@link #defaults default config values} will be applied
+ * accordingly (see
{@link #defaults}
for details).
+ * - Fires the {@link #add} event after the component has been added.
+ *
+ *
Notes :
+ *
+ * - If the Container is already rendered when
add
+ * is called, you may need to call {@link #doLayout} to refresh the view which causes
+ * any unrendered child Components to be rendered. This is required so that you can
+ * add
multiple child components if needed while only refreshing the layout
+ * once. For example:
+var tb = new {@link Ext.Toolbar}();
+tb.render(document.body); // toolbar is rendered
+tb.add({text:'Button 1'}); // add multiple items ({@link #defaultType} for {@link Ext.Toolbar Toolbar} is 'button')
+tb.add({text:'Button 2'});
+tb.{@link #doLayout}(); // refresh the layout
+ *
+ * - Warning: Containers directly managed by the BorderLayout layout manager
+ * may not be removed or added. See the Notes for {@link Ext.layout.BorderLayout BorderLayout}
+ * for more details.
+ *
+ * @param {...Object/Array} component
+ * Either one or more Components to add or an Array of Components to add. See
+ * {@link #items}
for additional information.
+ * @return {Ext.Component/Array} The Components that were added.
+ */
+ add : function(comp){
+ this.initItems();
+ var args = arguments.length > 1;
+ if(args || Ext.isArray(comp)){
+ var result = [];
+ Ext.each(args ? arguments : comp, function(c){
+ result.push(this.add(c));
+ }, this);
+ return result;
}
- this.state.collapsed = true;
- this.panel.saveState();
+ var c = this.lookupComponent(this.applyDefaults(comp));
+ var index = this.items.length;
+ if(this.fireEvent('beforeadd', this, c, index) !== false && this.onBeforeAdd(c) !== false){
+ this.items.add(c);
+ // *onAdded
+ c.onAdded(this, index);
+ this.onAdd(c);
+ this.fireEvent('add', this, c, index);
+ }
+ return c;
+ },
+
+ onAdd : function(c){
+ // Empty template method
},
// private
- beforeExpand : function(animate){
- var c = this.getCollapsedEl();
- this.el.show();
- if(this.position == 'east' || this.position == 'west'){
- this.panel.setSize(undefined, c.getHeight());
- }else{
- this.panel.setSize(c.getWidth(), undefined);
+ onAdded : function(container, pos) {
+ //overridden here so we can cascade down, not worth creating a template method.
+ this.ownerCt = container;
+ this.initRef();
+ //initialize references for child items
+ this.cascade(function(c){
+ c.initRef();
+ });
+ this.fireEvent('added', this, container, pos);
+ },
+
+ /**
+ * Inserts a Component into this Container at a specified index. Fires the
+ * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
+ * Component has been inserted.
+ * @param {Number} index The index at which the Component will be inserted
+ * into the Container's items collection
+ * @param {Ext.Component} component The child Component to insert.
+ * Ext uses lazy rendering, and will only render the inserted Component should
+ * it become necessary.
+ * A Component config object may be passed in order to avoid the overhead of
+ * constructing a real Component object if lazy rendering might mean that the
+ * inserted Component will not be rendered immediately. To take advantage of
+ * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
+ * property to the registered type of the Component wanted.
+ * For a list of all available xtypes, see {@link Ext.Component}.
+ * @return {Ext.Component} component The Component (or config object) that was
+ * inserted with the Container's default config values applied.
+ */
+ insert : function(index, comp) {
+ var args = arguments,
+ length = args.length,
+ result = [],
+ i, c;
+
+ this.initItems();
+
+ if (length > 2) {
+ for (i = length - 1; i >= 1; --i) {
+ result.push(this.insert(index, args[i]));
+ }
+ return result;
}
- c.hide();
- c.dom.style.visibility = 'hidden';
- this.panel.el.setStyle('z-index', this.floatingZIndex);
+
+ c = this.lookupComponent(this.applyDefaults(comp));
+ index = Math.min(index, this.items.length);
+
+ if (this.fireEvent('beforeadd', this, c, index) !== false && this.onBeforeAdd(c) !== false) {
+ if (c.ownerCt == this) {
+ this.items.remove(c);
+ }
+ this.items.insert(index, c);
+ c.onAdded(this, index);
+ this.onAdd(c);
+ this.fireEvent('add', this, c, index);
+ }
+
+ return c;
},
// private
- onExpand : function(){
- this.isCollapsed = false;
- if(this.splitEl){
- this.splitEl.show();
+ applyDefaults : function(c){
+ var d = this.defaults;
+ if(d){
+ if(Ext.isFunction(d)){
+ d = d.call(this, c);
+ }
+ if(Ext.isString(c)){
+ c = Ext.ComponentMgr.get(c);
+ Ext.apply(c, d);
+ }else if(!c.events){
+ Ext.applyIf(c.isAction ? c.initialConfig : c, d);
+ }else{
+ Ext.apply(c, d);
+ }
}
- this.layout.layout();
- this.panel.el.setStyle('z-index', 1);
- this.state.collapsed = false;
- this.panel.saveState();
+ return c;
},
// private
- collapseClick : function(e){
- if(this.isSlid){
- e.stopPropagation();
- this.slideIn();
- }else{
- e.stopPropagation();
- this.slideOut();
+ onBeforeAdd : function(item){
+ if(item.ownerCt){
+ item.ownerCt.remove(item, false);
+ }
+ if(this.hideBorders === true){
+ item.border = (item.border === true);
}
},
- // private
- onHide : function(){
- if(this.isCollapsed){
- this.getCollapsedEl().hide();
- }else if(this.splitEl){
- this.splitEl.hide();
+ /**
+ * Removes a component from this container. Fires the {@link #beforeremove} event before removing, then fires
+ * the {@link #remove} event after the component has been removed.
+ * @param {Component/String} component The component reference or id to remove.
+ * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
+ * Defaults to the value of this Container's {@link #autoDestroy} config.
+ * @return {Ext.Component} component The Component that was removed.
+ */
+ remove : function(comp, autoDestroy){
+ this.initItems();
+ var c = this.getComponent(comp);
+ if(c && this.fireEvent('beforeremove', this, c) !== false){
+ this.doRemove(c, autoDestroy);
+ this.fireEvent('remove', this, c);
}
+ return c;
+ },
+
+ onRemove: function(c){
+ // Empty template method
},
// private
- onShow : function(){
- if(this.isCollapsed){
- this.getCollapsedEl().show();
- }else if(this.splitEl){
- this.splitEl.show();
+ doRemove: function(c, autoDestroy){
+ var l = this.layout,
+ hasLayout = l && this.rendered;
+
+ if(hasLayout){
+ l.onRemove(c);
+ }
+ this.items.remove(c);
+ c.onRemoved();
+ this.onRemove(c);
+ if(autoDestroy === true || (autoDestroy !== false && this.autoDestroy)){
+ c.destroy();
+ }
+ if(hasLayout){
+ l.afterRemove(c);
}
},
/**
- * True if this region is currently visible, else false.
- * @return {Boolean}
+ * Removes all components from this container.
+ * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
+ * Defaults to the value of this Container's {@link #autoDestroy} config.
+ * @return {Array} Array of the destroyed components
*/
- isVisible : function(){
- return !this.panel.hidden;
+ removeAll: function(autoDestroy){
+ this.initItems();
+ var item, rem = [], items = [];
+ this.items.each(function(i){
+ rem.push(i);
+ });
+ for (var i = 0, len = rem.length; i < len; ++i){
+ item = rem[i];
+ this.remove(item, autoDestroy);
+ if(item.ownerCt !== this){
+ items.push(item);
+ }
+ }
+ return items;
},
/**
- * Returns the current margins for this region. If the region is collapsed, the
- * {@link #cmargins} (collapsed margins) value will be returned, otherwise the
- * {@link #margins} value will be returned.
- * @return {Object} An object containing the element's margins: {left: (left
- * margin), top: (top margin), right: (right margin), bottom: (bottom margin)}
+ * Examines this container's {@link #items}
property
+ * and gets a direct child component of this container.
+ * @param {String/Number} comp This parameter may be any of the following:
+ *
+ * - a
String
: representing the {@link Ext.Component#itemId itemId}
+ * or {@link Ext.Component#id id}
of the child component
+ * - a
Number
: representing the position of the child component
+ * within the {@link #items}
property
+ *
+ * For additional information see {@link Ext.util.MixedCollection#get}.
+ * @return Ext.Component The component (if found).
*/
- getMargins : function(){
- return this.isCollapsed && this.cmargins ? this.cmargins : this.margins;
+ getComponent : function(comp){
+ if(Ext.isObject(comp)){
+ comp = comp.getItemId();
+ }
+ return this.items.get(comp);
},
- /**
- * Returns the current size of this region. If the region is collapsed, the size of the
- * collapsedEl will be returned, otherwise the size of the region's panel will be returned.
- * @return {Object} An object containing the element's size: {width: (element width),
- * height: (element height)}
- */
- getSize : function(){
- return this.isCollapsed ? this.getCollapsedEl().getSize() : this.panel.getSize();
+ // private
+ lookupComponent : function(comp){
+ if(Ext.isString(comp)){
+ return Ext.ComponentMgr.get(comp);
+ }else if(!comp.events){
+ return this.createComponent(comp);
+ }
+ return comp;
},
- /**
- * Sets the specified panel as the container element for this region.
- * @param {Ext.Panel} panel The new panel
- */
- setPanel : function(panel){
- this.panel = panel;
+ // private
+ createComponent : function(config, defaultType){
+ if (config.render) {
+ return config;
+ }
+ // add in ownerCt at creation time but then immediately
+ // remove so that onBeforeAdd can handle it
+ var c = Ext.create(Ext.apply({
+ ownerCt: this
+ }, config), defaultType || this.defaultType);
+ delete c.initialConfig.ownerCt;
+ delete c.ownerCt;
+ return c;
},
/**
- * Returns the minimum allowable width for this region.
- * @return {Number} The minimum width
+ * @private
+ * We can only lay out if there is a view area in which to layout.
+ * display:none on the layout target, *or any of its parent elements* will mean it has no view area.
*/
- getMinWidth: function(){
- return this.minWidth;
+ canLayout : function() {
+ var el = this.getVisibilityEl();
+ return el && el.dom && !el.isStyle("display", "none");
},
/**
- * Returns the minimum allowable height for this region.
- * @return {Number} The minimum height
+ * Force this container's layout to be recalculated. A call to this function is required after adding a new component
+ * to an already rendered container, or possibly after changing sizing/position properties of child components.
+ * @param {Boolean} shallow (optional) True to only calc the layout of this component, and let child components auto
+ * calc layouts as required (defaults to false, which calls doLayout recursively for each subcontainer)
+ * @param {Boolean} force (optional) True to force a layout to occur, even if the item is hidden.
+ * @return {Ext.Container} this
*/
- getMinHeight: function(){
- return this.minHeight;
- },
- // private
- applyLayoutCollapsed : function(box){
- var ce = this.getCollapsedEl();
- ce.setLeftTop(box.x, box.y);
- ce.setSize(box.width, box.height);
+ doLayout : function(shallow, force){
+ var rendered = this.rendered,
+ forceLayout = force || this.forceLayout;
+
+ if(this.collapsed || !this.canLayout()){
+ this.deferLayout = this.deferLayout || !shallow;
+ if(!forceLayout){
+ return;
+ }
+ shallow = shallow && !this.deferLayout;
+ } else {
+ delete this.deferLayout;
+ }
+ if(rendered && this.layout){
+ this.layout.layout();
+ }
+ if(shallow !== true && this.items){
+ var cs = this.items.items;
+ for(var i = 0, len = cs.length; i < len; i++){
+ var c = cs[i];
+ if(c.doLayout){
+ c.doLayout(false, forceLayout);
+ }
+ }
+ }
+ if(rendered){
+ this.onLayout(shallow, forceLayout);
+ }
+ // Initial layout completed
+ this.hasLayout = true;
+ delete this.forceLayout;
},
+ onLayout : Ext.emptyFn,
+
// private
- applyLayout : function(box){
- if(this.isCollapsed){
- this.applyLayoutCollapsed(box);
- }else{
- this.panel.setPosition(box.x, box.y);
- this.panel.setSize(box.width, box.height);
+ shouldBufferLayout: function(){
+ /*
+ * Returns true if the container should buffer a layout.
+ * This is true only if the container has previously been laid out
+ * and has a parent container that is pending a layout.
+ */
+ var hl = this.hasLayout;
+ if(this.ownerCt){
+ // Only ever buffer if we've laid out the first time and we have one pending.
+ return hl ? !this.hasLayoutPending() : false;
}
+ // Never buffer initial layout
+ return hl;
},
// private
- beforeSlide: function(){
- this.panel.beforeEffect();
+ hasLayoutPending: function(){
+ // Traverse hierarchy to see if any parent container has a pending layout.
+ var pending = false;
+ this.ownerCt.bubble(function(c){
+ if(c.layoutPending){
+ pending = true;
+ return false;
+ }
+ });
+ return pending;
},
- // private
- afterSlide : function(){
- this.panel.afterEffect();
+ onShow : function(){
+ // removes css classes that were added to hide
+ Ext.Container.superclass.onShow.call(this);
+ // If we were sized during the time we were hidden, layout.
+ if(Ext.isDefined(this.deferLayout)){
+ delete this.deferLayout;
+ this.doLayout(true);
+ }
},
- // private
- initAutoHide : function(){
- if(this.autoHide !== false){
- if(!this.autoHideHd){
- var st = new Ext.util.DelayedTask(this.slideIn, this);
- this.autoHideHd = {
- "mouseout": function(e){
- if(!e.within(this.el, true)){
- st.delay(500);
- }
- },
- "mouseover" : function(e){
- st.cancel();
- },
- scope : this
- };
- }
- this.el.on(this.autoHideHd);
+ /**
+ * Returns the layout currently in use by the container. If the container does not currently have a layout
+ * set, a default {@link Ext.layout.ContainerLayout} will be created and set as the container's layout.
+ * @return {ContainerLayout} layout The container's layout
+ */
+ getLayout : function(){
+ if(!this.layout){
+ var layout = new Ext.layout.AutoLayout(this.layoutConfig);
+ this.setLayout(layout);
}
+ return this.layout;
},
// private
- clearAutoHide : function(){
- if(this.autoHide !== false){
- this.el.un("mouseout", this.autoHideHd.mouseout);
- this.el.un("mouseover", this.autoHideHd.mouseover);
+ beforeDestroy : function(){
+ var c;
+ if(this.items){
+ while(c = this.items.first()){
+ this.doRemove(c, true);
+ }
+ }
+ if(this.monitorResize){
+ Ext.EventManager.removeResizeListener(this.doLayout, this);
+ }
+ Ext.destroy(this.layout);
+ Ext.Container.superclass.beforeDestroy.call(this);
+ },
+
+ /**
+ * Cascades down the component/container heirarchy from this component (called first), calling the specified function with
+ * each component. The scope (this) of
+ * function call will be the scope provided or the current component. The arguments to the function
+ * will be the args provided or the current component. If the function returns false at any point,
+ * the cascade is stopped on that branch.
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional) The scope of the function (defaults to current component)
+ * @param {Array} args (optional) The args to call the function with (defaults to passing the current component)
+ * @return {Ext.Container} this
+ */
+ cascade : function(fn, scope, args){
+ if(fn.apply(scope || this, args || [this]) !== false){
+ if(this.items){
+ var cs = this.items.items;
+ for(var i = 0, len = cs.length; i < len; i++){
+ if(cs[i].cascade){
+ cs[i].cascade(fn, scope, args);
+ }else{
+ fn.apply(scope || cs[i], args || [cs[i]]);
+ }
+ }
+ }
}
+ return this;
},
- // private
- clearMonitor : function(){
- Ext.getDoc().un("click", this.slideInIf, this);
+ /**
+ * Find a component under this container at any level by id
+ * @param {String} id
+ * @deprecated Fairly useless method, since you can just use Ext.getCmp. Should be removed for 4.0
+ * If you need to test if an id belongs to a container, you can use getCmp and findParent*.
+ * @return Ext.Component
+ */
+ findById : function(id){
+ var m = null,
+ ct = this;
+ this.cascade(function(c){
+ if(ct != c && c.id === id){
+ m = c;
+ return false;
+ }
+ });
+ return m;
},
/**
- * If this Region is {@link #floatable}, this method slides this Region into full visibility over the top
- * of the center Region where it floats until either {@link #slideIn} is called, or other regions of the layout
- * are clicked, or the mouse exits the Region.
+ * Find a component under this container at any level by xtype or class
+ * @param {String/Class} xtype The xtype string for a component, or the class of the component directly
+ * @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is
+ * the default), or true to check whether this Component is directly of the specified xtype.
+ * @return {Array} Array of Ext.Components
*/
- slideOut : function(){
- if(this.isSlid || this.el.hasActiveFx()){
- return;
- }
- this.isSlid = true;
- var ts = this.panel.tools;
- if(ts && ts.toggle){
- ts.toggle.hide();
- }
- this.el.show();
- if(this.position == 'east' || this.position == 'west'){
- this.panel.setSize(undefined, this.collapsedEl.getHeight());
- }else{
- this.panel.setSize(this.collapsedEl.getWidth(), undefined);
- }
- this.restoreLT = [this.el.dom.style.left, this.el.dom.style.top];
- this.el.alignTo(this.collapsedEl, this.getCollapseAnchor());
- this.el.setStyle("z-index", this.floatingZIndex+2);
- this.panel.el.replaceClass('x-panel-collapsed', 'x-panel-floating');
- if(this.animFloat !== false){
- this.beforeSlide();
- this.el.slideIn(this.getSlideAnchor(), {
- callback: function(){
- this.afterSlide();
- this.initAutoHide();
- Ext.getDoc().on("click", this.slideInIf, this);
- },
- scope: this,
- block: true
- });
- }else{
- this.initAutoHide();
- Ext.getDoc().on("click", this.slideInIf, this);
- }
+ findByType : function(xtype, shallow){
+ return this.findBy(function(c){
+ return c.isXType(xtype, shallow);
+ });
},
- // private
- afterSlideIn : function(){
- this.clearAutoHide();
- this.isSlid = false;
- this.clearMonitor();
- this.el.setStyle("z-index", "");
- this.panel.el.replaceClass('x-panel-floating', 'x-panel-collapsed');
- this.el.dom.style.left = this.restoreLT[0];
- this.el.dom.style.top = this.restoreLT[1];
+ /**
+ * Find a component under this container at any level by property
+ * @param {String} prop
+ * @param {String} value
+ * @return {Array} Array of Ext.Components
+ */
+ find : function(prop, value){
+ return this.findBy(function(c){
+ return c[prop] === value;
+ });
+ },
- var ts = this.panel.tools;
- if(ts && ts.toggle){
- ts.toggle.show();
- }
+ /**
+ * Find a component under this container at any level by a custom function. If the passed function returns
+ * true, the component will be included in the results. The passed function is called with the arguments (component, this container).
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional)
+ * @return {Array} Array of Ext.Components
+ */
+ findBy : function(fn, scope){
+ var m = [], ct = this;
+ this.cascade(function(c){
+ if(ct != c && fn.call(scope || c, c, ct) === true){
+ m.push(c);
+ }
+ });
+ return m;
},
/**
- * If this Region is {@link #floatable}, and this Region has been slid into floating visibility, then this method slides
- * this region back into its collapsed state.
+ * Get a component contained by this container (alias for items.get(key))
+ * @param {String/Number} key The index or id of the component
+ * @deprecated Should be removed in 4.0, since getComponent does the same thing.
+ * @return {Ext.Component} Ext.Component
*/
- slideIn : function(cb){
- if(!this.isSlid || this.el.hasActiveFx()){
- Ext.callback(cb);
- return;
+ get : function(key){
+ return this.getComponent(key);
+ }
+});
+
+Ext.Container.LAYOUTS = {};
+Ext.reg('container', Ext.Container);
+/**
+ * @class Ext.layout.ContainerLayout
+ *
This class is intended to be extended or created via the {@link Ext.Container#layout layout}
+ * configuration property. See {@link Ext.Container#layout} for additional details.
+ */
+Ext.layout.ContainerLayout = Ext.extend(Object, {
+ /**
+ * @cfg {String} extraCls
+ * An optional extra CSS class that will be added to the container. This can be useful for adding
+ * customized styles to the container or any of its children using standard CSS rules. See
+ * {@link Ext.Component}.{@link Ext.Component#ctCls ctCls} also.
+ * Note: extraCls defaults to '' except for the following classes
+ * which assign a value by default:
+ *
+ * - {@link Ext.layout.AbsoluteLayout Absolute Layout} : 'x-abs-layout-item'
+ * - {@link Ext.layout.Box Box Layout} : 'x-box-item'
+ * - {@link Ext.layout.ColumnLayout Column Layout} : 'x-column'
+ *
+ * To configure the above Classes with an extra CSS class append to the default. For example,
+ * for ColumnLayout:
+ * extraCls: 'x-column custom-class'
+ *
+ *
+ */
+ /**
+ * @cfg {Boolean} renderHidden
+ * True to hide each contained item on render (defaults to false).
+ */
+
+ /**
+ * A reference to the {@link Ext.Component} that is active. For example,
+ * if(myPanel.layout.activeItem.id == 'item-1') { ... }
+ *
+ * activeItem only applies to layout styles that can display items one at a time
+ * (like {@link Ext.layout.AccordionLayout}, {@link Ext.layout.CardLayout}
+ * and {@link Ext.layout.FitLayout}). Read-only. Related to {@link Ext.Container#activeItem}.
+ * @type {Ext.Component}
+ * @property activeItem
+ */
+
+ // private
+ monitorResize:false,
+ // private
+ activeItem : null,
+
+ constructor : function(config){
+ this.id = Ext.id(null, 'ext-layout-');
+ Ext.apply(this, config);
+ },
+
+ type: 'container',
+
+ /* Workaround for how IE measures autoWidth elements. It prefers bottom-up measurements
+ whereas other browser prefer top-down. We will hide all target child elements before we measure and
+ put them back to get an accurate measurement.
+ */
+ IEMeasureHack : function(target, viewFlag) {
+ var tChildren = target.dom.childNodes, tLen = tChildren.length, c, d = [], e, i, ret;
+ for (i = 0 ; i < tLen ; i++) {
+ c = tChildren[i];
+ e = Ext.get(c);
+ if (e) {
+ d[i] = e.getStyle('display');
+ e.setStyle({display: 'none'});
+ }
}
- this.isSlid = false;
- if(this.animFloat !== false){
- this.beforeSlide();
- this.el.slideOut(this.getSlideAnchor(), {
- callback: function(){
- this.el.hide();
- this.afterSlide();
- this.afterSlideIn();
- Ext.callback(cb);
- },
- scope: this,
- block: true
- });
- }else{
- this.el.hide();
- this.afterSlideIn();
+ ret = target ? target.getViewSize(viewFlag) : {};
+ for (i = 0 ; i < tLen ; i++) {
+ c = tChildren[i];
+ e = Ext.get(c);
+ if (e) {
+ e.setStyle({display: d[i]});
+ }
}
+ return ret;
},
+ // Placeholder for the derived layouts
+ getLayoutTargetSize : Ext.EmptyFn,
+
// private
- slideInIf : function(e){
- if(!e.within(this.el)){
- this.slideIn();
+ layout : function(){
+ var ct = this.container, target = ct.getLayoutTarget();
+ if(!(this.hasLayout || Ext.isEmpty(this.targetCls))){
+ target.addClass(this.targetCls);
}
+ this.onLayout(ct, target);
+ ct.fireEvent('afterlayout', ct, this);
},
// private
- anchors : {
- "west" : "left",
- "east" : "right",
- "north" : "top",
- "south" : "bottom"
+ onLayout : function(ct, target){
+ this.renderAll(ct, target);
},
// private
- sanchors : {
- "west" : "l",
- "east" : "r",
- "north" : "t",
- "south" : "b"
+ isValidParent : function(c, target){
+ return target && c.getPositionEl().dom.parentNode == (target.dom || target);
},
// private
- canchors : {
- "west" : "tl-tr",
- "east" : "tr-tl",
- "north" : "tl-bl",
- "south" : "bl-tl"
+ renderAll : function(ct, target){
+ var items = ct.items.items, i, c, len = items.length;
+ for(i = 0; i < len; i++) {
+ c = items[i];
+ if(c && (!c.rendered || !this.isValidParent(c, target))){
+ this.renderItem(c, i, target);
+ }
+ }
},
- // private
- getAnchor : function(){
- return this.anchors[this.position];
+ /**
+ * @private
+ * Renders the given Component into the target Element. If the Component is already rendered,
+ * it is moved to the provided target instead.
+ * @param {Ext.Component} c The Component to render
+ * @param {Number} position The position within the target to render the item to
+ * @param {Ext.Element} target The target Element
+ */
+ renderItem : function(c, position, target){
+ if (c) {
+ if (!c.rendered) {
+ c.render(target, position);
+ this.configureItem(c);
+ } else if (!this.isValidParent(c, target)) {
+ if (Ext.isNumber(position)) {
+ position = target.dom.childNodes[position];
+ }
+
+ target.dom.insertBefore(c.getPositionEl().dom, position || null);
+ c.container = target;
+ this.configureItem(c);
+ }
+ }
},
- // private
- getCollapseAnchor : function(){
- return this.canchors[this.position];
+ // private.
+ // Get all rendered items to lay out.
+ getRenderedItems: function(ct){
+ var t = ct.getLayoutTarget(), cti = ct.items.items, len = cti.length, i, c, items = [];
+ for (i = 0; i < len; i++) {
+ if((c = cti[i]).rendered && this.isValidParent(c, t) && c.shouldLayout !== false){
+ items.push(c);
+ }
+ };
+ return items;
},
- // private
- getSlideAnchor : function(){
- return this.sanchors[this.position];
+ /**
+ * @private
+ * Applies extraCls and hides the item if renderHidden is true
+ */
+ configureItem: function(c){
+ if (this.extraCls) {
+ var t = c.getPositionEl ? c.getPositionEl() : c;
+ t.addClass(this.extraCls);
+ }
+
+ // If we are forcing a layout, do so *before* we hide so elements have height/width
+ if (c.doLayout && this.forceLayout) {
+ c.doLayout();
+ }
+ if (this.renderHidden && c != this.activeItem) {
+ c.hide();
+ }
+ },
+
+ onRemove: function(c){
+ if(this.activeItem == c){
+ delete this.activeItem;
+ }
+ if(c.rendered && this.extraCls){
+ var t = c.getPositionEl ? c.getPositionEl() : c;
+ t.removeClass(this.extraCls);
+ }
+ },
+
+ afterRemove: function(c){
+ if(c.removeRestore){
+ c.removeMode = 'container';
+ delete c.removeRestore;
+ }
},
// private
- getAlignAdj : function(){
- var cm = this.cmargins;
- switch(this.position){
- case "west":
- return [0, 0];
- break;
- case "east":
- return [0, 0];
- break;
- case "north":
- return [0, 0];
- break;
- case "south":
- return [0, 0];
- break;
+ onResize: function(){
+ var ct = this.container,
+ b;
+ if(ct.collapsed){
+ return;
+ }
+ if(b = ct.bufferResize && ct.shouldBufferLayout()){
+ if(!this.resizeTask){
+ this.resizeTask = new Ext.util.DelayedTask(this.runLayout, this);
+ this.resizeBuffer = Ext.isNumber(b) ? b : 50;
+ }
+ ct.layoutPending = true;
+ this.resizeTask.delay(this.resizeBuffer);
+ }else{
+ this.runLayout();
}
},
+ runLayout: function(){
+ var ct = this.container;
+ this.layout();
+ ct.onLayout();
+ delete ct.layoutPending;
+ },
+
// private
- getExpandAdj : function(){
- var c = this.collapsedEl, cm = this.cmargins;
- switch(this.position){
- case "west":
- return [-(cm.right+c.getWidth()+cm.left), 0];
- break;
- case "east":
- return [cm.right+c.getWidth()+cm.left, 0];
- break;
- case "north":
- return [0, -(cm.top+cm.bottom+c.getHeight())];
- break;
- case "south":
- return [0, cm.top+cm.bottom+c.getHeight()];
- break;
+ setContainer : function(ct){
+ /**
+ * This monitorResize flag will be renamed soon as to avoid confusion
+ * with the Container version which hooks onWindowResize to doLayout
+ *
+ * monitorResize flag in this context attaches the resize event between
+ * a container and it's layout
+ */
+ if(this.monitorResize && ct != this.container){
+ var old = this.container;
+ if(old){
+ old.un(old.resizeEvent, this.onResize, this);
+ }
+ if(ct){
+ ct.on(ct.resizeEvent, this.onResize, this);
+ }
}
- }
-};
+ this.container = ct;
+ },
-/**
- * @class Ext.layout.BorderLayout.SplitRegion
- * @extends Ext.layout.BorderLayout.Region
- * This is a specialized type of {@link Ext.layout.BorderLayout.Region BorderLayout region} that
- * has a built-in {@link Ext.SplitBar} for user resizing of regions. The movement of the split bar
- * is configurable to move either {@link #tickSize smooth or incrementally}.
- * @constructor
- * Create a new SplitRegion.
- * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region.
- * @param {Object} config The configuration options
- * @param {String} position The region position. Valid values are: north, south, east, west and center. Every
- * BorderLayout must have a center region for the primary content -- all other regions are optional.
- */
-Ext.layout.BorderLayout.SplitRegion = function(layout, config, pos){
- Ext.layout.BorderLayout.SplitRegion.superclass.constructor.call(this, layout, config, pos);
- // prevent switch
- this.applyLayout = this.applyFns[pos];
-};
-
-Ext.extend(Ext.layout.BorderLayout.SplitRegion, Ext.layout.BorderLayout.Region, {
- /**
- * @cfg {Number} tickSize
- * The increment, in pixels by which to move this Region's {@link Ext.SplitBar SplitBar}.
- * By default, the {@link Ext.SplitBar SplitBar} moves smoothly.
- */
/**
- * @cfg {String} splitTip
- * The tooltip to display when the user hovers over a
- * {@link Ext.layout.BorderLayout.Region#collapsible non-collapsible} region's split bar
- * (defaults to "Drag to resize."). Only applies if
- * {@link #useSplitTips} = true.
+ * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
+ * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
+ * @param {Number|String} v The encoded margins
+ * @return {Object} An object with margin sizes for top, right, bottom and left
*/
- splitTip : "Drag to resize.",
+ parseMargins : function(v){
+ if (Ext.isNumber(v)) {
+ v = v.toString();
+ }
+ var ms = v.split(' '),
+ len = ms.length;
+
+ if (len == 1) {
+ ms[1] = ms[2] = ms[3] = ms[0];
+ } else if(len == 2) {
+ ms[2] = ms[0];
+ ms[3] = ms[1];
+ } else if(len == 3) {
+ ms[3] = ms[1];
+ }
+
+ return {
+ top :parseInt(ms[0], 10) || 0,
+ right :parseInt(ms[1], 10) || 0,
+ bottom:parseInt(ms[2], 10) || 0,
+ left :parseInt(ms[3], 10) || 0
+ };
+ },
+
/**
- * @cfg {String} collapsibleSplitTip
- * The tooltip to display when the user hovers over a
- * {@link Ext.layout.BorderLayout.Region#collapsible collapsible} region's split bar
- * (defaults to "Drag to resize. Double click to hide."). Only applies if
- * {@link #useSplitTips} = true.
+ * The {@link Ext.Template Ext.Template} used by Field rendering layout classes (such as
+ * {@link Ext.layout.FormLayout}) to create the DOM structure of a fully wrapped,
+ * labeled and styled form Field. A default Template is supplied, but this may be
+ * overriden to create custom field structures. The template processes values returned from
+ * {@link Ext.layout.FormLayout#getTemplateArgs}.
+ * @property fieldTpl
+ * @type Ext.Template
*/
- collapsibleSplitTip : "Drag to resize. Double click to hide.",
- /**
- * @cfg {Boolean} useSplitTips
- * true to display a tooltip when the user hovers over a region's split bar
- * (defaults to false). The tooltip text will be the value of either
- * {@link #splitTip} or {@link #collapsibleSplitTip} as appropriate.
+ fieldTpl: (function() {
+ var t = new Ext.Template(
+ '',
+ '',
+ '',
+ '',
+ ''
+ );
+ t.disableFormats = true;
+ return t.compile();
+ })(),
+
+ /*
+ * Destroys this layout. This is a template method that is empty by default, but should be implemented
+ * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
+ * @protected
*/
- useSplitTips : false,
+ destroy : function(){
+ // Stop any buffered layout tasks
+ if(this.resizeTask && this.resizeTask.cancel){
+ this.resizeTask.cancel();
+ }
+ if(this.container) {
+ this.container.un(this.container.resizeEvent, this.onResize, this);
+ }
+ if(!Ext.isEmpty(this.targetCls)){
+ var target = this.container.getLayoutTarget();
+ if(target){
+ target.removeClass(this.targetCls);
+ }
+ }
+ }
+});/**
+ * @class Ext.layout.AutoLayout
+ * The AutoLayout is the default layout manager delegated by {@link Ext.Container} to
+ * render any child Components when no {@link Ext.Container#layout layout} is configured into
+ * a {@link Ext.Container Container}.. AutoLayout provides only a passthrough of any layout calls
+ * to any child containers.
+ */
+Ext.layout.AutoLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ type: 'auto',
+
+ monitorResize: true,
+ onLayout : function(ct, target){
+ Ext.layout.AutoLayout.superclass.onLayout.call(this, ct, target);
+ var cs = this.getRenderedItems(ct), len = cs.length, i, c;
+ for(i = 0; i < len; i++){
+ c = cs[i];
+ if (c.doLayout){
+ // Shallow layout children
+ c.doLayout(true);
+ }
+ }
+ }
+});
+
+Ext.Container.LAYOUTS['auto'] = Ext.layout.AutoLayout;
+/**
+ * @class Ext.layout.FitLayout
+ * @extends Ext.layout.ContainerLayout
+ * This is a base class for layouts that contain a single item that automatically expands to fill the layout's
+ * container. This class is intended to be extended or created via the layout:'fit' {@link Ext.Container#layout}
+ * config, and should generally not need to be created directly via the new keyword.
+ * FitLayout does not have any direct config options (other than inherited ones). To fit a panel to a container
+ * using FitLayout, simply set layout:'fit' on the container and add a single panel to it. If the container has
+ * multiple panels, only the first one will be displayed. Example usage:
+ *
+var p = new Ext.Panel({
+ title: 'Fit Layout',
+ layout:'fit',
+ items: {
+ title: 'Inner Panel',
+ html: '<p>This is the inner panel content</p>',
+ border: false
+ }
+});
+
+ */
+Ext.layout.FitLayout = Ext.extend(Ext.layout.ContainerLayout, {
// private
- splitSettings : {
- north : {
- orientation: Ext.SplitBar.VERTICAL,
- placement: Ext.SplitBar.TOP,
- maxFn : 'getVMaxSize',
- minProp: 'minHeight',
- maxProp: 'maxHeight'
- },
- south : {
- orientation: Ext.SplitBar.VERTICAL,
- placement: Ext.SplitBar.BOTTOM,
- maxFn : 'getVMaxSize',
- minProp: 'minHeight',
- maxProp: 'maxHeight'
- },
- east : {
- orientation: Ext.SplitBar.HORIZONTAL,
- placement: Ext.SplitBar.RIGHT,
- maxFn : 'getHMaxSize',
- minProp: 'minWidth',
- maxProp: 'maxWidth'
- },
- west : {
- orientation: Ext.SplitBar.HORIZONTAL,
- placement: Ext.SplitBar.LEFT,
- maxFn : 'getHMaxSize',
- minProp: 'minWidth',
- maxProp: 'maxWidth'
+ monitorResize:true,
+
+ type: 'fit',
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget();
+ if (!target) {
+ return {};
}
+ // Style Sized (scrollbars not included)
+ return target.getStyleSize();
},
// private
- applyFns : {
- west : function(box){
- if(this.isCollapsed){
- return this.applyLayoutCollapsed(box);
- }
- var sd = this.splitEl.dom, s = sd.style;
- this.panel.setPosition(box.x, box.y);
- var sw = sd.offsetWidth;
- s.left = (box.x+box.width-sw)+'px';
- s.top = (box.y)+'px';
- s.height = Math.max(0, box.height)+'px';
- this.panel.setSize(box.width-sw, box.height);
- },
- east : function(box){
- if(this.isCollapsed){
- return this.applyLayoutCollapsed(box);
- }
- var sd = this.splitEl.dom, s = sd.style;
- var sw = sd.offsetWidth;
- this.panel.setPosition(box.x+sw, box.y);
- s.left = (box.x)+'px';
- s.top = (box.y)+'px';
- s.height = Math.max(0, box.height)+'px';
- this.panel.setSize(box.width-sw, box.height);
- },
- north : function(box){
- if(this.isCollapsed){
- return this.applyLayoutCollapsed(box);
- }
- var sd = this.splitEl.dom, s = sd.style;
- var sh = sd.offsetHeight;
- this.panel.setPosition(box.x, box.y);
- s.left = (box.x)+'px';
- s.top = (box.y+box.height-sh)+'px';
- s.width = Math.max(0, box.width)+'px';
- this.panel.setSize(box.width, box.height-sh);
- },
- south : function(box){
- if(this.isCollapsed){
- return this.applyLayoutCollapsed(box);
- }
- var sd = this.splitEl.dom, s = sd.style;
- var sh = sd.offsetHeight;
- this.panel.setPosition(box.x, box.y+sh);
- s.left = (box.x)+'px';
- s.top = (box.y)+'px';
- s.width = Math.max(0, box.width)+'px';
- this.panel.setSize(box.width, box.height-sh);
+ onLayout : function(ct, target){
+ Ext.layout.FitLayout.superclass.onLayout.call(this, ct, target);
+ if(!ct.collapsed){
+ this.setItemSize(this.activeItem || ct.items.itemAt(0), this.getLayoutTargetSize());
}
},
// private
- render : function(ct, p){
- Ext.layout.BorderLayout.SplitRegion.superclass.render.call(this, ct, p);
+ setItemSize : function(item, size){
+ if(item && size.height > 0){ // display none?
+ item.setSize(size);
+ }
+ }
+});
+Ext.Container.LAYOUTS['fit'] = Ext.layout.FitLayout;/**
+ * @class Ext.layout.CardLayout
+ * @extends Ext.layout.FitLayout
+ * This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be
+ * visible at any given time. This layout style is most commonly used for wizards, tab implementations, etc.
+ * This class is intended to be extended or created via the layout:'card' {@link Ext.Container#layout} config,
+ * and should generally not need to be created directly via the new keyword.
+ * The CardLayout's focal method is {@link #setActiveItem}. Since only one panel is displayed at a time,
+ * the only way to move from one Component to the next is by calling setActiveItem, passing the id or index of
+ * the next panel to display. The layout itself does not provide a user interface for handling this navigation,
+ * so that functionality must be provided by the developer.
+ * In the following example, a simplistic wizard setup is demonstrated. A button bar is added
+ * to the footer of the containing panel to provide navigation buttons. The buttons will be handled by a
+ * common navigation routine -- for this example, the implementation of that routine has been ommitted since
+ * it can be any type of custom logic. Note that other uses of a CardLayout (like a tab control) would require a
+ * completely different implementation. For serious implementations, a better approach would be to extend
+ * CardLayout to provide the custom functionality needed. Example usage:
+ *
+var navHandler = function(direction){
+ // This routine could contain business logic required to manage the navigation steps.
+ // It would call setActiveItem as needed, manage navigation button state, handle any
+ // branching logic that might be required, handle alternate actions like cancellation
+ // or finalization, etc. A complete wizard implementation could get pretty
+ // sophisticated depending on the complexity required, and should probably be
+ // done as a subclass of CardLayout in a real-world implementation.
+};
- var ps = this.position;
+var card = new Ext.Panel({
+ title: 'Example Wizard',
+ layout:'card',
+ activeItem: 0, // make sure the active item is set on the container config!
+ bodyStyle: 'padding:15px',
+ defaults: {
+ // applied to each contained panel
+ border:false
+ },
+ // just an example of one possible navigation scheme, using buttons
+ bbar: [
+ {
+ id: 'move-prev',
+ text: 'Back',
+ handler: navHandler.createDelegate(this, [-1]),
+ disabled: true
+ },
+ '->', // greedy spacer so that the buttons are aligned to each side
+ {
+ id: 'move-next',
+ text: 'Next',
+ handler: navHandler.createDelegate(this, [1])
+ }
+ ],
+ // the panels (or "cards") within the layout
+ items: [{
+ id: 'card-0',
+ html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
+ },{
+ id: 'card-1',
+ html: '<p>Step 2 of 3</p>'
+ },{
+ id: 'card-2',
+ html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
+ }]
+});
+
+ */
+Ext.layout.CardLayout = Ext.extend(Ext.layout.FitLayout, {
+ /**
+ * @cfg {Boolean} deferredRender
+ * True to render each contained item at the time it becomes active, false to render all contained items
+ * as soon as the layout is rendered (defaults to false). If there is a significant amount of content or
+ * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
+ * true might improve performance.
+ */
+ deferredRender : false,
- this.splitEl = ct.createChild({
- cls: "x-layout-split x-layout-split-"+ps, html: " ",
- id: this.panel.id + '-xsplit'
- });
+ /**
+ * @cfg {Boolean} layoutOnCardChange
+ * True to force a layout of the active item when the active card is changed. Defaults to false.
+ */
+ layoutOnCardChange : false,
- if(this.collapseMode == 'mini'){
- this.miniSplitEl = this.splitEl.createChild({
- cls: "x-layout-mini x-layout-mini-"+ps, html: " "
- });
- this.miniSplitEl.addClassOnOver('x-layout-mini-over');
- this.miniSplitEl.on('click', this.onCollapseClick, this, {stopEvent:true});
- }
+ /**
+ * @cfg {Boolean} renderHidden @hide
+ */
+ // private
+ renderHidden : true,
- var s = this.splitSettings[ps];
+ type: 'card',
- this.split = new Ext.SplitBar(this.splitEl.dom, p.el, s.orientation);
- this.split.tickSize = this.tickSize;
- this.split.placement = s.placement;
- this.split.getMaximumSize = this[s.maxFn].createDelegate(this);
- this.split.minSize = this.minSize || this[s.minProp];
- this.split.on("beforeapply", this.onSplitMove, this);
- this.split.useShim = this.useShim === true;
- this.maxSize = this.maxSize || this[s.maxProp];
+ /**
+ * Sets the active (visible) item in the layout.
+ * @param {String/Number} item The string component id or numeric index of the item to activate
+ */
+ setActiveItem : function(item){
+ var ai = this.activeItem,
+ ct = this.container;
+ item = ct.getComponent(item);
- if(p.hidden){
- this.splitEl.hide();
- }
+ // Is this a valid, different card?
+ if(item && ai != item){
- if(this.useSplitTips){
- this.splitEl.dom.title = this.collapsible ? this.collapsibleSplitTip : this.splitTip;
- }
- if(this.collapsible){
- this.splitEl.on("dblclick", this.onCollapseClick, this);
+ // Changing cards, hide the current one
+ if(ai){
+ ai.hide();
+ if (ai.hidden !== true) {
+ return false;
+ }
+ ai.fireEvent('deactivate', ai);
+ }
+
+ var layout = item.doLayout && (this.layoutOnCardChange || !item.rendered);
+
+ // Change activeItem reference
+ this.activeItem = item;
+
+ // The container is about to get a recursive layout, remove any deferLayout reference
+ // because it will trigger a redundant layout.
+ delete item.deferLayout;
+
+ // Show the new component
+ item.show();
+
+ this.layout();
+
+ if(layout){
+ item.doLayout();
+ }
+ item.fireEvent('activate', item);
}
},
- //docs inherit from superclass
- getSize : function(){
- if(this.isCollapsed){
- return this.collapsedEl.getSize();
- }
- var s = this.panel.getSize();
- if(this.position == 'north' || this.position == 'south'){
- s.height += this.splitEl.dom.offsetHeight;
+ // private
+ renderAll : function(ct, target){
+ if(this.deferredRender){
+ this.renderItem(this.activeItem, undefined, target);
}else{
- s.width += this.splitEl.dom.offsetWidth;
+ Ext.layout.CardLayout.superclass.renderAll.call(this, ct, target);
}
- return s;
- },
+ }
+});
+Ext.Container.LAYOUTS['card'] = Ext.layout.CardLayout;
+/**
+ * @class Ext.layout.AnchorLayout
+ * @extends Ext.layout.ContainerLayout
+ * This is a layout that enables anchoring of contained elements relative to the container's dimensions.
+ * If the container is resized, all anchored items are automatically rerendered according to their
+ * {@link #anchor} rules.
+ * This class is intended to be extended or created via the layout:'anchor' {@link Ext.Container#layout}
+ * config, and should generally not need to be created directly via the new keyword.
+ * AnchorLayout does not have any direct config options (other than inherited ones). By default,
+ * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
+ * container using the AnchorLayout can supply an anchoring-specific config property of anchorSize.
+ * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
+ * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
+ * logic if necessary. For example:
+ *
+var viewport = new Ext.Viewport({
+ layout:'anchor',
+ anchorSize: {width:800, height:600},
+ items:[{
+ title:'Item 1',
+ html:'Content 1',
+ width:800,
+ anchor:'right 20%'
+ },{
+ title:'Item 2',
+ html:'Content 2',
+ width:300,
+ anchor:'50% 30%'
+ },{
+ title:'Item 3',
+ html:'Content 3',
+ width:600,
+ anchor:'-100 50%'
+ }]
+});
+ *
+ */
+Ext.layout.AnchorLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ /**
+ * @cfg {String} anchor
+ * This configuation option is to be applied to child items of a container managed by
+ * this layout (ie. configured with layout:'anchor').
+ *
+ * This value is what tells the layout how an item should be anchored to the container. items
+ * added to an AnchorLayout accept an anchoring-specific config property of anchor which is a string
+ * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
+ * The following types of anchor values are supported:
+ *
+ * - Percentage : Any value between 1 and 100, expressed as a percentage.
+ * The first anchor is the percentage width that the item should take up within the container, and the
+ * second is the percentage height. For example:
+// two values specified
+anchor: '100% 50%' // render item complete width of the container and
+ // 1/2 height of the container
+// one value specified
+anchor: '100%' // the width value; the height will default to auto
+ *
+ *
+ * - Offsets : Any positive or negative integer value.
+ * This is a raw adjustment where the first anchor is the offset from the right edge of the container,
+ * and the second is the offset from the bottom edge. For example:
+// two values specified
+anchor: '-50 -100' // render item the complete width of the container
+ // minus 50 pixels and
+ // the complete height minus 100 pixels.
+// one value specified
+anchor: '-50' // anchor value is assumed to be the right offset value
+ // bottom offset will default to 0
+ *
+ *
+ * - Sides : Valid values are 'right' (or 'r') and 'bottom'
+ * (or 'b').
+ * Either the container must have a fixed size or an anchorSize config value defined at render time in
+ * order for these to have any effect.
+ *
+ * - Mixed :
+ * Anchor values can also be mixed as needed. For example, to render the width offset from the container
+ * right edge by 50 pixels and 75% of the container's height use:
+ *
+anchor: '-50 75%'
+ *
+ *
+ *
+ *
+ */
// private
- getHMaxSize : function(){
- var cmax = this.maxSize || 10000;
- var center = this.layout.center;
- return Math.min(cmax, (this.el.getWidth()+center.el.getWidth())-center.getMinWidth());
- },
+ monitorResize : true,
- // private
- getVMaxSize : function(){
- var cmax = this.maxSize || 10000;
- var center = this.layout.center;
- return Math.min(cmax, (this.el.getHeight()+center.el.getHeight())-center.getMinHeight());
+ type : 'anchor',
+
+ /**
+ * @cfg {String} defaultAnchor
+ *
+ * default anchor for all child container items applied if no anchor or specific width is set on the child item. Defaults to '100%'.
+ *
+ */
+ defaultAnchor : '100%',
+
+ parseAnchorRE : /^(r|right|b|bottom)$/i,
+
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget(), ret = {};
+ if (target) {
+ ret = target.getViewSize();
+
+ // IE in strict mode will return a width of 0 on the 1st pass of getViewSize.
+ // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
+ // with getViewSize
+ if (Ext.isIE && Ext.isStrict && ret.width == 0){
+ ret = target.getStyleSize();
+ }
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
+ }
+ return ret;
+ },
+
+ // private
+ onLayout : function(container, target) {
+ Ext.layout.AnchorLayout.superclass.onLayout.call(this, container, target);
+
+ var size = this.getLayoutTargetSize(),
+ containerWidth = size.width,
+ containerHeight = size.height,
+ overflow = target.getStyle('overflow'),
+ components = this.getRenderedItems(container),
+ len = components.length,
+ boxes = [],
+ box,
+ anchorWidth,
+ anchorHeight,
+ component,
+ anchorSpec,
+ calcWidth,
+ calcHeight,
+ anchorsArray,
+ totalHeight = 0,
+ i,
+ el;
+
+ if(containerWidth < 20 && containerHeight < 20){
+ return;
+ }
+
+ // find the container anchoring size
+ if(container.anchorSize) {
+ if(typeof container.anchorSize == 'number') {
+ anchorWidth = container.anchorSize;
+ } else {
+ anchorWidth = container.anchorSize.width;
+ anchorHeight = container.anchorSize.height;
+ }
+ } else {
+ anchorWidth = container.initialConfig.width;
+ anchorHeight = container.initialConfig.height;
+ }
+
+ for(i = 0; i < len; i++) {
+ component = components[i];
+ el = component.getPositionEl();
+
+ // If a child container item has no anchor and no specific width, set the child to the default anchor size
+ if (!component.anchor && component.items && !Ext.isNumber(component.width) && !(Ext.isIE6 && Ext.isStrict)){
+ component.anchor = this.defaultAnchor;
+ }
+
+ if(component.anchor) {
+ anchorSpec = component.anchorSpec;
+ // cache all anchor values
+ if(!anchorSpec){
+ anchorsArray = component.anchor.split(' ');
+ component.anchorSpec = anchorSpec = {
+ right: this.parseAnchor(anchorsArray[0], component.initialConfig.width, anchorWidth),
+ bottom: this.parseAnchor(anchorsArray[1], component.initialConfig.height, anchorHeight)
+ };
+ }
+ calcWidth = anchorSpec.right ? this.adjustWidthAnchor(anchorSpec.right(containerWidth) - el.getMargins('lr'), component) : undefined;
+ calcHeight = anchorSpec.bottom ? this.adjustHeightAnchor(anchorSpec.bottom(containerHeight) - el.getMargins('tb'), component) : undefined;
+
+ if(calcWidth || calcHeight) {
+ boxes.push({
+ component: component,
+ width: calcWidth || undefined,
+ height: calcHeight || undefined
+ });
+ }
+ }
+ }
+ for (i = 0, len = boxes.length; i < len; i++) {
+ box = boxes[i];
+ box.component.setSize(box.width, box.height);
+ }
+
+ if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
+ var newTargetSize = this.getLayoutTargetSize();
+ if (newTargetSize.width != size.width || newTargetSize.height != size.height){
+ this.adjustmentPass = true;
+ this.onLayout(container, target);
+ }
+ }
+
+ delete this.adjustmentPass;
},
// private
- onSplitMove : function(split, newSize){
- var s = this.panel.getSize();
- this.lastSplitSize = newSize;
- if(this.position == 'north' || this.position == 'south'){
- this.panel.setSize(s.width, newSize);
- this.state.height = newSize;
- }else{
- this.panel.setSize(newSize, s.height);
- this.state.width = newSize;
+ parseAnchor : function(a, start, cstart) {
+ if (a && a != 'none') {
+ var last;
+ // standard anchor
+ if (this.parseAnchorRE.test(a)) {
+ var diff = cstart - start;
+ return function(v){
+ if(v !== last){
+ last = v;
+ return v - diff;
+ }
+ };
+ // percentage
+ } else if(a.indexOf('%') != -1) {
+ var ratio = parseFloat(a.replace('%', ''))*.01;
+ return function(v){
+ if(v !== last){
+ last = v;
+ return Math.floor(v*ratio);
+ }
+ };
+ // simple offset adjustment
+ } else {
+ a = parseInt(a, 10);
+ if (!isNaN(a)) {
+ return function(v) {
+ if (v !== last) {
+ last = v;
+ return v + a;
+ }
+ };
+ }
+ }
}
- this.layout.layout();
- this.panel.saveState();
return false;
},
- /**
- * Returns a reference to the split bar in use by this region.
- * @return {Ext.SplitBar} The split bar
- */
- getSplitBar : function(){
- return this.split;
+ // private
+ adjustWidthAnchor : function(value, comp){
+ return value;
},
- // inherit docs
- destroy : function() {
- Ext.destroy(
- this.miniSplitEl,
- this.split,
- this.splitEl
- );
+ // private
+ adjustHeightAnchor : function(value, comp){
+ return value;
}
-});
-Ext.Container.LAYOUTS['border'] = Ext.layout.BorderLayout;/**
- * @class Ext.layout.FormLayout
- * @extends Ext.layout.AnchorLayout
- * This layout manager is specifically designed for rendering and managing child Components of
- * {@link Ext.form.FormPanel forms}. It is responsible for rendering the labels of
- * {@link Ext.form.Field Field}s.
- *
- * This layout manager is used when a Container is configured with the layout:'form'
- * {@link Ext.Container#layout layout} config option, and should generally not need to be created directly
- * via the new keyword. See {@link Ext.Container#layout} for additional details.
- *
- * In an application, it will usually be preferrable to use a {@link Ext.form.FormPanel FormPanel}
- * (which is configured with FormLayout as its layout class by default) since it also provides built-in
- * functionality for {@link Ext.form.BasicForm#doAction loading, validating and submitting} the form.
- *
- * A {@link Ext.Container Container} using the FormLayout layout manager (e.g.
- * {@link Ext.form.FormPanel} or specifying layout:'form') can also accept the following
- * layout-specific config properties:
- * - {@link Ext.form.FormPanel#hideLabels hideLabels}
- * - {@link Ext.form.FormPanel#labelAlign labelAlign}
- * - {@link Ext.form.FormPanel#labelPad labelPad}
- * - {@link Ext.form.FormPanel#labelSeparator labelSeparator}
- * - {@link Ext.form.FormPanel#labelWidth labelWidth}
- *
- *
- * Any Component (including Fields) managed by FormLayout accepts the following as a config option:
- *
- * - {@link Ext.Component#anchor anchor}
- *
- *
- * Any Component managed by FormLayout may be rendered as a form field (with an associated label) by
- * configuring it with a non-null {@link Ext.Component#fieldLabel fieldLabel}. Components configured
- * in this way may be configured with the following options which affect the way the FormLayout renders them:
- *
- * - {@link Ext.Component#clearCls clearCls}
- * - {@link Ext.Component#fieldLabel fieldLabel}
- * - {@link Ext.Component#hideLabel hideLabel}
- * - {@link Ext.Component#itemCls itemCls}
- * - {@link Ext.Component#labelSeparator labelSeparator}
- * - {@link Ext.Component#labelStyle labelStyle}
- *
- *
- * Example usage:
+ /**
+ * @property activeItem
+ * @hide
+ */
+});
+Ext.Container.LAYOUTS['anchor'] = Ext.layout.AnchorLayout;
+/**
+ * @class Ext.layout.ColumnLayout
+ * @extends Ext.layout.ContainerLayout
+ * This is the layout style of choice for creating structural layouts in a multi-column format where the width of
+ * each column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content.
+ * This class is intended to be extended or created via the layout:'column' {@link Ext.Container#layout} config,
+ * and should generally not need to be created directly via the new keyword.
+ * ColumnLayout does not have any direct config options (other than inherited ones), but it does support a
+ * specific config property of columnWidth that can be included in the config of any panel added to it. The
+ * layout will use the columnWidth (if present) or width of each panel during layout to determine how to size each panel.
+ * If width or columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).
+ * The width property is always evaluated as pixels, and must be a number greater than or equal to 1.
+ * The columnWidth property is always evaluated as a percentage, and must be a decimal value greater than 0 and
+ * less than 1 (e.g., .25).
+ * The basic rules for specifying column widths are pretty simple. The logic makes two passes through the
+ * set of contained panels. During the first layout pass, all panels that either have a fixed width or none
+ * specified (auto) are skipped, but their widths are subtracted from the overall container width. During the second
+ * pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages based on
+ * the total remaining container width. In other words, percentage width panels are designed to fill the space
+ * left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any number of columns
+ * with different percentages, the columnWidths must always add up to 1 (or 100%) when added together, otherwise your
+ * layout may not render as expected. Example usage:
*
-// Required if showing validation messages
-Ext.QuickTips.init();
+// All columns are percentages -- they must add up to 1
+var p = new Ext.Panel({
+ title: 'Column Layout - Percentage Only',
+ layout:'column',
+ items: [{
+ title: 'Column 1',
+ columnWidth: .25
+ },{
+ title: 'Column 2',
+ columnWidth: .6
+ },{
+ title: 'Column 3',
+ columnWidth: .15
+ }]
+});
-// While you can create a basic Panel with layout:'form', practically
-// you should usually use a FormPanel to also get its form functionality
-// since it already creates a FormLayout internally.
-var form = new Ext.form.FormPanel({
- title: 'Form Layout',
- bodyStyle: 'padding:15px',
- width: 350,
- defaultType: 'textfield',
- defaults: {
- // applied to each contained item
- width: 230,
- msgTarget: 'side'
- },
+// Mix of width and columnWidth -- all columnWidth values must add up
+// to 1. The first column will take up exactly 120px, and the last two
+// columns will fill the remaining container width.
+var p = new Ext.Panel({
+ title: 'Column Layout - Mixed',
+ layout:'column',
items: [{
- fieldLabel: 'First Name',
- name: 'first',
- allowBlank: false,
- {@link Ext.Component#labelSeparator labelSeparator}: ':' // override labelSeparator layout config
- },{
- fieldLabel: 'Last Name',
- name: 'last'
- },{
- fieldLabel: 'Email',
- name: 'email',
- vtype:'email'
- }, {
- xtype: 'textarea',
- hideLabel: true, // override hideLabels layout config
- name: 'msg',
- anchor: '100% -53'
- }
- ],
- buttons: [
- {text: 'Save'},
- {text: 'Cancel'}
- ],
- layoutConfig: {
- {@link #labelSeparator}: '~' // superseded by assignment below
- },
- // config options applicable to container when layout='form':
- hideLabels: false,
- labelAlign: 'left', // or 'right' or 'top'
- {@link Ext.form.FormPanel#labelSeparator labelSeparator}: '>>', // takes precedence over layoutConfig value
- labelWidth: 65, // defaults to 100
- labelPad: 8 // defaults to 5, must specify labelWidth to be honored
+ title: 'Column 1',
+ width: 120
+ },{
+ title: 'Column 2',
+ columnWidth: .8
+ },{
+ title: 'Column 3',
+ columnWidth: .2
+ }]
});
*/
-Ext.layout.FormLayout = Ext.extend(Ext.layout.AnchorLayout, {
+Ext.layout.ColumnLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ // private
+ monitorResize:true,
- /**
- * @cfg {String} labelSeparator
- * See {@link Ext.form.FormPanel}.{@link Ext.form.FormPanel#labelSeparator labelSeparator}. Configuration
- * of this property at the container level takes precedence.
- */
- labelSeparator : ':',
+ type: 'column',
- /**
- * Read only. The CSS style specification string added to field labels in this layout if not
- * otherwise {@link Ext.Component#labelStyle specified by each contained field}.
- * @type String
- * @property labelStyle
- */
-
- /**
- * @cfg {Boolean} trackLabels
- * True to show/hide the field label when the field is hidden. Defaults to false.
- */
- trackLabels: false,
-
+ extraCls: 'x-column',
- onRemove: function(c){
- Ext.layout.FormLayout.superclass.onRemove.call(this, c);
- if(this.trackLabels && !this.isHide(c)){
- c.un('show', this.onFieldShow, this);
- c.un('hide', this.onFieldHide, this);
- }
- // check for itemCt, since we may be removing a fieldset or something similar
- var el = c.getPositionEl(),
- ct = c.getItemCt && c.getItemCt();
- if(c.rendered && ct){
- el.insertAfter(ct);
- Ext.destroy(ct);
- Ext.destroyMembers(c, 'label', 'itemCt');
- if(c.customItemCt){
- Ext.destroyMembers(c, 'getItemCt', 'customItemCt');
+ scrollOffset : 0,
+
+ // private
+
+ targetCls: 'x-column-layout-ct',
+
+ isValidParent : function(c, target){
+ return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
+ },
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget(), ret;
+ if (target) {
+ ret = target.getViewSize();
+
+ // IE in strict mode will return a width of 0 on the 1st pass of getViewSize.
+ // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
+ // with getViewSize
+ if (Ext.isIE && Ext.isStrict && ret.width == 0){
+ ret = target.getStyleSize();
}
+
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
}
+ return ret;
},
-
- // private
- setContainer : function(ct){
- Ext.layout.FormLayout.superclass.setContainer.call(this, ct);
- if(ct.labelAlign){
- ct.addClass('x-form-label-'+ct.labelAlign);
+
+ renderAll : function(ct, target) {
+ if(!this.innerCt){
+ // the innerCt prevents wrapping and shuffling while
+ // the container is resizing
+ this.innerCt = target.createChild({cls:'x-column-inner'});
+ this.innerCt.createChild({cls:'x-clear'});
}
+ Ext.layout.ColumnLayout.superclass.renderAll.call(this, ct, this.innerCt);
+ },
- if(ct.hideLabels){
- Ext.apply(this, {
- labelStyle: 'display:none',
- elementStyle: 'padding-left:0;',
- labelAdjust: 0
- });
- }else{
- this.labelSeparator = ct.labelSeparator || this.labelSeparator;
- ct.labelWidth = ct.labelWidth || 100;
- if(Ext.isNumber(ct.labelWidth)){
- var pad = Ext.isNumber(ct.labelPad) ? ct.labelPad : 5;
- Ext.apply(this, {
- labelAdjust: ct.labelWidth + pad,
- labelStyle: 'width:' + ct.labelWidth + 'px;',
- elementStyle: 'padding-left:' + (ct.labelWidth + pad) + 'px'
- });
- }
- if(ct.labelAlign == 'top'){
- Ext.apply(this, {
- labelStyle: 'width:auto;',
- labelAdjust: 0,
- elementStyle: 'padding-left:0;'
- });
- }
- }
- },
-
- isHide: function(c){
- return c.hideLabel || this.container.hideLabels;
- },
-
- onFieldShow: function(c){
- c.getItemCt().removeClass('x-hide-' + c.hideMode);
- },
-
- onFieldHide: function(c){
- c.getItemCt().addClass('x-hide-' + c.hideMode);
- },
+ // private
+ onLayout : function(ct, target){
+ var cs = ct.items.items,
+ len = cs.length,
+ c,
+ i,
+ m,
+ margins = [];
- //private
- getLabelStyle: function(s){
- var ls = '', items = [this.labelStyle, s];
- for (var i = 0, len = items.length; i < len; ++i){
- if (items[i]){
- ls += items[i];
- if (ls.substr(-1, 1) != ';'){
- ls += ';'
- }
- }
+ this.renderAll(ct, target);
+
+ var size = this.getLayoutTargetSize();
+
+ if(size.width < 1 && size.height < 1){ // display none?
+ return;
}
- return ls;
- },
- /**
- * @cfg {Ext.Template} fieldTpl
- * A {@link Ext.Template#compile compile}d {@link Ext.Template} for rendering
- * the fully wrapped, labeled and styled form Field. Defaults to:
-new Ext.Template(
- '<div class="x-form-item {itemCls}" tabIndex="-1">',
- '<label for="{id}" style="{labelStyle}" class="x-form-item-label">{label}{labelSeparator}</label>',
- '<div class="x-form-element" id="x-form-el-{id}" style="{elementStyle}">',
- '</div><div class="{clearCls}"></div>',
- '</div>'
-);
-
- * This may be specified to produce a different DOM structure when rendering form Fields.
- * A description of the properties within the template follows:
- * - itemCls : StringThe CSS class applied to the outermost div wrapper
- * that contains this field label and field element (the default class is 'x-form-item' and itemCls
- * will be added to that). If supplied, itemCls at the field level will override the default itemCls
- * supplied at the container level.
- * - id : StringThe id of the Field
- * - {@link #labelStyle} : String
- * A CSS style specification string to add to the field label for this field (defaults to '' or the
- * {@link #labelStyle layout's value for labelStyle}).
- * - label : StringThe text to display as the label for this
- * field (defaults to '')
- * - {@link #labelSeparator} : StringThe separator to display after
- * the text of the label for this field (defaults to a colon ':' or the
- * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.
- * - elementStyle : StringThe styles text for the input element's wrapper.
- * - clearCls : StringThe CSS class to apply to the special clearing div
- * rendered directly after each form field wrapper (defaults to 'x-form-clear-left')
- *
- * Also see {@link #getTemplateArgs}
- */
+ var w = size.width - this.scrollOffset,
+ h = size.height,
+ pw = w;
- // private
- renderItem : function(c, position, target){
- if(c && (c.isFormField || c.fieldLabel) && c.inputType != 'hidden'){
- var args = this.getTemplateArgs(c);
- if(Ext.isNumber(position)){
- position = target.dom.childNodes[position] || null;
- }
- if(position){
- c.itemCt = this.fieldTpl.insertBefore(position, args, true);
- }else{
- c.itemCt = this.fieldTpl.append(target, args, true);
- }
- if(!c.rendered){
- c.render('x-form-el-' + c.id);
- }else if(!this.isValidParent(c, target)){
- Ext.fly('x-form-el-' + c.id).appendChild(c.getPositionEl());
- }
- if(!c.getItemCt){
- // Non form fields don't have getItemCt, apply it here
- // This will get cleaned up in onRemove
- Ext.apply(c, {
- getItemCt: function(){
- return c.itemCt;
- },
- customItemCt: true
- });
- }
- c.label = c.getItemCt().child('label.x-form-item-label');
- if(this.trackLabels && !this.isHide(c)){
- if(c.hidden){
- this.onFieldHide(c);
- }
- c.on({
- scope: this,
- show: this.onFieldShow,
- hide: this.onFieldHide
- });
+ this.innerCt.setWidth(w);
+
+ // some columns can be percentages while others are fixed
+ // so we need to make 2 passes
+
+ for(i = 0; i < len; i++){
+ c = cs[i];
+ m = c.getPositionEl().getMargins('lr');
+ margins[i] = m;
+ if(!c.columnWidth){
+ pw -= (c.getWidth() + m);
}
- this.configureItem(c);
- }else {
- Ext.layout.FormLayout.superclass.renderItem.apply(this, arguments);
}
- },
- /**
- * Provides template arguments for rendering the fully wrapped, labeled and styled form Field.
- * This method returns an object hash containing properties used by the layout's {@link #fieldTpl}
- * to create a correctly wrapped, labeled and styled form Field. This may be overriden to
- * create custom layouts. The properties which must be returned are:
- * - itemCls : StringThe CSS class applied to the outermost div wrapper
- * that contains this field label and field element (the default class is 'x-form-item' and itemCls
- * will be added to that). If supplied, itemCls at the field level will override the default itemCls
- * supplied at the container level.
- * - id : StringThe id of the Field
- * - {@link #labelStyle} : String
- * A CSS style specification string to add to the field label for this field (defaults to '' or the
- * {@link #labelStyle layout's value for labelStyle}).
- * - label : StringThe text to display as the label for this
- * field (defaults to '')
- * - {@link #labelSeparator} : StringThe separator to display after
- * the text of the label for this field (defaults to a colon ':' or the
- * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.
- * - elementStyle : StringThe styles text for the input element's wrapper.
- * - clearCls : StringThe CSS class to apply to the special clearing div
- * rendered directly after each form field wrapper (defaults to 'x-form-clear-left')
- *
- * @param field The {@link Field Ext.form.Field} being rendered.
- * @return An object hash containing the properties required to render the Field.
- */
- 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'
- };
- },
+ pw = pw < 0 ? 0 : pw;
- // private
- adjustWidthAnchor: function(value, c){
- if(c.label && !this.isHide(c) && (this.container.labelAlign != 'top')){
- var adjust = Ext.isIE6 || (Ext.isIE && !Ext.isStrict);
- return value - this.labelAdjust + (adjust ? -3 : 0);
- }
- return value;
- },
-
- adjustHeightAnchor : function(value, c){
- if(c.label && !this.isHide(c) && (this.container.labelAlign == 'top')){
- return value - c.label.getHeight();
+ for(i = 0; i < len; i++){
+ c = cs[i];
+ m = margins[i];
+ if(c.columnWidth){
+ c.setSize(Math.floor(c.columnWidth * pw) - m);
+ }
}
- return value;
- },
- // private
- isValidParent : function(c, target){
- return target && this.container.getEl().contains(c.getDomPositionEl());
+ // Browsers differ as to when they account for scrollbars. We need to re-measure to see if the scrollbar
+ // spaces were accounted for properly. If not, re-layout.
+ if (Ext.isIE) {
+ if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) {
+ var ts = this.getLayoutTargetSize();
+ if (ts.width != size.width){
+ this.adjustmentPass = true;
+ this.onLayout(ct, target);
+ }
+ }
+ }
+ delete this.adjustmentPass;
}
/**
@@ -6634,4929 +5851,8904 @@ new Ext.Template(
*/
});
-Ext.Container.LAYOUTS['form'] = Ext.layout.FormLayout;/**
- * @class Ext.layout.AccordionLayout
- * @extends Ext.layout.FitLayout
- * This is a layout that manages multiple Panels in an expandable accordion style such that only
- * one Panel can be expanded at any given time. Each Panel has built-in support for expanding and collapsing.
- * Note: Only Ext.Panels and all subclasses of Ext.Panel may be used in an accordion layout Container.
- * This class is intended to be extended or created via the {@link Ext.Container#layout layout}
- * configuration property. See {@link Ext.Container#layout} for additional details.
- * Example usage:
- *
-var accordion = new Ext.Panel({
- title: 'Accordion Layout',
- layout:'accordion',
- defaults: {
- // applied to each contained panel
- bodyStyle: 'padding:15px'
- },
- layoutConfig: {
- // layout-specific configs go here
- titleCollapse: false,
- animate: true,
- activeOnTop: true
- },
- items: [{
- title: 'Panel 1',
- html: '<p>Panel content!</p>'
- },{
- title: 'Panel 2',
- html: '<p>Panel content!</p>'
- },{
- title: 'Panel 3',
- html: '<p>Panel content!</p>'
- }]
-});
-
- */
-Ext.layout.AccordionLayout = Ext.extend(Ext.layout.FitLayout, {
- /**
- * @cfg {Boolean} fill
- * True to adjust the active item's height to fill the available space in the container, false to use the
- * item's current height, or auto height if not explicitly set (defaults to true).
- */
- fill : true,
- /**
- * @cfg {Boolean} autoWidth
- * True to set each contained item's width to 'auto', false to use the item's current width (defaults to true).
- * Note that some components, in particular the {@link Ext.grid.GridPanel grid}, will not function properly within
- * layouts if they have auto width, so in such cases this config should be set to false.
- */
- autoWidth : true,
- /**
- * @cfg {Boolean} titleCollapse
- * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
- * expand/collapse only when the toggle tool button is clicked (defaults to true). When set to false,
- * {@link #hideCollapseTool} should be false also.
- */
- titleCollapse : true,
- /**
- * @cfg {Boolean} hideCollapseTool
- * True to hide the contained panels' collapse/expand toggle buttons, false to display them (defaults to false).
- * When set to true, {@link #titleCollapse} should be true also.
- */
- hideCollapseTool : false,
- /**
- * @cfg {Boolean} collapseFirst
- * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
- * in the contained panels' title bars, false to render it last (defaults to false).
- */
- collapseFirst : false,
- /**
- * @cfg {Boolean} animate
- * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
- * close directly with no animation (defaults to false). Note: to defer to the specific config setting of each
- * contained panel for this property, set this to undefined at the layout level.
- */
- animate : false,
- /**
- * @cfg {Boolean} sequence
- * Experimental. If animate is set to true, this will result in each animation running in sequence.
- */
- sequence : false,
- /**
- * @cfg {Boolean} activeOnTop
- * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
- * false to keep the panels in the rendered order. This is NOT compatible with "animate:true" (defaults to false).
- */
- activeOnTop : false,
-
- renderItem : function(c){
- if(this.animate === false){
- c.animCollapse = false;
- }
- c.collapsible = true;
- if(this.autoWidth){
- c.autoWidth = true;
- }
- if(this.titleCollapse){
- c.titleCollapse = true;
- }
- if(this.hideCollapseTool){
- c.hideCollapseTool = true;
- }
- if(this.collapseFirst !== undefined){
- c.collapseFirst = this.collapseFirst;
- }
- if(!this.activeItem && !c.collapsed){
- this.activeItem = c;
- }else if(this.activeItem && this.activeItem != c){
- c.collapsed = true;
- }
- Ext.layout.AccordionLayout.superclass.renderItem.apply(this, arguments);
- c.header.addClass('x-accordion-hd');
- c.on('beforeexpand', this.beforeExpand, this);
- },
-
- 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.activeItem = p;
- if(this.activeOnTop){
- p.el.dom.parentNode.insertBefore(p.el.dom, p.el.dom.parentNode.firstChild);
- }
- this.layout();
- },
-
- // private
- setItemSize : function(item, size){
- if(this.fill && item){
- var hh = 0;
- this.container.items.each(function(p){
- if(p != item){
- hh += p.header.getHeight();
- }
- });
- size.height -= hh;
- item.setSize(size);
- }
- },
-
- /**
- * Sets the active (expanded) item in the layout.
- * @param {String/Number} item The string component id or numeric index of the item to activate
- */
- setActiveItem : function(item){
- item = this.container.getComponent(item);
- if(this.activeItem != item){
- if(item.rendered && item.collapsed){
- item.expand();
- }else{
- this.activeItem = item;
- }
- }
-
- }
-});
-Ext.Container.LAYOUTS.accordion = Ext.layout.AccordionLayout;
-
-//backwards compat
-Ext.layout.Accordion = Ext.layout.AccordionLayout;/**
- * @class Ext.layout.TableLayout
- * @extends Ext.layout.ContainerLayout
- * This layout allows you to easily render content into an HTML table. The total number of columns can be
- * specified, and rowspan and colspan can be used to create complex layouts within the table.
- * This class is intended to be extended or created via the layout:'table' {@link Ext.Container#layout} config,
- * and should generally not need to be created directly via the new keyword.
- * Note that when creating a layout via config, the layout-specific config properties must be passed in via
- * the {@link Ext.Container#layoutConfig} object which will then be applied internally to the layout. In the
- * case of TableLayout, the only valid layout config property is {@link #columns}. However, the items added to a
- * TableLayout can supply the following table-specific config properties:
- *
- * - rowspan Applied to the table cell containing the item.
- * - colspan Applied to the table cell containing the item.
- * - cellId An id applied to the table cell containing the item.
- * - cellCls A CSS class name added to the table cell containing the item.
- *
- * The basic concept of building up a TableLayout is conceptually very similar to building up a standard
- * HTML table. You simply add each panel (or "cell") that you want to include along with any span attributes
- * specified as the special config properties of rowspan and colspan which work exactly like their HTML counterparts.
- * Rather than explicitly creating and nesting rows and columns as you would in HTML, you simply specify the
- * total column count in the layoutConfig and start adding panels in their natural order from left to right,
- * top to bottom. The layout will automatically figure out, based on the column count, rowspans and colspans,
- * how to position each panel within the table. Just like with HTML tables, your rowspans and colspans must add
- * up correctly in your overall layout or you'll end up with missing and/or extra cells! Example usage:
- *
-// This code will generate a layout table that is 3 columns by 2 rows
-// with some spanning included. The basic layout will be:
-// +--------+-----------------+
-// | A | B |
-// | |--------+--------|
-// | | C | D |
-// +--------+--------+--------+
-var table = new Ext.Panel({
- title: 'Table Layout',
- layout:'table',
- defaults: {
- // applied to each contained panel
- bodyStyle:'padding:20px'
- },
- layoutConfig: {
- // The total column count must be specified here
- columns: 3
- },
- items: [{
- html: '<p>Cell A content</p>',
- rowspan: 2
- },{
- html: '<p>Cell B content</p>',
- colspan: 2
- },{
- html: '<p>Cell C content</p>',
- cellCls: 'highlight'
- },{
- html: '<p>Cell D content</p>'
- }]
-});
-
- */
-Ext.layout.TableLayout = Ext.extend(Ext.layout.ContainerLayout, {
- /**
- * @cfg {Number} columns
- * The total number of columns to create in the table for this layout. If not specified, all Components added to
- * this layout will be rendered into a single row using one column per Component.
- */
-
- // private
- monitorResize:false,
-
- /**
- * @cfg {Object} tableAttrs
- * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification
- * used to create the layout's <table> element. Example:
-{
- xtype: 'panel',
- layout: 'table',
- layoutConfig: {
- tableAttrs: {
- style: {
- width: '100%'
- }
- },
- columns: 3
- }
-}
- */
- tableAttrs:null,
-
- // private
- setContainer : function(ct){
- Ext.layout.TableLayout.superclass.setContainer.call(this, ct);
-
- this.currentRow = 0;
- this.currentColumn = 0;
- this.cells = [];
- },
-
- // private
- onLayout : function(ct, target){
- var cs = ct.items.items, len = cs.length, c, i;
-
- if(!this.table){
- target.addClass('x-table-layout-ct');
-
- this.table = target.createChild(
- Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true);
- }
- this.renderAll(ct, target);
- },
-
- // private
- getRow : function(index){
- var row = this.table.tBodies[0].childNodes[index];
- if(!row){
- row = document.createElement('tr');
- this.table.tBodies[0].appendChild(row);
- }
- return row;
- },
-
- // private
- getNextCell : function(c){
- var cell = this.getNextNonSpan(this.currentColumn, this.currentRow);
- var curCol = this.currentColumn = cell[0], curRow = this.currentRow = cell[1];
- for(var rowIndex = curRow; rowIndex < curRow + (c.rowspan || 1); rowIndex++){
- if(!this.cells[rowIndex]){
- this.cells[rowIndex] = [];
- }
- for(var colIndex = curCol; colIndex < curCol + (c.colspan || 1); colIndex++){
- this.cells[rowIndex][colIndex] = true;
- }
- }
- var td = document.createElement('td');
- if(c.cellId){
- td.id = c.cellId;
- }
- var cls = 'x-table-layout-cell';
- if(c.cellCls){
- cls += ' ' + c.cellCls;
- }
- td.className = cls;
- if(c.colspan){
- td.colSpan = c.colspan;
- }
- if(c.rowspan){
- td.rowSpan = c.rowspan;
- }
- this.getRow(curRow).appendChild(td);
- return td;
- },
-
- // private
- getNextNonSpan: function(colIndex, rowIndex){
- var cols = this.columns;
- while((cols && colIndex >= cols) || (this.cells[rowIndex] && this.cells[rowIndex][colIndex])) {
- if(cols && colIndex >= cols){
- rowIndex++;
- colIndex = 0;
- }else{
- colIndex++;
- }
- }
- return [colIndex, rowIndex];
- },
-
- // private
- renderItem : function(c, position, target){
- if(c && !c.rendered){
- c.render(this.getNextCell(c));
- this.configureItem(c, position);
- }else if(c && !this.isValidParent(c, target)){
- var container = this.getNextCell(c);
- container.insertBefore(c.getDomPositionEl().dom, null);
- c.container = Ext.get(container);
- this.configureItem(c, position);
- }
- },
-
- // private
- isValidParent : function(c, target){
- return c.getDomPositionEl().up('table', 5).dom.parentNode === (target.dom || target);
- }
-
- /**
- * @property activeItem
- * @hide
- */
-});
-
-Ext.Container.LAYOUTS['table'] = Ext.layout.TableLayout;/**
- * @class Ext.layout.AbsoluteLayout
- * @extends Ext.layout.AnchorLayout
- * This is a layout that inherits the anchoring of {@link Ext.layout.AnchorLayout} and adds the
- * ability for x/y positioning using the standard x and y component config options.
- * This class is intended to be extended or created via the {@link Ext.Container#layout layout}
- * configuration property. See {@link Ext.Container#layout} for additional details.
- * Example usage:
- *
-var form = new Ext.form.FormPanel({
- title: 'Absolute Layout',
- layout:'absolute',
- layoutConfig: {
- // layout-specific configs go here
- extraCls: 'x-abs-layout-item',
- },
- baseCls: 'x-plain',
- url:'save-form.php',
- defaultType: 'textfield',
- items: [{
- x: 0,
- y: 5,
- xtype:'label',
- text: 'Send To:'
- },{
- x: 60,
- y: 0,
- name: 'to',
- anchor:'100%' // anchor width by percentage
- },{
- x: 0,
- y: 35,
- xtype:'label',
- text: 'Subject:'
- },{
- x: 60,
- y: 30,
- name: 'subject',
- anchor: '100%' // anchor width by percentage
- },{
- x:0,
- y: 60,
- xtype: 'textarea',
- name: 'msg',
- anchor: '100% 100%' // anchor width and height
- }]
-});
-
- */
-Ext.layout.AbsoluteLayout = Ext.extend(Ext.layout.AnchorLayout, {
-
- extraCls: 'x-abs-layout-item',
-
- onLayout : function(ct, target){
- target.position();
- this.paddingLeft = target.getPadding('l');
- this.paddingTop = target.getPadding('t');
-
- Ext.layout.AbsoluteLayout.superclass.onLayout.call(this, ct, target);
- },
-
- // private
- adjustWidthAnchor : function(value, comp){
- return value ? value - comp.getPosition(true)[0] + this.paddingLeft : value;
- },
-
- // private
- adjustHeightAnchor : function(value, comp){
- return value ? value - comp.getPosition(true)[1] + this.paddingTop : value;
- }
- /**
- * @property activeItem
- * @hide
- */
-});
-Ext.Container.LAYOUTS['absolute'] = Ext.layout.AbsoluteLayout;
-/**
- * @class Ext.layout.BoxLayout
- * @extends Ext.layout.ContainerLayout
- * Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.
- */
-Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, {
- /**
- * @cfg {Object} defaultMargins
- * If the individual contained items do not have a margins
- * property specified, the default margins from this property will be
- * applied to each item.
- *
This property may be specified as an object containing margins
- * to apply in the format:
-{
- top: (top margin),
- right: (right margin),
- bottom: (bottom margin),
- left: (left margin)
-}
- * This property may also be specified as a string containing
- * space-separated, numeric margin values. The order of the sides associated
- * with each value matches the way CSS processes margin values:
- *
- * - If there is only one value, it applies to all sides.
- * - If there are two values, the top and bottom borders are set to the
- * first value and the right and left are set to the second.
- * - If there are three values, the top is set to the first value, the left
- * and right are set to the second, and the bottom is set to the third.
- * - If there are four values, they apply to the top, right, bottom, and
- * left, respectively.
- *
- * Defaults to:
- * {top:0, right:0, bottom:0, left:0}
- *
- */
- defaultMargins : {left:0,top:0,right:0,bottom:0},
- /**
- * @cfg {String} padding
- * Sets the padding to be applied to all child items managed by this layout.
- * This property must be specified as a string containing
- * space-separated, numeric padding values. The order of the sides associated
- * with each value matches the way CSS processes padding values:
- *
- * - If there is only one value, it applies to all sides.
- * - If there are two values, the top and bottom borders are set to the
- * first value and the right and left are set to the second.
- * - If there are three values, the top is set to the first value, the left
- * and right are set to the second, and the bottom is set to the third.
- * - If there are four values, they apply to the top, right, bottom, and
- * left, respectively.
- *
- * Defaults to: "0"
- */
- padding : '0',
- // documented in subclasses
- pack : 'start',
-
- // private
- monitorResize : true,
- scrollOffset : 0,
- extraCls : 'x-box-item',
- ctCls : 'x-box-layout-ct',
- innerCls : 'x-box-inner',
-
- constructor : function(config){
- Ext.layout.BoxLayout.superclass.constructor.call(this, config);
- if(Ext.isString(this.defaultMargins)){
- this.defaultMargins = this.parseMargins(this.defaultMargins);
- }
- },
-
- // private
- isValidParent : function(c, target){
- return c.getEl().dom.parentNode == this.innerCt.dom;
- },
-
- // private
- onLayout : function(ct, target){
- var cs = ct.items.items, len = cs.length, c, i, last = len-1, cm;
-
- if(!this.innerCt){
- target.addClass(this.ctCls);
-
- // the innerCt prevents wrapping and shuffling while
- // the container is resizing
- this.innerCt = target.createChild({cls:this.innerCls});
- this.padding = this.parseMargins(this.padding);
- }
- this.renderAll(ct, this.innerCt);
- },
-
- // private
- renderItem : function(c){
- if(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);
- },
-
- getTargetSize : function(target){
- return (Ext.isIE6 && Ext.isStrict && target.dom == document.body) ? target.getStyleSize() : target.getViewSize();
- },
-
- getItems: function(ct){
- var items = [];
- ct.items.each(function(c){
- if(c.isVisible()){
- items.push(c);
- }
- });
- return items;
- }
-
- /**
- * @property activeItem
- * @hide
- */
-});
-
-/**
- * @class Ext.layout.VBoxLayout
- * @extends Ext.layout.BoxLayout
- * A layout that arranges items vertically down a Container. This layout optionally divides available vertical
- * space between child items containing a numeric flex
configuration.
- * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
- */
-Ext.layout.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
- /**
- * @cfg {String} align
- * Controls how the child items of the container are aligned. Acceptable configuration values for this
- * property are:
- *
- * - left : Defaultchild items are aligned horizontally
- * at the left side of the container
- * - center : child items are aligned horizontally at the
- * mid-width of the container
- * - stretch : child items are stretched horizontally to fill
- * the width of the container
- * - stretchmax : child items are stretched horizontally to
- * the size of the largest item.
- *
- */
- align : 'left', // left, center, stretch, strechmax
- /**
- * @cfg {String} pack
- * Controls how the child items of the container are packed together. Acceptable configuration values
- * for this property are:
- *
- * - start : Defaultchild items are packed together at
- * top side of container
- * - center : child items are packed together at
- * mid-height of container
- * - end : child items are packed together at bottom
- * side of container
- *
- */
- /**
- * @cfg {Number} flex
- * This configuation option is to be applied to child items of the container managed
- * by this layout. Each child item with a flex property will be flexed vertically
- * according to each item's relative flex value compared to the sum of all items with
- * a flex value specified. Any child items that have either a flex = 0 or
- * flex = undefined will not be 'flexed' (the initial size will not be changed).
- */
-
- // private
- onLayout : function(ct, target){
- Ext.layout.VBoxLayout.superclass.onLayout.call(this, ct, target);
-
-
- var cs = this.getItems(ct), cm, ch, margin,
- size = this.getTargetSize(target),
- w = size.width - target.getPadding('lr'),
- h = size.height - target.getPadding('tb') - this.scrollOffset,
- l = this.padding.left, t = this.padding.top,
- isStart = this.pack == 'start',
- isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1,
- stretchWidth = w - (this.padding.left + this.padding.right),
- extraHeight = 0,
- maxWidth = 0,
- totalFlex = 0,
- flexHeight = 0,
- usedHeight = 0;
-
- Ext.each(cs, function(c){
- cm = c.margins;
- totalFlex += c.flex || 0;
- ch = c.getHeight();
- margin = cm.top + cm.bottom;
- extraHeight += ch + margin;
- flexHeight += margin + (c.flex ? 0 : ch);
- maxWidth = Math.max(maxWidth, c.getWidth() + cm.left + cm.right);
- });
- extraHeight = h - extraHeight - this.padding.top - this.padding.bottom;
-
- var innerCtWidth = maxWidth + this.padding.left + this.padding.right;
- switch(this.align){
- case 'stretch':
- this.innerCt.setSize(w, h);
- break;
- case 'stretchmax':
- case 'left':
- this.innerCt.setSize(innerCtWidth, h);
- break;
- case 'center':
- this.innerCt.setSize(w = Math.max(w, innerCtWidth), h);
- break;
- }
-
- var availHeight = Math.max(0, h - this.padding.top - this.padding.bottom - flexHeight),
- leftOver = availHeight,
- heights = [],
- restore = [],
- idx = 0,
- availableWidth = Math.max(0, w - this.padding.left - this.padding.right);
-
-
- Ext.each(cs, function(c){
- if(isStart && c.flex){
- ch = Math.floor(availHeight * (c.flex / totalFlex));
- leftOver -= ch;
- heights.push(ch);
- }
- });
-
- if(this.pack == 'center'){
- t += extraHeight ? extraHeight / 2 : 0;
- }else if(this.pack == 'end'){
- t += extraHeight;
- }
- Ext.each(cs, function(c){
- cm = c.margins;
- t += cm.top;
- c.setPosition(l + cm.left, t);
- if(isStart && c.flex){
- ch = Math.max(0, heights[idx++] + (leftOver-- > 0 ? 1 : 0));
- if(isRestore){
- restore.push(c.getWidth());
- }
- c.setSize(availableWidth, ch);
- }else{
- ch = c.getHeight();
- }
- t += ch + cm.bottom;
- });
-
- idx = 0;
- Ext.each(cs, function(c){
- cm = c.margins;
- if(this.align == 'stretch'){
- c.setWidth((stretchWidth - (cm.left + cm.right)).constrain(
- c.minWidth || 0, c.maxWidth || 1000000));
- }else if(this.align == 'stretchmax'){
- c.setWidth((maxWidth - (cm.left + cm.right)).constrain(
- c.minWidth || 0, c.maxWidth || 1000000));
- }else{
- if(this.align == 'center'){
- var diff = availableWidth - (c.getWidth() + cm.left + cm.right);
- if(diff > 0){
- c.setPosition(l + cm.left + (diff/2), c.y);
- }
- }
- if(isStart && c.flex){
- c.setWidth(restore[idx++]);
- }
- }
- }, this);
- }
- /**
- * @property activeItem
- * @hide
- */
-});
-
-Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout;
-
-/**
- * @class Ext.layout.HBoxLayout
- * @extends Ext.layout.BoxLayout
- * A layout that arranges items horizontally 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 : Defaultchild items are aligned vertically
- * at the left side of the container
- * - middle : child items are aligned vertically at the
- * mid-height of the container
- * - stretch : child items are stretched vertically to fill
- * the height of the container
- * - stretchmax : child items are stretched vertically to
- * the size of the largest item.
- */
- align : 'top', // top, middle, stretch, strechmax
- /**
- * @cfg {String} pack
- * Controls how the child items of the container are packed together. Acceptable configuration values
- * for this property are:
- *
- * - start : Defaultchild items are packed together at
- * left side of container
- * - center : child items are packed together at
- * mid-width of container
- * - end : child items are packed together at right
- * side of container
- *
- */
- /**
- * @cfg {Number} flex
- * This configuation option is to be applied to child items of the container managed
- * by this layout. Each child item with a flex property will be flexed horizontally
- * according to each item's relative flex value compared to the sum of all items with
- * a flex value specified. Any child items that have either a flex = 0 or
- * flex = undefined will not be 'flexed' (the initial size will not be changed).
- */
-
- // private
- onLayout : function(ct, target){
- Ext.layout.HBoxLayout.superclass.onLayout.call(this, ct, target);
-
- var cs = this.getItems(ct), cm, cw, margin,
- size = this.getTargetSize(target),
- w = size.width - target.getPadding('lr') - this.scrollOffset,
- h = size.height - target.getPadding('tb'),
- l = this.padding.left, t = this.padding.top,
- isStart = this.pack == 'start',
- isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1,
- stretchHeight = h - (this.padding.top + this.padding.bottom),
- extraWidth = 0,
- maxHeight = 0,
- totalFlex = 0,
- flexWidth = 0,
- usedWidth = 0;
-
- Ext.each(cs, function(c){
- cm = c.margins;
- totalFlex += c.flex || 0;
- cw = c.getWidth();
- margin = cm.left + cm.right;
- extraWidth += cw + margin;
- flexWidth += margin + (c.flex ? 0 : cw);
- maxHeight = Math.max(maxHeight, c.getHeight() + cm.top + cm.bottom);
- });
- extraWidth = w - extraWidth - this.padding.left - this.padding.right;
-
- var innerCtHeight = maxHeight + this.padding.top + this.padding.bottom;
- switch(this.align){
- case 'stretch':
- this.innerCt.setSize(w, h);
- break;
- case 'stretchmax':
- case 'top':
- this.innerCt.setSize(w, innerCtHeight);
- break;
- case 'middle':
- this.innerCt.setSize(w, h = Math.max(h, innerCtHeight));
- break;
- }
-
-
- var availWidth = Math.max(0, w - this.padding.left - this.padding.right - flexWidth),
- leftOver = availWidth,
- widths = [],
- restore = [],
- idx = 0,
- availableHeight = Math.max(0, h - this.padding.top - this.padding.bottom);
-
-
- Ext.each(cs, function(c){
- if(isStart && c.flex){
- cw = Math.floor(availWidth * (c.flex / totalFlex));
- leftOver -= cw;
- widths.push(cw);
- }
- });
-
- if(this.pack == 'center'){
- l += extraWidth ? extraWidth / 2 : 0;
- }else if(this.pack == 'end'){
- l += extraWidth;
- }
- Ext.each(cs, function(c){
- cm = c.margins;
- l += cm.left;
- c.setPosition(l, t + cm.top);
- if(isStart && c.flex){
- cw = Math.max(0, widths[idx++] + (leftOver-- > 0 ? 1 : 0));
- if(isRestore){
- restore.push(c.getHeight());
- }
- c.setSize(cw, availableHeight);
- }else{
- cw = c.getWidth();
- }
- l += cw + cm.right;
- });
-
- idx = 0;
- Ext.each(cs, function(c){
- var cm = c.margins;
- if(this.align == 'stretch'){
- c.setHeight((stretchHeight - (cm.top + cm.bottom)).constrain(
- c.minHeight || 0, c.maxHeight || 1000000));
- }else if(this.align == 'stretchmax'){
- c.setHeight((maxHeight - (cm.top + cm.bottom)).constrain(
- c.minHeight || 0, c.maxHeight || 1000000));
- }else{
- if(this.align == 'middle'){
- var diff = availableHeight - (c.getHeight() + cm.top + cm.bottom);
- if(diff > 0){
- c.setPosition(c.x, t + cm.top + (diff/2));
- }
- }
- if(isStart && c.flex){
- c.setHeight(restore[idx++]);
- }
- }
- }, this);
- }
-
- /**
- * @property activeItem
- * @hide
- */
-});
-
-Ext.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout;
-/**
- * @class Ext.Viewport
- * @extends Ext.Container
- * A specialized container representing the viewable application area (the browser viewport).
- * The Viewport renders itself to the document body, and automatically sizes itself to the size of
- * the browser viewport and manages window resizing. There may only be one Viewport created
- * in a page. Inner layouts are available by virtue of the fact that all {@link Ext.Panel Panel}s
- * added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add}
- * method of any of its child Panels may themselves have a layout.
- * The Viewport does not provide scrolling, so child Panels within the Viewport should provide
- * for scrolling if needed using the {@link #autoScroll} config.
- * An example showing a classic application border layout:
-new Ext.Viewport({
- layout: 'border',
- items: [{
- region: 'north',
- html: '<h1 class="x-panel-header">Page Title</h1>',
- autoHeight: true,
- border: false,
- margins: '0 0 5 0'
- }, {
- region: 'west',
- collapsible: true,
- title: 'Navigation',
- width: 200
- // the west region might typically utilize a {@link Ext.tree.TreePanel TreePanel} or a Panel with {@link Ext.layout.AccordionLayout Accordion layout}
- }, {
- region: 'south',
- title: 'Title for Panel',
- collapsible: true,
- html: 'Information goes here',
- split: true,
- height: 100,
- minHeight: 100
- }, {
- region: 'east',
- title: 'Title for the Grid Panel',
- collapsible: true,
- split: true,
- width: 200,
- xtype: 'grid',
- // remaining grid configuration not shown ...
- // notice that the GridPanel is added directly as the region
- // it is not "overnested" inside another Panel
- }, {
- region: 'center',
- xtype: 'tabpanel', // TabPanel itself has no title
- items: {
- title: 'Default Tab',
- html: 'The first tab\'s content. Others may be added dynamically'
- }
- }]
-});
-
- * @constructor
- * Create a new Viewport
- * @param {Object} config The config object
- * @xtype viewport
- */
-Ext.Viewport = Ext.extend(Ext.Container, {
- /*
- * Privatize config options which, if used, would interfere with the
- * correct operation of the Viewport as the sole manager of the
- * layout of the document body.
- */
- /**
- * @cfg {Mixed} applyTo @hide
- */
- /**
- * @cfg {Boolean} allowDomMove @hide
- */
- /**
- * @cfg {Boolean} hideParent @hide
- */
- /**
- * @cfg {Mixed} renderTo @hide
- */
- /**
- * @cfg {Boolean} hideParent @hide
- */
- /**
- * @cfg {Number} height @hide
- */
- /**
- * @cfg {Number} width @hide
- */
- /**
- * @cfg {Boolean} autoHeight @hide
- */
- /**
- * @cfg {Boolean} autoWidth @hide
- */
- /**
- * @cfg {Boolean} deferHeight @hide
- */
- /**
- * @cfg {Boolean} monitorResize @hide
- */
- initComponent : function() {
- Ext.Viewport.superclass.initComponent.call(this);
- document.getElementsByTagName('html')[0].className += ' x-viewport';
- this.el = Ext.getBody();
- this.el.setHeight = Ext.emptyFn;
- this.el.setWidth = Ext.emptyFn;
- this.el.setSize = Ext.emptyFn;
- this.el.dom.scroll = 'no';
- this.allowDomMove = false;
- this.autoWidth = true;
- this.autoHeight = true;
- Ext.EventManager.onWindowResize(this.fireResize, this);
- this.renderTo = this.el;
- },
-
- fireResize : function(w, h){
- this.fireEvent('resize', this, w, h, w, h);
- }
-});
-Ext.reg('viewport', Ext.Viewport);/**
- * @class Ext.Panel
- * @extends Ext.Container
- * Panel is a container that has specific functionality and structural components that make
- * it the perfect building block for application-oriented user interfaces.
- * Panels are, by virtue of their inheritance from {@link Ext.Container}, capable
- * of being configured with a {@link Ext.Container#layout layout}, and containing child Components.
- * When either specifying child {@link Ext.Component#items items} of a Panel, or dynamically {@link Ext.Container#add adding} Components
- * to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether
- * those child elements need to be sized using one of Ext's built-in {@link Ext.Container#layout layout}
schemes. By
- * default, Panels use the {@link Ext.layout.ContainerLayout ContainerLayout} scheme. This simply renders
- * child components, appending them one after the other inside the Container, and does not apply any sizing
- * at all.
- * A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate
- * {@link #header}, {@link #footer} and {@link #body} sections (see {@link #frame} for additional
- * information).
- * Panel also provides built-in {@link #collapsible expandable and collapsible behavior}, along with
- * a variety of {@link #tools prebuilt tool buttons} that can be wired up to provide other customized
- * behavior. Panels can be easily dropped into any {@link Ext.Container Container} or layout, and the
- * layout and rendering pipeline is {@link Ext.Container#add completely managed by the framework}.
- * @constructor
- * @param {Object} config The config object
- * @xtype panel
- */
-Ext.Panel = Ext.extend(Ext.Container, {
- /**
- * The Panel's header {@link Ext.Element Element}. Read-only.
- * This Element is used to house the {@link #title} and {@link #tools}
- *
Note: see the Note for {@link Ext.Component#el el}
also.
- * @type Ext.Element
- * @property header
- */
- /**
- * The Panel's body {@link Ext.Element Element} which may be used to contain HTML content.
- * The content may be specified in the {@link #html} config, or it may be loaded using the
- * {@link autoLoad} config, or through the Panel's {@link #getUpdater Updater}. Read-only.
- * If this is used to load visible HTML elements in either way, then
- * the Panel may not be used as a Layout for hosting nested Panels.
- * If this Panel is intended to be used as the host of a Layout (See {@link #layout}
- * then the body Element must not be loaded or changed - it is under the control
- * of the Panel's Layout.
- *
Note: see the Note for {@link Ext.Component#el el}
also.
- * @type Ext.Element
- * @property body
- */
- /**
- * The Panel's bwrap {@link Ext.Element Element} used to contain other Panel elements
- * (tbar, body, bbar, footer). See {@link #bodyCfg}. Read-only.
- * @type Ext.Element
- * @property bwrap
- */
- /**
- * True if this panel is collapsed. Read-only.
- * @type Boolean
- * @property collapsed
- */
- /**
- * @cfg {Object} bodyCfg
- * A {@link Ext.DomHelper DomHelper} element specification object may be specified for any
- * Panel Element.
- * By default, the Default element in the table below will be used for the html markup to
- * create a child element with the commensurate Default class name (baseCls
will be
- * replaced by {@link #baseCls}
):
- *
- * Panel Default Default Custom Additional Additional
- * Element element class element class style
- * ======== ========================== ========= ============== ===========
- * {@link #header} div {@link #baseCls}+'-header' {@link #headerCfg} headerCssClass headerStyle
- * {@link #bwrap} div {@link #baseCls}+'-bwrap' {@link #bwrapCfg} bwrapCssClass bwrapStyle
- * + tbar div {@link #baseCls}+'-tbar' {@link #tbarCfg} tbarCssClass tbarStyle
- * + {@link #body} div {@link #baseCls}+'-body' {@link #bodyCfg} {@link #bodyCssClass} {@link #bodyStyle}
- * + bbar div {@link #baseCls}+'-bbar' {@link #bbarCfg} bbarCssClass bbarStyle
- * + {@link #footer} div {@link #baseCls}+'-footer' {@link #footerCfg} footerCssClass footerStyle
- *
- * Configuring a Custom element may be used, for example, to force the {@link #body} Element
- * to use a different form of markup than is created by default. An example of this might be
- * to {@link Ext.Element#createChild create a child} Panel containing a custom content, such as
- * a header, or forcing centering of all Panel content by having the body be a <center>
- * element:
- *
-new Ext.Panel({
- title: 'Message Title',
- renderTo: Ext.getBody(),
- width: 200, height: 130,
- bodyCfg: {
- tag: 'center',
- cls: 'x-panel-body', // Default class not applied if Custom element specified
- html: 'Message'
+Ext.Container.LAYOUTS['column'] = Ext.layout.ColumnLayout;
+/**
+ * @class Ext.layout.BorderLayout
+ * @extends Ext.layout.ContainerLayout
+ * This is a multi-pane, application-oriented UI layout style that supports multiple
+ * nested panels, automatic {@link Ext.layout.BorderLayout.Region#split split} bars between
+ * {@link Ext.layout.BorderLayout.Region#BorderLayout.Region regions} and built-in
+ * {@link Ext.layout.BorderLayout.Region#collapsible expanding and collapsing} of regions.
+ * This class is intended to be extended or created via the layout:'border'
+ * {@link Ext.Container#layout} config, and should generally not need to be created directly
+ * via the new keyword.
+ * BorderLayout does not have any direct config options (other than inherited ones).
+ * All configuration options available for customizing the BorderLayout are at the
+ * {@link Ext.layout.BorderLayout.Region} and {@link Ext.layout.BorderLayout.SplitRegion}
+ * levels.
+ * Example usage:
+ *
+var myBorderPanel = new Ext.Panel({
+ {@link Ext.Component#renderTo renderTo}: document.body,
+ {@link Ext.BoxComponent#width width}: 700,
+ {@link Ext.BoxComponent#height height}: 500,
+ {@link Ext.Panel#title title}: 'Border Layout',
+ {@link Ext.Container#layout layout}: 'border',
+ {@link Ext.Container#items items}: [{
+ {@link Ext.Panel#title title}: 'South Region is resizable',
+ {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'south', // position for region
+ {@link Ext.BoxComponent#height height}: 100,
+ {@link Ext.layout.BorderLayout.Region#split split}: true, // enable resizing
+ {@link Ext.SplitBar#minSize minSize}: 75, // defaults to {@link Ext.layout.BorderLayout.Region#minHeight 50}
+ {@link Ext.SplitBar#maxSize maxSize}: 150,
+ {@link Ext.layout.BorderLayout.Region#margins margins}: '0 5 5 5'
+ },{
+ // xtype: 'panel' implied by default
+ {@link Ext.Panel#title title}: 'West Region is collapsible',
+ {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}:'west',
+ {@link Ext.layout.BorderLayout.Region#margins margins}: '5 0 0 5',
+ {@link Ext.BoxComponent#width width}: 200,
+ {@link Ext.layout.BorderLayout.Region#collapsible collapsible}: true, // make collapsible
+ {@link Ext.layout.BorderLayout.Region#cmargins cmargins}: '5 5 0 5', // adjust top margin when collapsed
+ {@link Ext.Component#id id}: 'west-region-container',
+ {@link Ext.Container#layout layout}: 'fit',
+ {@link Ext.Panel#unstyled unstyled}: true
+ },{
+ {@link Ext.Panel#title title}: 'Center Region',
+ {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'center', // center region is required, no width/height specified
+ {@link Ext.Component#xtype xtype}: 'container',
+ {@link Ext.Container#layout layout}: 'fit',
+ {@link Ext.layout.BorderLayout.Region#margins margins}: '5 5 0 0'
+ }]
+});
+
+ * Notes:
+ * - Any container using the BorderLayout must have a child item with region:'center'.
+ * The child item in the center region will always be resized to fill the remaining space not used by
+ * the other regions in the layout.
+ * - Any child items with a region of west or east must have width defined
+ * (an integer representing the number of pixels that the region should take up).
+ * - Any child items with a region of north or south must have height defined.
+ * - The regions of a BorderLayout are fixed at render time and thereafter, its child Components may not be removed or added. To add/remove
+ * Components within a BorderLayout, have them wrapped by an additional Container which is directly
+ * managed by the BorderLayout. If the region is to be collapsible, the Container used directly
+ * by the BorderLayout manager should be a Panel. In the following example a Container (an Ext.Panel)
+ * is added to the west region:
+ *
+wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
+wrc.{@link Ext.Panel#removeAll removeAll}();
+wrc.{@link Ext.Container#add add}({
+ title: 'Added Panel',
+ html: 'Some content'
+});
+wrc.{@link Ext.Container#doLayout doLayout}();
+ *
+ *
+ * - To reference a {@link Ext.layout.BorderLayout.Region Region}:
+ *
+wr = myBorderPanel.layout.west;
+ *
+ *
+ *
+ */
+Ext.layout.BorderLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ // private
+ monitorResize:true,
+ // private
+ rendered : false,
+
+ type: 'border',
+
+ targetCls: 'x-border-layout-ct',
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget();
+ return target ? target.getViewSize() : {};
},
- footerCfg: {
- tag: 'h2',
- cls: 'x-panel-footer' // same as the Default class
- html: 'footer html'
+
+ // private
+ onLayout : function(ct, target){
+ var collapsed, i, c, pos, items = ct.items.items, len = items.length;
+ if(!this.rendered){
+ collapsed = [];
+ for(i = 0; i < len; i++) {
+ c = items[i];
+ pos = c.region;
+ if(c.collapsed){
+ collapsed.push(c);
+ }
+ c.collapsed = false;
+ if(!c.rendered){
+ c.render(target, i);
+ c.getPositionEl().addClass('x-border-panel');
+ }
+ this[pos] = pos != 'center' && c.split ?
+ new Ext.layout.BorderLayout.SplitRegion(this, c.initialConfig, pos) :
+ new Ext.layout.BorderLayout.Region(this, c.initialConfig, pos);
+ this[pos].render(target, c);
+ }
+ this.rendered = true;
+ }
+
+ var size = this.getLayoutTargetSize();
+ if(size.width < 20 || size.height < 20){ // display none?
+ if(collapsed){
+ this.restoreCollapsed = collapsed;
+ }
+ return;
+ }else if(this.restoreCollapsed){
+ collapsed = this.restoreCollapsed;
+ delete this.restoreCollapsed;
+ }
+
+ var w = size.width, h = size.height,
+ centerW = w, centerH = h, centerY = 0, centerX = 0,
+ n = this.north, s = this.south, west = this.west, e = this.east, c = this.center,
+ b, m, totalWidth, totalHeight;
+ if(!c && Ext.layout.BorderLayout.WARN !== false){
+ throw 'No center region defined in BorderLayout ' + ct.id;
+ }
+
+ if(n && n.isVisible()){
+ b = n.getSize();
+ m = n.getMargins();
+ b.width = w - (m.left+m.right);
+ b.x = m.left;
+ b.y = m.top;
+ centerY = b.height + b.y + m.bottom;
+ centerH -= centerY;
+ n.applyLayout(b);
+ }
+ if(s && s.isVisible()){
+ b = s.getSize();
+ m = s.getMargins();
+ b.width = w - (m.left+m.right);
+ b.x = m.left;
+ totalHeight = (b.height + m.top + m.bottom);
+ b.y = h - totalHeight + m.top;
+ centerH -= totalHeight;
+ s.applyLayout(b);
+ }
+ if(west && west.isVisible()){
+ b = west.getSize();
+ m = west.getMargins();
+ b.height = centerH - (m.top+m.bottom);
+ b.x = m.left;
+ b.y = centerY + m.top;
+ totalWidth = (b.width + m.left + m.right);
+ centerX += totalWidth;
+ centerW -= totalWidth;
+ west.applyLayout(b);
+ }
+ if(e && e.isVisible()){
+ b = e.getSize();
+ m = e.getMargins();
+ b.height = centerH - (m.top+m.bottom);
+ totalWidth = (b.width + m.left + m.right);
+ b.x = w - totalWidth + m.left;
+ b.y = centerY + m.top;
+ centerW -= totalWidth;
+ e.applyLayout(b);
+ }
+ if(c){
+ m = c.getMargins();
+ var centerBox = {
+ x: centerX + m.left,
+ y: centerY + m.top,
+ width: centerW - (m.left+m.right),
+ height: centerH - (m.top+m.bottom)
+ };
+ c.applyLayout(centerBox);
+ }
+ if(collapsed){
+ for(i = 0, len = collapsed.length; i < len; i++){
+ collapsed[i].collapse(false);
+ }
+ }
+ if(Ext.isIE && Ext.isStrict){ // workaround IE strict repainting issue
+ target.repaint();
+ }
+ // Putting a border layout into an overflowed container is NOT correct and will make a second layout pass necessary.
+ if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) {
+ var ts = this.getLayoutTargetSize();
+ if (ts.width != size.width || ts.height != size.height){
+ this.adjustmentPass = true;
+ this.onLayout(ct, target);
+ }
+ }
+ delete this.adjustmentPass;
},
- footerCssClass: 'custom-footer', // additional css class, see {@link Ext.element#addClass addClass}
- footerStyle: 'background-color:red' // see {@link #bodyStyle}
+
+ destroy: function() {
+ var r = ['north', 'south', 'east', 'west'], i, region;
+ for (i = 0; i < r.length; i++) {
+ region = this[r[i]];
+ if(region){
+ if(region.destroy){
+ region.destroy();
+ }else if (region.split){
+ region.split.destroy(true);
+ }
+ }
+ }
+ Ext.layout.BorderLayout.superclass.destroy.call(this);
+ }
+
+ /**
+ * @property activeItem
+ * @hide
+ */
});
- *
- * The example above also explicitly creates a {@link #footer}
with custom markup and
- * styling applied.
+
+/**
+ * @class Ext.layout.BorderLayout.Region
+ * This is a region of a {@link Ext.layout.BorderLayout BorderLayout} that acts as a subcontainer
+ * within the layout. Each region has its own {@link Ext.layout.ContainerLayout layout} that is
+ * independent of other regions and the containing BorderLayout, and can be any of the
+ * {@link Ext.layout.ContainerLayout valid Ext layout types}.
+ * Region size is managed automatically and cannot be changed by the user -- for
+ * {@link #split resizable regions}, see {@link Ext.layout.BorderLayout.SplitRegion}.
+ * @constructor
+ * Create a new Region.
+ * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region.
+ * @param {Object} config The configuration options
+ * @param {String} position The region position. Valid values are: north, south,
+ * east, west and center. Every {@link Ext.layout.BorderLayout BorderLayout}
+ * must have a center region for the primary content -- all other regions are optional.
+ */
+Ext.layout.BorderLayout.Region = function(layout, config, pos){
+ Ext.apply(this, config);
+ this.layout = layout;
+ this.position = pos;
+ this.state = {};
+ if(typeof this.margins == 'string'){
+ this.margins = this.layout.parseMargins(this.margins);
+ }
+ this.margins = Ext.applyIf(this.margins || {}, this.defaultMargins);
+ if(this.collapsible){
+ if(typeof this.cmargins == 'string'){
+ this.cmargins = this.layout.parseMargins(this.cmargins);
+ }
+ if(this.collapseMode == 'mini' && !this.cmargins){
+ this.cmargins = {left:0,top:0,right:0,bottom:0};
+ }else{
+ this.cmargins = Ext.applyIf(this.cmargins || {},
+ pos == 'north' || pos == 'south' ? this.defaultNSCMargins : this.defaultEWCMargins);
+ }
+ }
+};
+
+Ext.layout.BorderLayout.Region.prototype = {
+ /**
+ * @cfg {Boolean} animFloat
+ * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
+ * panel that will close again once the user mouses out of that panel (or clicks out if
+ * {@link #autoHide} = false). Setting {@link #animFloat} = false will
+ * prevent the open and close of these floated panels from being animated (defaults to true).
+ */
+ /**
+ * @cfg {Boolean} autoHide
+ * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
+ * panel. If autoHide = true, the panel will automatically hide after the user mouses
+ * out of the panel. If autoHide = false, the panel will continue to display until the
+ * user clicks outside of the panel (defaults to true).
+ */
+ /**
+ * @cfg {String} collapseMode
+ * collapseMode supports two configuration values:
+ * - undefined (default)By default, {@link #collapsible}
+ * regions are collapsed by clicking the expand/collapse tool button that renders into the region's
+ * title bar.
+ * - 'mini'Optionally, when collapseMode is set to
+ * 'mini' the region's split bar will also display a small collapse button in the center of
+ * the bar. In 'mini' mode the region will collapse to a thinner bar than in normal mode.
+ *
+ *
+ * Note: if a collapsible region does not have a title bar, then set collapseMode =
+ * 'mini' and {@link #split} = true in order for the region to be {@link #collapsible}
+ * by the user as the expand/collapse tool button (that would go in the title bar) will not be rendered.
+ * See also {@link #cmargins}.
+ */
+ /**
+ * @cfg {Object} margins
+ * An object containing margins to apply to the region when in the expanded state in the
+ * format:
+{
+ top: (top margin),
+ right: (right margin),
+ bottom: (bottom margin),
+ left: (left margin)
+}
+ * May also be a string containing space-separated, numeric margin values. The order of the
+ * sides associated with each value matches the way CSS processes margin values:
+ *
+ * - If there is only one value, it applies to all sides.
+ * - If there are two values, the top and bottom borders are set to the first value and the
+ * right and left are set to the second.
+ * - If there are three values, the top is set to the first value, the left and right are set
+ * to the second, and the bottom is set to the third.
+ * - If there are four values, they apply to the top, right, bottom, and left, respectively.
+ *
+ * Defaults to:
+ * {top:0, right:0, bottom:0, left:0}
+ *
+ */
+ /**
+ * @cfg {Object} cmargins
+ * An object containing margins to apply to the region when in the collapsed state in the
+ * format:
+{
+ top: (top margin),
+ right: (right margin),
+ bottom: (bottom margin),
+ left: (left margin)
+}
+ * May also be a string containing space-separated, numeric margin values. The order of the
+ * sides associated with each value matches the way CSS processes margin values.
+ *
+ * - If there is only one value, it applies to all sides.
+ * - If there are two values, the top and bottom borders are set to the first value and the
+ * right and left are set to the second.
+ * - If there are three values, the top is set to the first value, the left and right are set
+ * to the second, and the bottom is set to the third.
+ * - If there are four values, they apply to the top, right, bottom, and left, respectively.
+ *
+ */
+ /**
+ * @cfg {Boolean} collapsible
+ * true to allow the user to collapse this region (defaults to false). If
+ * true, an expand/collapse tool button will automatically be rendered into the title
+ * bar of the region, otherwise the button will not be shown.
+ * Note: that a title bar is required to display the collapse/expand toggle button -- if
+ * no title is specified for the region's panel, the region will only be collapsible if
+ * {@link #collapseMode} = 'mini' and {@link #split} = true.
+ */
+ collapsible : false,
+ /**
+ * @cfg {Boolean} split
+ *
true to create a {@link Ext.layout.BorderLayout.SplitRegion SplitRegion} and
+ * display a 5px wide {@link Ext.SplitBar} between this region and its neighbor, allowing the user to
+ * resize the regions dynamically. Defaults to false creating a
+ * {@link Ext.layout.BorderLayout.Region Region}.
+ * Notes:
+ * - this configuration option is ignored if region='center'
+ * - when split == true, it is common to specify a
+ * {@link Ext.SplitBar#minSize minSize} and {@link Ext.SplitBar#maxSize maxSize}
+ * for the {@link Ext.BoxComponent BoxComponent} representing the region. These are not native
+ * configs of {@link Ext.BoxComponent BoxComponent}, and are used only by this class.
+ * - if {@link #collapseMode} = 'mini' requires split = true to reserve space
+ * for the collapse tool
+ *
+ */
+ split:false,
+ /**
+ * @cfg {Boolean} floatable
+ * true to allow clicking a collapsed region's bar to display the region's panel floated
+ * above the layout, false to force the user to fully expand a collapsed region by
+ * clicking the expand button to see it again (defaults to true).
+ */
+ floatable: true,
+ /**
+ * @cfg {Number} minWidth
+ * The minimum allowable width in pixels for this region (defaults to 50).
+ * maxWidth may also be specified.
+ * Note: setting the {@link Ext.SplitBar#minSize minSize} /
+ * {@link Ext.SplitBar#maxSize maxSize} supersedes any specified
+ * minWidth / maxWidth.
+ */
+ minWidth:50,
+ /**
+ * @cfg {Number} minHeight
+ * The minimum allowable height in pixels for this region (defaults to 50)
+ * maxHeight may also be specified.
+ * Note: setting the {@link Ext.SplitBar#minSize minSize} /
+ * {@link Ext.SplitBar#maxSize maxSize} supersedes any specified
+ * minHeight / maxHeight.
+ */
+ minHeight:50,
+
+ // private
+ defaultMargins : {left:0,top:0,right:0,bottom:0},
+ // private
+ defaultNSCMargins : {left:5,top:5,right:5,bottom:5},
+ // private
+ defaultEWCMargins : {left:5,top:0,right:5,bottom:0},
+ floatingZIndex: 100,
+
+ /**
+ * True if this region is collapsed. Read-only.
+ * @type Boolean
+ * @property
+ */
+ isCollapsed : false,
+
+ /**
+ * This region's panel. Read-only.
+ * @type Ext.Panel
+ * @property panel
+ */
+ /**
+ * This region's layout. Read-only.
+ * @type Layout
+ * @property layout
+ */
+ /**
+ * This region's layout position (north, south, east, west or center). Read-only.
+ * @type String
+ * @property position
+ */
+
+ // private
+ render : function(ct, p){
+ this.panel = p;
+ p.el.enableDisplayMode();
+ this.targetEl = ct;
+ this.el = p.el;
+
+ var gs = p.getState, ps = this.position;
+ p.getState = function(){
+ return Ext.apply(gs.call(p) || {}, this.state);
+ }.createDelegate(this);
+
+ if(ps != 'center'){
+ p.allowQueuedExpand = false;
+ p.on({
+ beforecollapse: this.beforeCollapse,
+ collapse: this.onCollapse,
+ beforeexpand: this.beforeExpand,
+ expand: this.onExpand,
+ hide: this.onHide,
+ show: this.onShow,
+ scope: this
+ });
+ if(this.collapsible || this.floatable){
+ p.collapseEl = 'el';
+ p.slideAnchor = this.getSlideAnchor();
+ }
+ if(p.tools && p.tools.toggle){
+ p.tools.toggle.addClass('x-tool-collapse-'+ps);
+ p.tools.toggle.addClassOnOver('x-tool-collapse-'+ps+'-over');
+ }
+ }
+ },
+
+ // private
+ getCollapsedEl : function(){
+ if(!this.collapsedEl){
+ if(!this.toolTemplate){
+ var tt = new Ext.Template(
+ ' '
+ );
+ tt.disableFormats = true;
+ tt.compile();
+ Ext.layout.BorderLayout.Region.prototype.toolTemplate = tt;
+ }
+ this.collapsedEl = this.targetEl.createChild({
+ cls: "x-layout-collapsed x-layout-collapsed-"+this.position,
+ id: this.panel.id + '-xcollapsed'
+ });
+ this.collapsedEl.enableDisplayMode('block');
+
+ if(this.collapseMode == 'mini'){
+ this.collapsedEl.addClass('x-layout-cmini-'+this.position);
+ this.miniCollapsedEl = this.collapsedEl.createChild({
+ cls: "x-layout-mini x-layout-mini-"+this.position, html: " "
+ });
+ this.miniCollapsedEl.addClassOnOver('x-layout-mini-over');
+ this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
+ this.collapsedEl.on('click', this.onExpandClick, this, {stopEvent:true});
+ }else {
+ if(this.collapsible !== false && !this.hideCollapseTool) {
+ var t = this.expandToolEl = this.toolTemplate.append(
+ this.collapsedEl.dom,
+ {id:'expand-'+this.position}, true);
+ t.addClassOnOver('x-tool-expand-'+this.position+'-over');
+ t.on('click', this.onExpandClick, this, {stopEvent:true});
+ }
+ if(this.floatable !== false || this.titleCollapse){
+ this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
+ this.collapsedEl.on("click", this[this.floatable ? 'collapseClick' : 'onExpandClick'], this);
+ }
+ }
+ }
+ return this.collapsedEl;
+ },
+
+ // private
+ onExpandClick : function(e){
+ if(this.isSlid){
+ this.panel.expand(false);
+ }else{
+ this.panel.expand();
+ }
+ },
+
+ // private
+ onCollapseClick : function(e){
+ this.panel.collapse();
+ },
+
+ // private
+ beforeCollapse : function(p, animate){
+ this.lastAnim = animate;
+ if(this.splitEl){
+ this.splitEl.hide();
+ }
+ this.getCollapsedEl().show();
+ var el = this.panel.getEl();
+ this.originalZIndex = el.getStyle('z-index');
+ el.setStyle('z-index', 100);
+ this.isCollapsed = true;
+ this.layout.layout();
+ },
+
+ // private
+ onCollapse : function(animate){
+ this.panel.el.setStyle('z-index', 1);
+ if(this.lastAnim === false || this.panel.animCollapse === false){
+ this.getCollapsedEl().dom.style.visibility = 'visible';
+ }else{
+ this.getCollapsedEl().slideIn(this.panel.slideAnchor, {duration:.2});
+ }
+ this.state.collapsed = true;
+ this.panel.saveState();
+ },
+
+ // private
+ beforeExpand : function(animate){
+ if(this.isSlid){
+ this.afterSlideIn();
+ }
+ var c = this.getCollapsedEl();
+ this.el.show();
+ if(this.position == 'east' || this.position == 'west'){
+ this.panel.setSize(undefined, c.getHeight());
+ }else{
+ this.panel.setSize(c.getWidth(), undefined);
+ }
+ c.hide();
+ c.dom.style.visibility = 'hidden';
+ this.panel.el.setStyle('z-index', this.floatingZIndex);
+ },
+
+ // private
+ onExpand : function(){
+ this.isCollapsed = false;
+ if(this.splitEl){
+ this.splitEl.show();
+ }
+ this.layout.layout();
+ this.panel.el.setStyle('z-index', this.originalZIndex);
+ this.state.collapsed = false;
+ this.panel.saveState();
+ },
+
+ // private
+ collapseClick : function(e){
+ if(this.isSlid){
+ e.stopPropagation();
+ this.slideIn();
+ }else{
+ e.stopPropagation();
+ this.slideOut();
+ }
+ },
+
+ // private
+ onHide : function(){
+ if(this.isCollapsed){
+ this.getCollapsedEl().hide();
+ }else if(this.splitEl){
+ this.splitEl.hide();
+ }
+ },
+
+ // private
+ onShow : function(){
+ if(this.isCollapsed){
+ this.getCollapsedEl().show();
+ }else if(this.splitEl){
+ this.splitEl.show();
+ }
+ },
+
+ /**
+ * True if this region is currently visible, else false.
+ * @return {Boolean}
+ */
+ isVisible : function(){
+ return !this.panel.hidden;
+ },
+
+ /**
+ * Returns the current margins for this region. If the region is collapsed, the
+ * {@link #cmargins} (collapsed margins) value will be returned, otherwise the
+ * {@link #margins} value will be returned.
+ * @return {Object} An object containing the element's margins: {left: (left
+ * margin), top: (top margin), right: (right margin), bottom: (bottom margin)}
+ */
+ getMargins : function(){
+ return this.isCollapsed && this.cmargins ? this.cmargins : this.margins;
+ },
+
+ /**
+ * Returns the current size of this region. If the region is collapsed, the size of the
+ * collapsedEl will be returned, otherwise the size of the region's panel will be returned.
+ * @return {Object} An object containing the element's size: {width: (element width),
+ * height: (element height)}
+ */
+ getSize : function(){
+ return this.isCollapsed ? this.getCollapsedEl().getSize() : this.panel.getSize();
+ },
+
+ /**
+ * Sets the specified panel as the container element for this region.
+ * @param {Ext.Panel} panel The new panel
+ */
+ setPanel : function(panel){
+ this.panel = panel;
+ },
+
+ /**
+ * Returns the minimum allowable width for this region.
+ * @return {Number} The minimum width
+ */
+ getMinWidth: function(){
+ return this.minWidth;
+ },
+
+ /**
+ * Returns the minimum allowable height for this region.
+ * @return {Number} The minimum height
+ */
+ getMinHeight: function(){
+ return this.minHeight;
+ },
+
+ // private
+ applyLayoutCollapsed : function(box){
+ var ce = this.getCollapsedEl();
+ ce.setLeftTop(box.x, box.y);
+ ce.setSize(box.width, box.height);
+ },
+
+ // private
+ applyLayout : function(box){
+ if(this.isCollapsed){
+ this.applyLayoutCollapsed(box);
+ }else{
+ this.panel.setPosition(box.x, box.y);
+ this.panel.setSize(box.width, box.height);
+ }
+ },
+
+ // private
+ beforeSlide: function(){
+ this.panel.beforeEffect();
+ },
+
+ // private
+ afterSlide : function(){
+ this.panel.afterEffect();
+ },
+
+ // private
+ initAutoHide : function(){
+ if(this.autoHide !== false){
+ if(!this.autoHideHd){
+ this.autoHideSlideTask = new Ext.util.DelayedTask(this.slideIn, this);
+ this.autoHideHd = {
+ "mouseout": function(e){
+ if(!e.within(this.el, true)){
+ this.autoHideSlideTask.delay(500);
+ }
+ },
+ "mouseover" : function(e){
+ this.autoHideSlideTask.cancel();
+ },
+ scope : this
+ };
+ }
+ this.el.on(this.autoHideHd);
+ this.collapsedEl.on(this.autoHideHd);
+ }
+ },
+
+ // private
+ clearAutoHide : function(){
+ if(this.autoHide !== false){
+ this.el.un("mouseout", this.autoHideHd.mouseout);
+ this.el.un("mouseover", this.autoHideHd.mouseover);
+ this.collapsedEl.un("mouseout", this.autoHideHd.mouseout);
+ this.collapsedEl.un("mouseover", this.autoHideHd.mouseover);
+ }
+ },
+
+ // private
+ clearMonitor : function(){
+ Ext.getDoc().un("click", this.slideInIf, this);
+ },
+
+ /**
+ * If this Region is {@link #floatable}, this method slides this Region into full visibility over the top
+ * of the center Region where it floats until either {@link #slideIn} is called, or other regions of the layout
+ * are clicked, or the mouse exits the Region.
+ */
+ slideOut : function(){
+ if(this.isSlid || this.el.hasActiveFx()){
+ return;
+ }
+ this.isSlid = true;
+ var ts = this.panel.tools, dh, pc;
+ if(ts && ts.toggle){
+ ts.toggle.hide();
+ }
+ this.el.show();
+
+ // Temporarily clear the collapsed flag so we can onResize the panel on the slide
+ pc = this.panel.collapsed;
+ this.panel.collapsed = false;
+
+ if(this.position == 'east' || this.position == 'west'){
+ // Temporarily clear the deferHeight flag so we can size the height on the slide
+ dh = this.panel.deferHeight;
+ this.panel.deferHeight = false;
+
+ this.panel.setSize(undefined, this.collapsedEl.getHeight());
+
+ // Put the deferHeight flag back after setSize
+ this.panel.deferHeight = dh;
+ }else{
+ this.panel.setSize(this.collapsedEl.getWidth(), undefined);
+ }
+
+ // Put the collapsed flag back after onResize
+ this.panel.collapsed = pc;
+
+ this.restoreLT = [this.el.dom.style.left, this.el.dom.style.top];
+ this.el.alignTo(this.collapsedEl, this.getCollapseAnchor());
+ this.el.setStyle("z-index", this.floatingZIndex+2);
+ this.panel.el.replaceClass('x-panel-collapsed', 'x-panel-floating');
+ if(this.animFloat !== false){
+ this.beforeSlide();
+ this.el.slideIn(this.getSlideAnchor(), {
+ callback: function(){
+ this.afterSlide();
+ this.initAutoHide();
+ Ext.getDoc().on("click", this.slideInIf, this);
+ },
+ scope: this,
+ block: true
+ });
+ }else{
+ this.initAutoHide();
+ Ext.getDoc().on("click", this.slideInIf, this);
+ }
+ },
+
+ // private
+ afterSlideIn : function(){
+ this.clearAutoHide();
+ this.isSlid = false;
+ this.clearMonitor();
+ this.el.setStyle("z-index", "");
+ this.panel.el.replaceClass('x-panel-floating', 'x-panel-collapsed');
+ this.el.dom.style.left = this.restoreLT[0];
+ this.el.dom.style.top = this.restoreLT[1];
+
+ var ts = this.panel.tools;
+ if(ts && ts.toggle){
+ ts.toggle.show();
+ }
+ },
+
+ /**
+ * If this Region is {@link #floatable}, and this Region has been slid into floating visibility, then this method slides
+ * this region back into its collapsed state.
+ */
+ slideIn : function(cb){
+ if(!this.isSlid || this.el.hasActiveFx()){
+ Ext.callback(cb);
+ return;
+ }
+ this.isSlid = false;
+ if(this.animFloat !== false){
+ this.beforeSlide();
+ this.el.slideOut(this.getSlideAnchor(), {
+ callback: function(){
+ this.el.hide();
+ this.afterSlide();
+ this.afterSlideIn();
+ Ext.callback(cb);
+ },
+ scope: this,
+ block: true
+ });
+ }else{
+ this.el.hide();
+ this.afterSlideIn();
+ }
+ },
+
+ // private
+ slideInIf : function(e){
+ if(!e.within(this.el)){
+ this.slideIn();
+ }
+ },
+
+ // private
+ anchors : {
+ "west" : "left",
+ "east" : "right",
+ "north" : "top",
+ "south" : "bottom"
+ },
+
+ // private
+ sanchors : {
+ "west" : "l",
+ "east" : "r",
+ "north" : "t",
+ "south" : "b"
+ },
+
+ // private
+ canchors : {
+ "west" : "tl-tr",
+ "east" : "tr-tl",
+ "north" : "tl-bl",
+ "south" : "bl-tl"
+ },
+
+ // private
+ getAnchor : function(){
+ return this.anchors[this.position];
+ },
+
+ // private
+ getCollapseAnchor : function(){
+ return this.canchors[this.position];
+ },
+
+ // private
+ getSlideAnchor : function(){
+ return this.sanchors[this.position];
+ },
+
+ // private
+ getAlignAdj : function(){
+ var cm = this.cmargins;
+ switch(this.position){
+ case "west":
+ return [0, 0];
+ break;
+ case "east":
+ return [0, 0];
+ break;
+ case "north":
+ return [0, 0];
+ break;
+ case "south":
+ return [0, 0];
+ break;
+ }
+ },
+
+ // private
+ getExpandAdj : function(){
+ var c = this.collapsedEl, cm = this.cmargins;
+ switch(this.position){
+ case "west":
+ return [-(cm.right+c.getWidth()+cm.left), 0];
+ break;
+ case "east":
+ return [cm.right+c.getWidth()+cm.left, 0];
+ break;
+ case "north":
+ return [0, -(cm.top+cm.bottom+c.getHeight())];
+ break;
+ case "south":
+ return [0, cm.top+cm.bottom+c.getHeight()];
+ break;
+ }
+ },
+
+ destroy : function(){
+ if (this.autoHideSlideTask && this.autoHideSlideTask.cancel){
+ this.autoHideSlideTask.cancel();
+ }
+ Ext.destroyMembers(this, 'miniCollapsedEl', 'collapsedEl', 'expandToolEl');
+ }
+};
+
+/**
+ * @class Ext.layout.BorderLayout.SplitRegion
+ * @extends Ext.layout.BorderLayout.Region
+ * This is a specialized type of {@link Ext.layout.BorderLayout.Region BorderLayout region} that
+ * has a built-in {@link Ext.SplitBar} for user resizing of regions. The movement of the split bar
+ * is configurable to move either {@link #tickSize smooth or incrementally}.
+ * @constructor
+ * Create a new SplitRegion.
+ * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region.
+ * @param {Object} config The configuration options
+ * @param {String} position The region position. Valid values are: north, south, east, west and center. Every
+ * BorderLayout must have a center region for the primary content -- all other regions are optional.
+ */
+Ext.layout.BorderLayout.SplitRegion = function(layout, config, pos){
+ Ext.layout.BorderLayout.SplitRegion.superclass.constructor.call(this, layout, config, pos);
+ // prevent switch
+ this.applyLayout = this.applyFns[pos];
+};
+
+Ext.extend(Ext.layout.BorderLayout.SplitRegion, Ext.layout.BorderLayout.Region, {
+ /**
+ * @cfg {Number} tickSize
+ * The increment, in pixels by which to move this Region's {@link Ext.SplitBar SplitBar}.
+ * By default, the {@link Ext.SplitBar SplitBar} moves smoothly.
+ */
+ /**
+ * @cfg {String} splitTip
+ * The tooltip to display when the user hovers over a
+ * {@link Ext.layout.BorderLayout.Region#collapsible non-collapsible} region's split bar
+ * (defaults to "Drag to resize."). Only applies if
+ * {@link #useSplitTips} = true.
+ */
+ splitTip : "Drag to resize.",
+ /**
+ * @cfg {String} collapsibleSplitTip
+ * The tooltip to display when the user hovers over a
+ * {@link Ext.layout.BorderLayout.Region#collapsible collapsible} region's split bar
+ * (defaults to "Drag to resize. Double click to hide."). Only applies if
+ * {@link #useSplitTips} = true.
+ */
+ collapsibleSplitTip : "Drag to resize. Double click to hide.",
+ /**
+ * @cfg {Boolean} useSplitTips
+ * true to display a tooltip when the user hovers over a region's split bar
+ * (defaults to false). The tooltip text will be the value of either
+ * {@link #splitTip} or {@link #collapsibleSplitTip} as appropriate.
+ */
+ useSplitTips : false,
+
+ // private
+ splitSettings : {
+ north : {
+ orientation: Ext.SplitBar.VERTICAL,
+ placement: Ext.SplitBar.TOP,
+ maxFn : 'getVMaxSize',
+ minProp: 'minHeight',
+ maxProp: 'maxHeight'
+ },
+ south : {
+ orientation: Ext.SplitBar.VERTICAL,
+ placement: Ext.SplitBar.BOTTOM,
+ maxFn : 'getVMaxSize',
+ minProp: 'minHeight',
+ maxProp: 'maxHeight'
+ },
+ east : {
+ orientation: Ext.SplitBar.HORIZONTAL,
+ placement: Ext.SplitBar.RIGHT,
+ maxFn : 'getHMaxSize',
+ minProp: 'minWidth',
+ maxProp: 'maxWidth'
+ },
+ west : {
+ orientation: Ext.SplitBar.HORIZONTAL,
+ placement: Ext.SplitBar.LEFT,
+ maxFn : 'getHMaxSize',
+ minProp: 'minWidth',
+ maxProp: 'maxWidth'
+ }
+ },
+
+ // private
+ applyFns : {
+ west : function(box){
+ if(this.isCollapsed){
+ return this.applyLayoutCollapsed(box);
+ }
+ var sd = this.splitEl.dom, s = sd.style;
+ this.panel.setPosition(box.x, box.y);
+ var sw = sd.offsetWidth;
+ s.left = (box.x+box.width-sw)+'px';
+ s.top = (box.y)+'px';
+ s.height = Math.max(0, box.height)+'px';
+ this.panel.setSize(box.width-sw, box.height);
+ },
+ east : function(box){
+ if(this.isCollapsed){
+ return this.applyLayoutCollapsed(box);
+ }
+ var sd = this.splitEl.dom, s = sd.style;
+ var sw = sd.offsetWidth;
+ this.panel.setPosition(box.x+sw, box.y);
+ s.left = (box.x)+'px';
+ s.top = (box.y)+'px';
+ s.height = Math.max(0, box.height)+'px';
+ this.panel.setSize(box.width-sw, box.height);
+ },
+ north : function(box){
+ if(this.isCollapsed){
+ return this.applyLayoutCollapsed(box);
+ }
+ var sd = this.splitEl.dom, s = sd.style;
+ var sh = sd.offsetHeight;
+ this.panel.setPosition(box.x, box.y);
+ s.left = (box.x)+'px';
+ s.top = (box.y+box.height-sh)+'px';
+ s.width = Math.max(0, box.width)+'px';
+ this.panel.setSize(box.width, box.height-sh);
+ },
+ south : function(box){
+ if(this.isCollapsed){
+ return this.applyLayoutCollapsed(box);
+ }
+ var sd = this.splitEl.dom, s = sd.style;
+ var sh = sd.offsetHeight;
+ this.panel.setPosition(box.x, box.y+sh);
+ s.left = (box.x)+'px';
+ s.top = (box.y)+'px';
+ s.width = Math.max(0, box.width)+'px';
+ this.panel.setSize(box.width, box.height-sh);
+ }
+ },
+
+ // private
+ render : function(ct, p){
+ Ext.layout.BorderLayout.SplitRegion.superclass.render.call(this, ct, p);
+
+ var ps = this.position;
+
+ this.splitEl = ct.createChild({
+ cls: "x-layout-split x-layout-split-"+ps, html: " ",
+ id: this.panel.id + '-xsplit'
+ });
+
+ if(this.collapseMode == 'mini'){
+ this.miniSplitEl = this.splitEl.createChild({
+ cls: "x-layout-mini x-layout-mini-"+ps, html: " "
+ });
+ this.miniSplitEl.addClassOnOver('x-layout-mini-over');
+ this.miniSplitEl.on('click', this.onCollapseClick, this, {stopEvent:true});
+ }
+
+ var s = this.splitSettings[ps];
+
+ this.split = new Ext.SplitBar(this.splitEl.dom, p.el, s.orientation);
+ this.split.tickSize = this.tickSize;
+ this.split.placement = s.placement;
+ this.split.getMaximumSize = this[s.maxFn].createDelegate(this);
+ this.split.minSize = this.minSize || this[s.minProp];
+ this.split.on("beforeapply", this.onSplitMove, this);
+ this.split.useShim = this.useShim === true;
+ this.maxSize = this.maxSize || this[s.maxProp];
+
+ if(p.hidden){
+ this.splitEl.hide();
+ }
+
+ if(this.useSplitTips){
+ this.splitEl.dom.title = this.collapsible ? this.collapsibleSplitTip : this.splitTip;
+ }
+ if(this.collapsible){
+ this.splitEl.on("dblclick", this.onCollapseClick, this);
+ }
+ },
+
+ //docs inherit from superclass
+ getSize : function(){
+ if(this.isCollapsed){
+ return this.collapsedEl.getSize();
+ }
+ var s = this.panel.getSize();
+ if(this.position == 'north' || this.position == 'south'){
+ s.height += this.splitEl.dom.offsetHeight;
+ }else{
+ s.width += this.splitEl.dom.offsetWidth;
+ }
+ return s;
+ },
+
+ // private
+ getHMaxSize : function(){
+ var cmax = this.maxSize || 10000;
+ var center = this.layout.center;
+ return Math.min(cmax, (this.el.getWidth()+center.el.getWidth())-center.getMinWidth());
+ },
+
+ // private
+ getVMaxSize : function(){
+ var cmax = this.maxSize || 10000;
+ var center = this.layout.center;
+ return Math.min(cmax, (this.el.getHeight()+center.el.getHeight())-center.getMinHeight());
+ },
+
+ // private
+ onSplitMove : function(split, newSize){
+ var s = this.panel.getSize();
+ this.lastSplitSize = newSize;
+ if(this.position == 'north' || this.position == 'south'){
+ this.panel.setSize(s.width, newSize);
+ this.state.height = newSize;
+ }else{
+ this.panel.setSize(newSize, s.height);
+ this.state.width = newSize;
+ }
+ this.layout.layout();
+ this.panel.saveState();
+ return false;
+ },
+
+ /**
+ * Returns a reference to the split bar in use by this region.
+ * @return {Ext.SplitBar} The split bar
+ */
+ getSplitBar : function(){
+ return this.split;
+ },
+
+ // inherit docs
+ destroy : function() {
+ Ext.destroy(this.miniSplitEl, this.split, this.splitEl);
+ Ext.layout.BorderLayout.SplitRegion.superclass.destroy.call(this);
+ }
+});
+
+Ext.Container.LAYOUTS['border'] = Ext.layout.BorderLayout;
+/**
+ * @class Ext.layout.FormLayout
+ * @extends Ext.layout.AnchorLayout
+ * This layout manager is specifically designed for rendering and managing child Components of
+ * {@link Ext.form.FormPanel forms}. It is responsible for rendering the labels of
+ * {@link Ext.form.Field Field}s.
+ *
+ * This layout manager is used when a Container is configured with the layout:'form'
+ * {@link Ext.Container#layout layout} config option, and should generally not need to be created directly
+ * via the new keyword. See {@link Ext.Container#layout} for additional details.
+ *
+ * In an application, it will usually be preferrable to use a {@link Ext.form.FormPanel FormPanel}
+ * (which is configured with FormLayout as its layout class by default) since it also provides built-in
+ * functionality for {@link Ext.form.BasicForm#doAction loading, validating and submitting} the form.
+ *
+ * A {@link Ext.Container Container} using the FormLayout layout manager (e.g.
+ * {@link Ext.form.FormPanel} or specifying layout:'form') can also accept the following
+ * layout-specific config properties:
+ * - {@link Ext.form.FormPanel#hideLabels hideLabels}
+ * - {@link Ext.form.FormPanel#labelAlign labelAlign}
+ * - {@link Ext.form.FormPanel#labelPad labelPad}
+ * - {@link Ext.form.FormPanel#labelSeparator labelSeparator}
+ * - {@link Ext.form.FormPanel#labelWidth labelWidth}
+ *
+ *
+ * Any Component (including Fields) managed by FormLayout accepts the following as a config option:
+ *
+ * - {@link Ext.Component#anchor anchor}
+ *
+ *
+ * Any Component managed by FormLayout may be rendered as a form field (with an associated label) by
+ * configuring it with a non-null {@link Ext.Component#fieldLabel fieldLabel}. Components configured
+ * in this way may be configured with the following options which affect the way the FormLayout renders them:
+ *
+ * - {@link Ext.Component#clearCls clearCls}
+ * - {@link Ext.Component#fieldLabel fieldLabel}
+ * - {@link Ext.Component#hideLabel hideLabel}
+ * - {@link Ext.Component#itemCls itemCls}
+ * - {@link Ext.Component#labelSeparator labelSeparator}
+ * - {@link Ext.Component#labelStyle labelStyle}
+ *
+ *
+ * Example usage:
+ *
+// Required if showing validation messages
+Ext.QuickTips.init();
+
+// While you can create a basic Panel with layout:'form', practically
+// you should usually use a FormPanel to also get its form functionality
+// since it already creates a FormLayout internally.
+var form = new Ext.form.FormPanel({
+ title: 'Form Layout',
+ bodyStyle: 'padding:15px',
+ width: 350,
+ defaultType: 'textfield',
+ defaults: {
+ // applied to each contained item
+ width: 230,
+ msgTarget: 'side'
+ },
+ items: [{
+ fieldLabel: 'First Name',
+ name: 'first',
+ allowBlank: false,
+ {@link Ext.Component#labelSeparator labelSeparator}: ':' // override labelSeparator layout config
+ },{
+ fieldLabel: 'Last Name',
+ name: 'last'
+ },{
+ fieldLabel: 'Email',
+ name: 'email',
+ vtype:'email'
+ }, {
+ xtype: 'textarea',
+ hideLabel: true, // override hideLabels layout config
+ name: 'msg',
+ anchor: '100% -53'
+ }
+ ],
+ buttons: [
+ {text: 'Save'},
+ {text: 'Cancel'}
+ ],
+ layoutConfig: {
+ {@link #labelSeparator}: '~' // superseded by assignment below
+ },
+ // config options applicable to container when layout='form':
+ hideLabels: false,
+ labelAlign: 'left', // or 'right' or 'top'
+ {@link Ext.form.FormPanel#labelSeparator labelSeparator}: '>>', // takes precedence over layoutConfig value
+ labelWidth: 65, // defaults to 100
+ labelPad: 8 // defaults to 5, must specify labelWidth to be honored
+});
+
+ */
+Ext.layout.FormLayout = Ext.extend(Ext.layout.AnchorLayout, {
+
+ /**
+ * @cfg {String} labelSeparator
+ * See {@link Ext.form.FormPanel}.{@link Ext.form.FormPanel#labelSeparator labelSeparator}. Configuration
+ * of this property at the container level takes precedence.
+ */
+ labelSeparator : ':',
+
+ /**
+ * Read only. The CSS style specification string added to field labels in this layout if not
+ * otherwise {@link Ext.Component#labelStyle specified by each contained field}.
+ * @type String
+ * @property labelStyle
+ */
+
+ /**
+ * @cfg {Boolean} trackLabels
+ * True to show/hide the field label when the field is hidden. Defaults to true.
+ */
+ trackLabels: true,
+
+ type: 'form',
+
+ onRemove: function(c){
+ Ext.layout.FormLayout.superclass.onRemove.call(this, c);
+ if(this.trackLabels){
+ c.un('show', this.onFieldShow, this);
+ c.un('hide', this.onFieldHide, this);
+ }
+ // check for itemCt, since we may be removing a fieldset or something similar
+ var el = c.getPositionEl(),
+ ct = c.getItemCt && c.getItemCt();
+ if (c.rendered && ct) {
+ if (el && el.dom) {
+ el.insertAfter(ct);
+ }
+ Ext.destroy(ct);
+ Ext.destroyMembers(c, 'label', 'itemCt');
+ if (c.customItemCt) {
+ Ext.destroyMembers(c, 'getItemCt', 'customItemCt');
+ }
+ }
+ },
+
+ // private
+ setContainer : function(ct){
+ Ext.layout.FormLayout.superclass.setContainer.call(this, ct);
+ if(ct.labelAlign){
+ ct.addClass('x-form-label-'+ct.labelAlign);
+ }
+
+ if(ct.hideLabels){
+ Ext.apply(this, {
+ labelStyle: 'display:none',
+ elementStyle: 'padding-left:0;',
+ labelAdjust: 0
+ });
+ }else{
+ this.labelSeparator = Ext.isDefined(ct.labelSeparator) ? ct.labelSeparator : this.labelSeparator;
+ ct.labelWidth = ct.labelWidth || 100;
+ if(Ext.isNumber(ct.labelWidth)){
+ var pad = Ext.isNumber(ct.labelPad) ? ct.labelPad : 5;
+ Ext.apply(this, {
+ labelAdjust: ct.labelWidth + pad,
+ labelStyle: 'width:' + ct.labelWidth + 'px;',
+ elementStyle: 'padding-left:' + (ct.labelWidth + pad) + 'px'
+ });
+ }
+ if(ct.labelAlign == 'top'){
+ Ext.apply(this, {
+ labelStyle: 'width:auto;',
+ labelAdjust: 0,
+ elementStyle: 'padding-left:0;'
+ });
+ }
+ }
+ },
+
+ // private
+ isHide: function(c){
+ return c.hideLabel || this.container.hideLabels;
+ },
+
+ onFieldShow: function(c){
+ c.getItemCt().removeClass('x-hide-' + c.hideMode);
+
+ // Composite fields will need to layout after the container is made visible
+ if (c.isComposite) {
+ c.doLayout();
+ }
+ },
+
+ onFieldHide: function(c){
+ c.getItemCt().addClass('x-hide-' + c.hideMode);
+ },
+
+ //private
+ getLabelStyle: function(s){
+ var ls = '', items = [this.labelStyle, s];
+ for (var i = 0, len = items.length; i < len; ++i){
+ if (items[i]){
+ ls += items[i];
+ if (ls.substr(-1, 1) != ';'){
+ ls += ';';
+ }
+ }
+ }
+ return ls;
+ },
+
+ /**
+ * @cfg {Ext.Template} fieldTpl
+ * A {@link Ext.Template#compile compile}d {@link Ext.Template} for rendering
+ * the fully wrapped, labeled and styled form Field. Defaults to:
+new Ext.Template(
+ '<div class="x-form-item {itemCls}" tabIndex="-1">',
+ '<label for="{id}" style="{labelStyle}" class="x-form-item-label">{label}{labelSeparator}</label>',
+ '<div class="x-form-element" id="x-form-el-{id}" style="{elementStyle}">',
+ '</div><div class="{clearCls}"></div>',
+ '</div>'
+);
+
+ * This may be specified to produce a different DOM structure when rendering form Fields.
+ * A description of the properties within the template follows:
+ * - itemCls : StringThe CSS class applied to the outermost div wrapper
+ * that contains this field label and field element (the default class is 'x-form-item' and itemCls
+ * will be added to that). If supplied, itemCls at the field level will override the default itemCls
+ * supplied at the container level.
+ * - id : StringThe id of the Field
+ * - {@link #labelStyle} : String
+ * A CSS style specification string to add to the field label for this field (defaults to '' or the
+ * {@link #labelStyle layout's value for labelStyle}).
+ * - label : StringThe text to display as the label for this
+ * field (defaults to '')
+ * - {@link #labelSeparator} : StringThe separator to display after
+ * the text of the label for this field (defaults to a colon ':' or the
+ * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.
+ * - elementStyle : StringThe styles text for the input element's wrapper.
+ * - clearCls : StringThe CSS class to apply to the special clearing div
+ * rendered directly after each form field wrapper (defaults to 'x-form-clear-left')
+ *
+ * Also see {@link #getTemplateArgs}
+ */
+
+ /**
+ * @private
+ *
+ */
+ renderItem : function(c, position, target){
+ if(c && (c.isFormField || c.fieldLabel) && c.inputType != 'hidden'){
+ var args = this.getTemplateArgs(c);
+ if(Ext.isNumber(position)){
+ position = target.dom.childNodes[position] || null;
+ }
+ if(position){
+ c.itemCt = this.fieldTpl.insertBefore(position, args, true);
+ }else{
+ c.itemCt = this.fieldTpl.append(target, args, true);
+ }
+ if(!c.getItemCt){
+ // Non form fields don't have getItemCt, apply it here
+ // This will get cleaned up in onRemove
+ Ext.apply(c, {
+ getItemCt: function(){
+ return c.itemCt;
+ },
+ customItemCt: true
+ });
+ }
+ c.label = c.getItemCt().child('label.x-form-item-label');
+ if(!c.rendered){
+ c.render('x-form-el-' + c.id);
+ }else if(!this.isValidParent(c, target)){
+ Ext.fly('x-form-el-' + c.id).appendChild(c.getPositionEl());
+ }
+ if(this.trackLabels){
+ if(c.hidden){
+ this.onFieldHide(c);
+ }
+ c.on({
+ scope: this,
+ show: this.onFieldShow,
+ hide: this.onFieldHide
+ });
+ }
+ this.configureItem(c);
+ }else {
+ Ext.layout.FormLayout.superclass.renderItem.apply(this, arguments);
+ }
+ },
+
+ /**
+ * Provides template arguments for rendering the fully wrapped, labeled and styled form Field.
+ * This method returns an object hash containing properties used by the layout's {@link #fieldTpl}
+ * to create a correctly wrapped, labeled and styled form Field. This may be overriden to
+ * create custom layouts. The properties which must be returned are:
+ * - itemCls : StringThe CSS class applied to the outermost div wrapper
+ * that contains this field label and field element (the default class is 'x-form-item' and itemCls
+ * will be added to that). If supplied, itemCls at the field level will override the default itemCls
+ * supplied at the container level.
+ * - id : StringThe id of the Field
+ * - {@link #labelStyle} : String
+ * A CSS style specification string to add to the field label for this field (defaults to '' or the
+ * {@link #labelStyle layout's value for labelStyle}).
+ * - label : StringThe text to display as the label for this
+ * field (defaults to the field's configured fieldLabel property)
+ * - {@link #labelSeparator} : StringThe separator to display after
+ * the text of the label for this field (defaults to a colon ':' or the
+ * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.
+ * - elementStyle : StringThe styles text for the input element's wrapper.
+ * - clearCls : StringThe CSS class to apply to the special clearing div
+ * rendered directly after each form field wrapper (defaults to 'x-form-clear-left')
+ *
+ * @param (Ext.form.Field} field The {@link Ext.form.Field Field} being rendered.
+ * @return {Object} An object hash containing the properties required to render the Field.
+ */
+ getTemplateArgs: function(field) {
+ var noLabelSep = !field.fieldLabel || field.hideLabel;
+
+ return {
+ id : field.id,
+ label : field.fieldLabel,
+ itemCls : (field.itemCls || this.container.itemCls || '') + (field.hideLabel ? ' x-hide-label' : ''),
+ clearCls : field.clearCls || 'x-form-clear-left',
+ labelStyle : this.getLabelStyle(field.labelStyle),
+ elementStyle : this.elementStyle || '',
+ labelSeparator: noLabelSep ? '' : (Ext.isDefined(field.labelSeparator) ? field.labelSeparator : this.labelSeparator)
+ };
+ },
+
+ // private
+ adjustWidthAnchor: function(value, c){
+ if(c.label && !this.isHide(c) && (this.container.labelAlign != 'top')){
+ var adjust = Ext.isIE6 || (Ext.isIE && !Ext.isStrict);
+ return value - this.labelAdjust + (adjust ? -3 : 0);
+ }
+ return value;
+ },
+
+ adjustHeightAnchor : function(value, c){
+ if(c.label && !this.isHide(c) && (this.container.labelAlign == 'top')){
+ return value - c.label.getHeight();
+ }
+ return value;
+ },
+
+ // private
+ isValidParent : function(c, target){
+ return target && this.container.getEl().contains(c.getPositionEl());
+ }
+
+ /**
+ * @property activeItem
+ * @hide
+ */
+});
+
+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 && !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);
+ }
+ },
+
+ /**
+ * Sets the active (expanded) item in the layout.
+ * @param {String/Number} item The string component id or numeric index of the item to activate
+ */
+ setActiveItem : function(item){
+ this.setActive(item, true);
+ },
+
+ // private
+ setActive : function(item, expand){
+ var ai = this.activeItem;
+ item = this.container.getComponent(item);
+ if(ai != item){
+ if(item.rendered && item.collapsed && expand){
+ item.expand();
+ }else{
+ if(ai){
+ ai.fireEvent('deactivate', ai);
+ }
+ this.activeItem = item;
+ item.fireEvent('activate', item);
+ }
+ }
+ }
+});
+Ext.Container.LAYOUTS.accordion = Ext.layout.AccordionLayout;
+
+//backwards compat
+Ext.layout.Accordion = Ext.layout.AccordionLayout;/**
+ * @class Ext.layout.TableLayout
+ * @extends Ext.layout.ContainerLayout
+ * This layout allows you to easily render content into an HTML table. The total number of columns can be
+ * specified, and rowspan and colspan can be used to create complex layouts within the table.
+ * This class is intended to be extended or created via the layout:'table' {@link Ext.Container#layout} config,
+ * and should generally not need to be created directly via the new keyword.
+ * Note that when creating a layout via config, the layout-specific config properties must be passed in via
+ * the {@link Ext.Container#layoutConfig} object which will then be applied internally to the layout. In the
+ * case of TableLayout, the only valid layout config property is {@link #columns}. However, the items added to a
+ * TableLayout can supply the following table-specific config properties:
+ *
+ * - rowspan Applied to the table cell containing the item.
+ * - colspan Applied to the table cell containing the item.
+ * - cellId An id applied to the table cell containing the item.
+ * - cellCls A CSS class name added to the table cell containing the item.
+ *
+ * The basic concept of building up a TableLayout is conceptually very similar to building up a standard
+ * HTML table. You simply add each panel (or "cell") that you want to include along with any span attributes
+ * specified as the special config properties of rowspan and colspan which work exactly like their HTML counterparts.
+ * Rather than explicitly creating and nesting rows and columns as you would in HTML, you simply specify the
+ * total column count in the layoutConfig and start adding panels in their natural order from left to right,
+ * top to bottom. The layout will automatically figure out, based on the column count, rowspans and colspans,
+ * how to position each panel within the table. Just like with HTML tables, your rowspans and colspans must add
+ * up correctly in your overall layout or you'll end up with missing and/or extra cells! Example usage:
+ *
+// This code will generate a layout table that is 3 columns by 2 rows
+// with some spanning included. The basic layout will be:
+// +--------+-----------------+
+// | A | B |
+// | |--------+--------|
+// | | C | D |
+// +--------+--------+--------+
+var table = new Ext.Panel({
+ title: 'Table Layout',
+ layout:'table',
+ defaults: {
+ // applied to each contained panel
+ bodyStyle:'padding:20px'
+ },
+ layoutConfig: {
+ // The total column count must be specified here
+ columns: 3
+ },
+ items: [{
+ html: '<p>Cell A content</p>',
+ rowspan: 2
+ },{
+ html: '<p>Cell B content</p>',
+ colspan: 2
+ },{
+ html: '<p>Cell C content</p>',
+ cellCls: 'highlight'
+ },{
+ html: '<p>Cell D content</p>'
+ }]
+});
+
+ */
+Ext.layout.TableLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ /**
+ * @cfg {Number} columns
+ * The total number of columns to create in the table for this layout. If not specified, all Components added to
+ * this layout will be rendered into a single row using one column per Component.
+ */
+
+ // private
+ monitorResize:false,
+
+ 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.
+ */
+Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ /**
+ * @cfg {Object} defaultMargins
+ * If the individual contained items do not have a margins
+ * property specified, the default margins from this property will be
+ * applied to each item.
+ *
This property may be specified as an object containing margins
+ * to apply in the format:
+{
+ top: (top margin),
+ right: (right margin),
+ bottom: (bottom margin),
+ left: (left margin)
+}
+ * This property may also be specified as a string containing
+ * space-separated, numeric margin values. The order of the sides associated
+ * with each value matches the way CSS processes margin values:
+ *
+ * - If there is only one value, it applies to all sides.
+ * - If there are two values, the top and bottom borders are set to the
+ * first value and the right and left are set to the second.
+ * - If there are three values, the top is set to the first value, the left
+ * and right are set to the second, and the bottom is set to the third.
+ * - If there are four values, they apply to the top, right, bottom, and
+ * left, respectively.
+ *
+ * Defaults to:
+ * {top:0, right:0, bottom:0, left:0}
+ *
+ */
+ defaultMargins : {left:0,top:0,right:0,bottom:0},
+ /**
+ * @cfg {String} padding
+ * Sets the padding to be applied to all child items managed by this layout.
+ * This property must be specified as a string containing
+ * space-separated, numeric padding values. The order of the sides associated
+ * with each value matches the way CSS processes padding values:
+ *
+ * - If there is only one value, it applies to all sides.
+ * - If there are two values, the top and bottom borders are set to the
+ * first value and the right and left are set to the second.
+ * - If there are three values, the top is set to the first value, the left
+ * and right are set to the second, and the bottom is set to the third.
+ * - If there are four values, they apply to the top, right, bottom, and
+ * left, respectively.
+ *
+ * Defaults to: "0"
+ */
+ padding : '0',
+ // documented in subclasses
+ pack : 'start',
+
+ // private
+ monitorResize : true,
+ type: 'box',
+ scrollOffset : 0,
+ extraCls : 'x-box-item',
+ targetCls : 'x-box-layout-ct',
+ innerCls : 'x-box-inner',
+
+ constructor : function(config){
+ Ext.layout.BoxLayout.superclass.constructor.call(this, config);
+
+ if (Ext.isString(this.defaultMargins)) {
+ this.defaultMargins = this.parseMargins(this.defaultMargins);
+ }
+
+ 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) {
+ 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
+ this.innerCt = target.createChild({cls:this.innerCls});
+ this.padding = this.parseMargins(this.padding);
+ }
+ Ext.layout.BoxLayout.superclass.renderAll.call(this, ct, this.innerCt);
+ },
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget(), ret;
+
+ if (target) {
+ ret = target.getViewSize();
+
+ // IE in strict mode will return a width of 0 on the 1st pass of getViewSize.
+ // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
+ // with getViewSize
+ if (Ext.isIE && Ext.isStrict && ret.width == 0){
+ ret = target.getStyleSize();
+ }
+
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
+ }
+
+ return ret;
+ },
+
+ // private
+ renderItem : function(c) {
+ if(Ext.isString(c.margins)){
+ c.margins = this.parseMargins(c.margins);
+ }else if(!c.margins){
+ c.margins = this.defaultMargins;
+ }
+ Ext.layout.BoxLayout.superclass.renderItem.apply(this, arguments);
+ },
+
+ /**
+ * @private
+ */
+ destroy: function() {
+ Ext.destroy(this.overflowHandler);
+
+ Ext.layout.BoxLayout.superclass.destroy.apply(this, arguments);
+ }
+});
+
+
+
+Ext.ns('Ext.layout.boxOverflow');
+
+/**
+ * @class Ext.layout.boxOverflow.None
+ * @extends Object
+ * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
+ * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
+ * for its container.
+ */
+
+Ext.layout.boxOverflow.None = Ext.extend(Object, {
+ constructor: function(layout, config) {
+ this.layout = layout;
+
+ Ext.apply(this, config || {});
+ },
+
+ handleOverflow: Ext.emptyFn,
+
+ clearOverflow: Ext.emptyFn
+});
+
+
+Ext.layout.boxOverflow.none = Ext.layout.boxOverflow.None;
+/**
+ * @class Ext.layout.boxOverflow.Menu
+ * @extends Ext.layout.boxOverflow.None
+ * Description
+ */
+Ext.layout.boxOverflow.Menu = Ext.extend(Ext.layout.boxOverflow.None, {
+ /**
+ * @cfg 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',
+
+ /**
+ * @property noItemsMenuText
+ * @type String
+ * HTML fragment to render into the toolbar overflow menu if there are no items to display
+ */
+ noItemsMenuText : ' ',
+
+ 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');
+ }
+ },
+
+ /**
+ * @private
+ */
+ clearOverflow: function(calculations, targetSize) {
+ var newWidth = targetSize.width + (this.afterCt ? this.afterCt.getWidth() : 0),
+ items = this.menuItems;
+
+ this.hideTrigger();
+
+ for (var index = 0, length = items.length; index < length; index++) {
+ items.pop().component.show();
+ }
+
+ return {
+ targetSize: {
+ height: targetSize.height,
+ width : newWidth
+ }
+ };
+ },
+
+ /**
+ * @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 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;
+
+ 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
+ * 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
+ }
+ });
+
+ /**
+ * @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;
+ }
+ }
+
+ if (recalculate) {
+ this.menuItems = this.menuItems.slice(showCount);
+ } else {
+ for (var i = boxes.length - 1; i >= 0; i--) {
+ var item = boxes[i].component,
+ right = boxes[i].left + boxes[i].width;
+
+ if (right >= newWidth) {
+ this.menuItems.unshift({
+ component: item,
+ width : boxes[i].width
+ });
+
+ item.hide();
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (this.menuItems.length == 0) {
+ this.hideTrigger();
+ }
+
+ return {
+ targetSize: {
+ height: targetSize.height,
+ width : newWidth
+ },
+ recalculate: recalculate
+ };
+ }
+});
+
+Ext.layout.boxOverflow.menu.hbox = Ext.layout.boxOverflow.HorizontalMenu;/**
+ * @class Ext.layout.boxOverflow.Scroller
+ * @extends Ext.layout.boxOverflow.None
+ * Description
+ */
+Ext.layout.boxOverflow.Scroller = Ext.extend(Ext.layout.boxOverflow.None, {
+ /**
+ * @cfg animateScroll
+ * @type Boolean
+ * True to animate the scrolling of items within the layout (defaults to true, ignored if enableScroll is false)
+ */
+ animateScroll: true,
+
+ /**
+ * @cfg scrollIncrement
+ * @type Number
+ * The number of pixels to scroll by on scroller click (defaults to 100)
+ */
+ scrollIncrement: 100,
+
+ /**
+ * @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();
+
+ this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
+ }
+ });
+ },
+
+ /**
+ * @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();
+ }
+ },
+
+ /**
+ * @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;
+ }
+ },
+
+ /**
+ * @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];
+ }
+
+ 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;
+ }
+
+ 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);
+ }
+ }
+ },
+
+ /**
+ * @private
+ * For a given item in the container, return an object with information on whether the item is visible
+ * with the current innerCt scroll value.
+ * @param {Ext.Component} item The item
+ * @return {Object} Values for fullyVisible, hiddenLeft and hiddenRight
+ */
+ getItemVisibility: function(item) {
+ var box = this.getItem(item).getBox(true, true),
+ itemLeft = box.x,
+ itemRight = box.x + box.width,
+ scrollLeft = this.getScrollPosition(),
+ scrollRight = this.layout.innerCt.getWidth() + scrollLeft;
+
+ return {
+ hiddenLeft : itemLeft < scrollLeft,
+ hiddenRight : itemRight > scrollRight,
+ fullyVisible: itemLeft > scrollLeft && itemRight < scrollRight
+ };
+ }
+});
+
+Ext.layout.boxOverflow.scroller = Ext.layout.boxOverflow.Scroller;
+
+
+/**
+ * @class Ext.layout.boxOverflow.VerticalScroller
+ * @extends Ext.layout.boxOverflow.Scroller
+ * Description
+ */
+Ext.layout.boxOverflow.VerticalScroller = Ext.extend(Ext.layout.boxOverflow.Scroller, {
+ scrollIncrement: 75,
+ wheelIncrement : 2,
+
+ handleOverflow: function(calculations, targetSize) {
+ Ext.layout.boxOverflow.VerticalScroller.superclass.handleOverflow.apply(this, arguments);
+
+ return {
+ targetSize: {
+ height: targetSize.height - (this.beforeCt.getHeight() + this.afterCt.getHeight()),
+ width : targetSize.width
+ }
+ };
+ },
+
+ /**
+ * @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');
+
+ 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.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();
+ }
+ }
+ },
+
+ /**
+ * 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();
+ }
+});
+
+Ext.layout.boxOverflow.scroller.vbox = Ext.layout.boxOverflow.VerticalScroller;
+
+
+/**
+ * @class Ext.layout.boxOverflow.HorizontalScroller
+ * @extends Ext.layout.boxOverflow.Scroller
+ * Description
+ */
+Ext.layout.boxOverflow.HorizontalScroller = Ext.extend(Ext.layout.boxOverflow.Scroller, {
+ handleOverflow: function(calculations, targetSize) {
+ Ext.layout.boxOverflow.HorizontalScroller.superclass.handleOverflow.apply(this, arguments);
+
+ return {
+ targetSize: {
+ height: targetSize.height,
+ width : targetSize.width - (this.beforeCt.getWidth() + this.afterCt.getWidth())
+ }
+ };
+ },
+
+ /**
+ * @private
+ * Creates the beforeCt and afterCt elements if they have not already been created
+ */
+ createInnerElements: function() {
+ var target = this.layout.innerCt;
+
+ //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
+ //special items such as scrollers or dropdown menu triggers
+ if (!this.beforeCt) {
+ this.afterCt = target.insertSibling({cls: this.afterCls}, 'before');
+ this.beforeCt = target.insertSibling({cls: this.beforeCls}, 'before');
+
+ this.createWheelListener();
+ }
+ },
+
+ /**
+ * @private
+ * Scrolls to the given position. Performs bounds checking.
+ * @param {Number} position The position to scroll to. This is constrained.
+ * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
+ */
+ scrollTo: function(position, animate) {
+ var oldPosition = this.getScrollPosition(),
+ newPosition = position.constrain(0, this.getMaxScrollRight());
+
+ if (newPosition != oldPosition && !this.scrolling) {
+ if (animate == undefined) {
+ animate = this.animateScroll;
+ }
+
+ this.layout.innerCt.scrollTo('left', newPosition, animate ? this.getScrollAnim() : false);
+
+ if (animate) {
+ this.scrolling = true;
+ } else {
+ this.scrolling = false;
+ this.updateScrollButtons();
+ }
+ }
+ },
+
+ /**
+ * Returns the current scroll position of the innerCt element
+ * @return {Number} The current scroll position
+ */
+ getScrollPosition: function(){
+ return parseInt(this.layout.innerCt.dom.scrollLeft, 10) || 0;
+ },
+
+ /**
+ * @private
+ * Returns the maximum value we can scrollTo
+ * @return {Number} The max scroll value
+ */
+ getMaxScrollRight: function() {
+ return this.layout.innerCt.dom.scrollWidth - this.layout.innerCt.getWidth();
+ },
+
+ /**
+ * @private
+ * Returns true if the innerCt scroll is already at its right-most point
+ * @return {Boolean} True if already at furthest right point
+ */
+ atExtremeAfter: function() {
+ return this.getScrollPosition() >= this.getMaxScrollRight();
+ }
+});
+
+Ext.layout.boxOverflow.scroller.hbox = Ext.layout.boxOverflow.HorizontalScroller;/**
+ * @class Ext.layout.HBoxLayout
+ * @extends Ext.layout.BoxLayout
+ * A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
+ * space between child items containing a numeric flex
configuration.
+ * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
+ */
+Ext.layout.HBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
+ /**
+ * @cfg {String} align
+ * Controls how the child items of the container are aligned. Acceptable configuration values for this
+ * property are:
+ *
+ * - top : Defaultchild items are aligned vertically
+ * at the top of the container
+ * - middle : child items are aligned vertically in the
+ * middle of the container
+ * - stretch : child items are stretched vertically to fill
+ * the height of the container
+ * - stretchmax : child items are stretched vertically to
+ * the height of the largest item.
+ */
+ align: 'top', // top, middle, stretch, strechmax
+
+ type : 'hbox',
+
+ /**
+ * @cfg {String} pack
+ * Controls how the child items of the container are packed together. Acceptable configuration values
+ * for this property are:
+ *
+ * - start : Defaultchild 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;
+ }
+ }
+
+ 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();
+ }
+
+ 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;
+
+ //calculate the widths of each flexed item
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ calcs = boxes[i];
+
+ 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;
+ }
+ }
+ }
+ }
+
+ if (isCenter) {
+ leftOffset += availableWidth / 2;
+ } else if (isEnd) {
+ leftOffset += availableWidth;
+ }
+
+ //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;
+ }
+
+ return {
+ boxes: boxes,
+ meta : {
+ maxHeight : maxHeight,
+ nonFlexWidth: nonFlexWidth,
+ desiredWidth: desiredWidth,
+ minimumWidth: minimumWidth,
+ shortfall : desiredWidth - width,
+ tooNarrow : tooNarrow
+ }
+ };
+ }
+});
+
+Ext.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout;/**
+ * @class Ext.layout.VBoxLayout
+ * @extends Ext.layout.BoxLayout
+ * A layout that arranges items vertically down a Container. This layout optionally divides available vertical
+ * space between child items containing a numeric flex
configuration.
+ * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
+ */
+Ext.layout.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
+ /**
+ * @cfg {String} align
+ * Controls how the child items of the container are aligned. Acceptable configuration values for this
+ * property are:
+ *
+ * - left : Defaultchild items are aligned horizontally
+ * at the left side of the container
+ * - center : child items are aligned horizontally at the
+ * mid-width of the container
+ * - stretch : child items are stretched horizontally to fill
+ * the width of the container
+ * - stretchmax : child items are stretched horizontally to
+ * the size of the largest item.
+ *
+ */
+ align : 'left', // left, center, stretch, strechmax
+ type: 'vbox',
+
+ /**
+ * @cfg {String} pack
+ * Controls how the child items of the container are packed together. Acceptable configuration values
+ * for this property are:
+ *
+ * - start : Defaultchild items are packed together at
+ * top side of container
+ * - center : child items are packed together at
+ * mid-height of container
+ * - end : child items are packed together at bottom
+ * side of container
+ *
+ */
+
+ /**
+ * @cfg {Number} flex
+ * This configuation option is to be applied to child items of the container managed
+ * by this layout. Each child item with a flex property will be flexed vertically
+ * according to each item's relative flex value compared to the sum of all items with
+ * a flex value specified. Any child items that have either a flex = 0 or
+ * flex = undefined will not be 'flexed' (the initial size will not be changed).
+ */
+
+ /**
+ * @private
+ * 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();
+ }
+
+ childSize = child.getSize();
+ childWidth = childSize.width;
+ childHeight = childSize.height;
+ }
+ }
+
+ childMargins = child.margins;
+ vertMargins = childMargins.top + childMargins.bottom;
+
+ nonFlexHeight += vertMargins + (childHeight || 0);
+ desiredHeight += vertMargins + (child.flex ? child.minHeight || 0 : childHeight);
+ minimumHeight += vertMargins + (child.minHeight || childHeight || 0);
+
+ // 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();
+ }
+
+ maxWidth = Math.max(maxWidth, childWidth + childMargins.left + childMargins.right);
+
+ //cache the size of each child component
+ boxes.push({
+ component: child,
+ height : childHeight || undefined,
+ width : childWidth || undefined
+ });
+ }
+
+ var shortfall = desiredHeight - height,
+ tooNarrow = minimumHeight > height;
+
+ //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 = [];
+
+ /**
+ * 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.vbox = Ext.layout.VBoxLayout;
+/**
+ * @class Ext.layout.ToolbarLayout
+ * @extends Ext.layout.ContainerLayout
+ * Layout manager used by Ext.Toolbar. This is highly specialised for use by Toolbars and would not
+ * usually be used by any other class.
+ */
+Ext.layout.ToolbarLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ monitorResize : true,
+
+ type: 'toolbar',
+
+ /**
+ * @property triggerWidth
+ * @type Number
+ * The width allocated for the menu trigger at the extreme right end of the Toolbar
+ */
+ triggerWidth: 18,
+
+ /**
+ * @property noItemsMenuText
+ * @type String
+ * HTML fragment to render into the toolbar overflow menu if there are no items to display
+ */
+ noItemsMenuText : ' ',
+
+ /**
+ * @private
+ * @property lastOverflow
+ * @type Boolean
+ * Used internally to record whether the last layout caused an overflow or not
+ */
+ lastOverflow: false,
+
+ /**
+ * @private
+ * @property tableHTML
+ * @type String
+ * String used to build the HTML injected to support the Toolbar's layout. The align property is
+ * injected into this string inside the td.x-toolbar-left element during onLayout.
+ */
+ tableHTML: [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ' ',
+ '',
+ '
',
+ ' ',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ' ',
+ '',
+ '
',
+ ' ',
+ '',
+ '',
+ '',
+ ' ',
+ '',
+ '
',
+ ' ',
+ ' ',
+ '',
+ '
',
+ ' ',
+ ' ',
+ '',
+ '
'
+ ].join(""),
+
+ /**
+ * @private
+ * Create the wrapping Toolbar HTML and render/move all the items into the correct places
+ */
+ onLayout : function(ct, target) {
+ //render the Toolbar HTML if it's not already present
+ if (!this.leftTr) {
+ var align = ct.buttonAlign == 'center' ? 'center' : 'left';
+
+ target.addClass('x-toolbar-layout-ct');
+ target.insertHtml('beforeEnd', String.format(this.tableHTML, align));
+
+ this.leftTr = target.child('tr.x-toolbar-left-row', true);
+ this.rightTr = target.child('tr.x-toolbar-right-row', true);
+ this.extrasTr = target.child('tr.x-toolbar-extras-row', true);
+
+ if (this.hiddenItem == undefined) {
+ /**
+ * @property hiddenItems
+ * @type Array
+ * Holds all items that are currently hidden due to there not being enough space to render them
+ * These items will appear on the expand menu.
+ */
+ this.hiddenItems = [];
+ }
+ }
+
+ var side = ct.buttonAlign == 'right' ? this.rightTr : this.leftTr,
+ items = ct.items.items,
+ position = 0;
+
+ //render each item if not already rendered, place it into the correct (left or right) target
+ for (var i = 0, len = items.length, c; i < len; i++, position++) {
+ c = items[i];
+
+ if (c.isFill) {
+ side = this.rightTr;
+ position = -1;
+ } else if (!c.rendered) {
+ c.render(this.insertCell(c, side, position));
+ 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);
+ },
+
+ /**
+ * @private
+ * Removes any empty nodes from the given element
+ * @param {Ext.Element} el The element to clean up
+ */
+ cleanup : function(el) {
+ var cn = el.childNodes, i, c;
+
+ for (i = cn.length-1; i >= 0 && (c = cn[i]); i--) {
+ if (!c.firstChild) {
+ el.removeChild(c);
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Inserts the given Toolbar item into the given element
+ * @param {Ext.Component} c The component to add
+ * @param {Ext.Element} target The target to add the component to
+ * @param {Number} position The position to add the component at
+ */
+ insertCell : function(c, target, position) {
+ var td = document.createElement('td');
+ td.className = 'x-toolbar-cell';
+
+ target.insertBefore(td, target.childNodes[position] || null);
+
+ return td;
+ },
+
+ /**
+ * @private
+ * Hides an item because it will not fit in the available width. The item will be unhidden again
+ * if the Toolbar is resized to be large enough to show it
+ * @param {Ext.Component} item The item to hide
+ */
+ hideItem : function(item) {
+ this.hiddenItems.push(item);
+
+ item.xtbHidden = true;
+ item.xtbWidth = item.getPositionEl().dom.parentNode.offsetWidth;
+ item.hide();
+ },
+
+ /**
+ * @private
+ * Unhides an item that was previously hidden due to there not being enough space left on the Toolbar
+ * @param {Ext.Component} item The item to show
+ */
+ unhideItem : function(item) {
+ item.show();
+ item.xtbHidden = false;
+ this.hiddenItems.remove(item);
+ },
+
+ /**
+ * @private
+ * Returns the width of the given toolbar item. If the item is currently hidden because there
+ * is not enough room to render it, its previous width is returned
+ * @param {Ext.Component} c The component to measure
+ * @return {Number} The width of the item
+ */
+ getItemWidth : function(c) {
+ return c.hidden ? (c.xtbWidth || 0) : c.getPositionEl().dom.parentNode.offsetWidth;
+ },
+
+ /**
+ * @private
+ * Called at the end of onLayout. At this point the Toolbar has already been resized, so we need
+ * to fit the items into the available width. We add up the width required by all of the items in
+ * the toolbar - if we don't have enough space we hide the extra items and render the expand menu
+ * trigger.
+ * @param {Ext.Element} target The Element the Toolbar is currently laid out within
+ */
+ fitToSize : function(target) {
+ if (this.container.enableOverflow === false) {
+ return;
+ }
+
+ var width = target.dom.clientWidth,
+ tableWidth = target.dom.firstChild.offsetWidth,
+ clipWidth = width - this.triggerWidth,
+ lastWidth = this.lastWidth || 0,
+
+ hiddenItems = this.hiddenItems,
+ hasHiddens = hiddenItems.length != 0,
+ isLarger = width >= lastWidth;
+
+ this.lastWidth = width;
+
+ if (tableWidth > width || (hasHiddens && isLarger)) {
+ var items = this.container.items.items,
+ len = items.length,
+ loopWidth = 0,
+ item;
+
+ for (var i = 0; i < len; i++) {
+ item = items[i];
+
+ if (!item.isFill) {
+ loopWidth += this.getItemWidth(item);
+ if (loopWidth > clipWidth) {
+ if (!(item.hidden || item.xtbHidden)) {
+ this.hideItem(item);
+ }
+ } else if (item.xtbHidden) {
+ this.unhideItem(item);
+ }
+ }
+ }
+ }
+
+ //test for number of hidden items again here because they may have changed above
+ hasHiddens = hiddenItems.length != 0;
+
+ if (hasHiddens) {
+ this.initMore();
+
+ if (!this.lastOverflow) {
+ this.container.fireEvent('overflowchange', this.container, true);
+ this.lastOverflow = true;
+ }
+ } else if (this.more) {
+ this.clearMenu();
+ this.more.destroy();
+ delete this.more;
+
+ if (this.lastOverflow) {
+ this.container.fireEvent('overflowchange', this.container, false);
+ this.lastOverflow = false;
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Returns a menu config for a given component. This config is used to create a menu item
+ * to be added to the expander menu
+ * @param {Ext.Component} component The component to create the config for
+ * @param {Boolean} hideOnClick Passed through to the menu item
+ */
+ createMenuConfig : function(component, hideOnClick){
+ var config = Ext.apply({}, component.initialConfig),
+ group = component.toggleGroup;
+
+ Ext.copyTo(config, component, [
+ 'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
+ ]);
+
+ Ext.apply(config, {
+ text : component.overflowText || component.text,
+ hideOnClick: hideOnClick
+ });
+
+ if (group || component.enableToggle) {
+ Ext.apply(config, {
+ group : group,
+ checked: component.pressed,
+ listeners: {
+ checkchange: function(item, checked){
+ component.toggle(checked);
+ }
+ }
+ });
+ }
+
+ delete config.ownerCt;
+ delete config.xtype;
+ delete config.id;
+
+ return config;
+ },
+
+ /**
+ * @private
+ * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
+ * @param {Ext.menu.Menu} menu The menu to add to
+ * @param {Ext.Component} component The component to add
+ */
+ addComponentToMenu : function(menu, component) {
+ if (component instanceof Ext.Toolbar.Separator) {
+ menu.add('-');
+
+ } else if (Ext.isFunction(component.isXType)) {
+ if (component.isXType('splitbutton')) {
+ menu.add(this.createMenuConfig(component, true));
+
+ } else if (component.isXType('button')) {
+ menu.add(this.createMenuConfig(component, !component.menu));
+
+ } else if (component.isXType('buttongroup')) {
+ component.items.each(function(item){
+ this.addComponentToMenu(menu, item);
+ }, this);
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
+ * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
+ */
+ clearMenu : function(){
+ var menu = this.moreMenu;
+ if (menu && menu.items) {
+ menu.items.each(function(item){
+ delete item.menu;
+ });
+ }
+ },
+
+ /**
+ * @private
+ * Called before the expand menu is shown, this rebuilds the menu since it was last shown because
+ * it is possible that the items hidden due to space limitations on the Toolbar have changed since.
+ * @param {Ext.menu.Menu} m The menu
+ */
+ beforeMoreShow : function(menu) {
+ var items = this.container.items.items,
+ len = items.length,
+ item,
+ prev;
+
+ var needsSep = function(group, item){
+ return group.isXType('buttongroup') && !(item instanceof Ext.Toolbar.Separator);
+ };
+
+ this.clearMenu();
+ menu.removeAll();
+ for (var i = 0; i < len; i++) {
+ item = items[i];
+ if (item.xtbHidden) {
+ if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
+ menu.add('-');
+ }
+ this.addComponentToMenu(menu, item);
+ prev = item;
+ }
+ }
+
+ // put something so the menu isn't empty if no compatible items found
+ if (menu.items.length < 1) {
+ menu.add(this.noItemsMenuText);
+ }
+ },
+
+ /**
+ * @private
+ * Creates the expand trigger and menu, adding them to the at the extreme right of the
+ * Toolbar table
+ */
+ initMore : function(){
+ if (!this.more) {
+ /**
+ * @private
+ * @property moreMenu
+ * @type Ext.menu.Menu
+ * The expand menu - holds items for every Toolbar item that cannot be shown
+ * because the Toolbar is currently not wide enough.
+ */
+ this.moreMenu = new Ext.menu.Menu({
+ ownerCt : this.container,
+ listeners: {
+ beforeshow: this.beforeMoreShow,
+ scope: this
+ }
+ });
+
+ /**
+ * @private
+ * @property more
+ * @type Ext.Button
+ * The expand button which triggers the overflow menu to be shown
+ */
+ this.more = new Ext.Button({
+ iconCls: 'x-toolbar-more-icon',
+ cls : 'x-toolbar-more',
+ menu : this.moreMenu,
+ ownerCt: this.container
+ });
+
+ var td = this.insertCell(this.more, this.extrasTr, 100);
+ this.more.render(td);
+ }
+ },
+
+ destroy : function(){
+ Ext.destroy(this.more, this.moreMenu);
+ delete this.leftTr;
+ delete this.rightTr;
+ delete this.extrasTr;
+ Ext.layout.ToolbarLayout.superclass.destroy.call(this);
+ }
+});
+
+Ext.Container.LAYOUTS.toolbar = Ext.layout.ToolbarLayout;
+/**
+ * @class Ext.layout.MenuLayout
+ * @extends Ext.layout.ContainerLayout
+ * Layout manager used by {@link Ext.menu.Menu}. Generally this class should not need to be used directly.
+ */
+ Ext.layout.MenuLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ monitorResize : true,
+
+ type: 'menu',
+
+ setContainer : function(ct){
+ this.monitorResize = !ct.floating;
+ // This event is only fired by the menu in IE, used so we don't couple
+ // the menu with the layout.
+ ct.on('autosize', this.doAutoSize, this);
+ Ext.layout.MenuLayout.superclass.setContainer.call(this, ct);
+ },
+
+ renderItem : function(c, position, target){
+ if (!this.itemTpl) {
+ this.itemTpl = Ext.layout.MenuLayout.prototype.itemTpl = new Ext.XTemplate(
+ '- ',
+ '
',
+ '',
+ ' ',
+ ' '
+ );
+ }
+
+ if(c && !c.rendered){
+ if(Ext.isNumber(position)){
+ position = target.dom.childNodes[position];
+ }
+ var a = this.getItemArgs(c);
+
+// The Component's positionEl is the - it is rendered into
+ c.render(c.positionEl = position ?
+ this.itemTpl.insertBefore(position, a, true) :
+ this.itemTpl.append(target, a, true));
+
+// Link the containing
- to the item.
+ c.positionEl.menuItemId = c.getItemId();
+
+// If rendering a regular Component, and it needs an icon,
+// move the Component rightwards.
+ if (!a.isMenuItem && a.needsIcon) {
+ c.positionEl.addClass('x-menu-list-item-indent');
+ }
+ this.configureItem(c);
+ }else if(c && !this.isValidParent(c, target)){
+ if(Ext.isNumber(position)){
+ position = target.dom.childNodes[position];
+ }
+ target.dom.insertBefore(c.getActionEl().dom, position || null);
+ }
+ },
+
+ getItemArgs : function(c) {
+ var isMenuItem = c instanceof Ext.menu.Item,
+ canHaveIcon = !(isMenuItem || c instanceof Ext.menu.Separator);
+
+ return {
+ isMenuItem: isMenuItem,
+ needsIcon: canHaveIcon && (c.icon || c.iconCls),
+ icon: c.icon || Ext.BLANK_IMAGE_URL,
+ iconCls: 'x-menu-item-icon ' + (c.iconCls || ''),
+ itemId: 'x-menu-el-' + c.id,
+ itemCls: 'x-menu-list-item ',
+ altText: c.altText || ''
+ };
+ },
+
+ // Valid if the Component is in a
- which is part of our target
+ isValidParent : function(c, target) {
+ return c.el.up('li.x-menu-list-item', 5).dom.parentNode === (target.dom || target);
+ },
+
+ onLayout : function(ct, target){
+ Ext.layout.MenuLayout.superclass.onLayout.call(this, ct, target);
+ this.doAutoSize();
+ },
+
+ doAutoSize : function(){
+ var ct = this.container, w = ct.width;
+ if(ct.floating){
+ if(w){
+ ct.setWidth(w);
+ }else if(Ext.isIE){
+ ct.setWidth(Ext.isStrict && (Ext.isIE7 || Ext.isIE8) ? 'auto' : ct.minWidth);
+ var el = ct.getEl(), t = el.dom.offsetWidth; // force recalc
+ ct.setWidth(ct.getLayoutTarget().getWidth() + el.getFrameWidth('lr'));
+ }
+ }
+ }
+});
+Ext.Container.LAYOUTS['menu'] = Ext.layout.MenuLayout;
+/**
+ * @class Ext.Viewport
+ * @extends Ext.Container
+ * A specialized container representing the viewable application area (the browser viewport).
+ * The Viewport renders itself to the document body, and automatically sizes itself to the size of
+ * the browser viewport and manages window resizing. There may only be one Viewport created
+ * in a page. Inner layouts are available by virtue of the fact that all {@link Ext.Panel Panel}s
+ * added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add}
+ * method of any of its child Panels may themselves have a layout.
+ * The Viewport does not provide scrolling, so child Panels within the Viewport should provide
+ * for scrolling if needed using the {@link #autoScroll} config.
+ * An example showing a classic application border layout:
+new Ext.Viewport({
+ layout: 'border',
+ items: [{
+ region: 'north',
+ html: '<h1 class="x-panel-header">Page Title</h1>',
+ autoHeight: true,
+ border: false,
+ margins: '0 0 5 0'
+ }, {
+ region: 'west',
+ collapsible: true,
+ title: 'Navigation',
+ width: 200
+ // the west region might typically utilize a {@link Ext.tree.TreePanel TreePanel} or a Panel with {@link Ext.layout.AccordionLayout Accordion layout}
+ }, {
+ region: 'south',
+ title: 'Title for Panel',
+ collapsible: true,
+ html: 'Information goes here',
+ split: true,
+ height: 100,
+ minHeight: 100
+ }, {
+ region: 'east',
+ title: 'Title for the Grid Panel',
+ collapsible: true,
+ split: true,
+ width: 200,
+ xtype: 'grid',
+ // remaining grid configuration not shown ...
+ // notice that the GridPanel is added directly as the region
+ // it is not "overnested" inside another Panel
+ }, {
+ region: 'center',
+ xtype: 'tabpanel', // TabPanel itself has no title
+ items: {
+ title: 'Default Tab',
+ html: 'The first tab\'s content. Others may be added dynamically'
+ }
+ }]
+});
+
+ * @constructor
+ * Create a new Viewport
+ * @param {Object} config The config object
+ * @xtype viewport
+ */
+Ext.Viewport = Ext.extend(Ext.Container, {
+ /*
+ * Privatize config options which, if used, would interfere with the
+ * correct operation of the Viewport as the sole manager of the
+ * layout of the document body.
+ */
+ /**
+ * @cfg {Mixed} applyTo @hide
+ */
+ /**
+ * @cfg {Boolean} allowDomMove @hide
+ */
+ /**
+ * @cfg {Boolean} hideParent @hide
+ */
+ /**
+ * @cfg {Mixed} renderTo @hide
+ */
+ /**
+ * @cfg {Boolean} hideParent @hide
+ */
+ /**
+ * @cfg {Number} height @hide
+ */
+ /**
+ * @cfg {Number} width @hide
+ */
+ /**
+ * @cfg {Boolean} autoHeight @hide
+ */
+ /**
+ * @cfg {Boolean} autoWidth @hide
+ */
+ /**
+ * @cfg {Boolean} deferHeight @hide
+ */
+ /**
+ * @cfg {Boolean} monitorResize @hide
+ */
+
+ initComponent : function() {
+ Ext.Viewport.superclass.initComponent.call(this);
+ document.getElementsByTagName('html')[0].className += ' x-viewport';
+ this.el = Ext.getBody();
+ this.el.setHeight = Ext.emptyFn;
+ this.el.setWidth = Ext.emptyFn;
+ this.el.setSize = Ext.emptyFn;
+ this.el.dom.scroll = 'no';
+ this.allowDomMove = false;
+ this.autoWidth = true;
+ this.autoHeight = true;
+ Ext.EventManager.onWindowResize(this.fireResize, this);
+ this.renderTo = this.el;
+ },
+
+ fireResize : function(w, h){
+ this.fireEvent('resize', this, w, h, w, h);
+ }
+});
+Ext.reg('viewport', Ext.Viewport);
+/**
+ * @class Ext.Panel
+ * @extends Ext.Container
+ * Panel is a container that has specific functionality and structural components that make
+ * it the perfect building block for application-oriented user interfaces.
+ * Panels are, by virtue of their inheritance from {@link Ext.Container}, capable
+ * of being configured with a {@link Ext.Container#layout layout}, and containing child Components.
+ * When either specifying child {@link Ext.Component#items items} of a Panel, or dynamically {@link Ext.Container#add adding} Components
+ * to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether
+ * those child elements need to be sized using one of Ext's built-in {@link Ext.Container#layout layout}
schemes. By
+ * default, Panels use the {@link Ext.layout.ContainerLayout ContainerLayout} scheme. This simply renders
+ * child components, appending them one after the other inside the Container, and does not apply any sizing
+ * at all.
+ * A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate
+ * {@link #header}, {@link #footer} and {@link #body} sections (see {@link #frame} for additional
+ * information).
+ * Panel also provides built-in {@link #collapsible expandable and collapsible behavior}, along with
+ * a variety of {@link #tools prebuilt tool buttons} that can be wired up to provide other customized
+ * behavior. Panels can be easily dropped into any {@link Ext.Container Container} or layout, and the
+ * layout and rendering pipeline is {@link Ext.Container#add completely managed by the framework}.
+ * @constructor
+ * @param {Object} config The config object
+ * @xtype panel
+ */
+Ext.Panel = Ext.extend(Ext.Container, {
+ /**
+ * The Panel's header {@link Ext.Element Element}. Read-only.
+ * This Element is used to house the {@link #title} and {@link #tools}
+ *
Note: see the Note for {@link Ext.Component#el el}
also.
+ * @type Ext.Element
+ * @property header
+ */
+ /**
+ * The Panel's body {@link Ext.Element Element} which may be used to contain HTML content.
+ * The content may be specified in the {@link #html} config, or it may be loaded using the
+ * {@link autoLoad} config, or through the Panel's {@link #getUpdater Updater}. Read-only.
+ * If this is used to load visible HTML elements in either way, then
+ * the Panel may not be used as a Layout for hosting nested Panels.
+ * If this Panel is intended to be used as the host of a Layout (See {@link #layout}
+ * then the body Element must not be loaded or changed - it is under the control
+ * of the Panel's Layout.
+ *
Note: see the Note for {@link Ext.Component#el el}
also.
+ * @type Ext.Element
+ * @property body
+ */
+ /**
+ * The Panel's bwrap {@link Ext.Element Element} used to contain other Panel elements
+ * (tbar, body, bbar, footer). See {@link #bodyCfg}. Read-only.
+ * @type Ext.Element
+ * @property bwrap
+ */
+ /**
+ * True if this panel is collapsed. Read-only.
+ * @type Boolean
+ * @property collapsed
+ */
+ /**
+ * @cfg {Object} bodyCfg
+ * A {@link Ext.DomHelper DomHelper} element specification object may be specified for any
+ * Panel Element.
+ * By default, the Default element in the table below will be used for the html markup to
+ * create a child element with the commensurate Default class name (baseCls
will be
+ * replaced by {@link #baseCls}
):
+ *
+ * Panel Default Default Custom Additional Additional
+ * Element element class element class style
+ * ======== ========================== ========= ============== ===========
+ * {@link #header} div {@link #baseCls}+'-header' {@link #headerCfg} headerCssClass headerStyle
+ * {@link #bwrap} div {@link #baseCls}+'-bwrap' {@link #bwrapCfg} bwrapCssClass bwrapStyle
+ * + tbar div {@link #baseCls}+'-tbar' {@link #tbarCfg} tbarCssClass tbarStyle
+ * + {@link #body} div {@link #baseCls}+'-body' {@link #bodyCfg} {@link #bodyCssClass} {@link #bodyStyle}
+ * + bbar div {@link #baseCls}+'-bbar' {@link #bbarCfg} bbarCssClass bbarStyle
+ * + {@link #footer} div {@link #baseCls}+'-footer' {@link #footerCfg} footerCssClass footerStyle
+ *
+ * Configuring a Custom element may be used, for example, to force the {@link #body} Element
+ * to use a different form of markup than is created by default. An example of this might be
+ * to {@link Ext.Element#createChild create a child} Panel containing a custom content, such as
+ * a header, or forcing centering of all Panel content by having the body be a <center>
+ * element:
+ *
+new Ext.Panel({
+ title: 'Message Title',
+ renderTo: Ext.getBody(),
+ width: 200, height: 130,
+ bodyCfg: {
+ tag: 'center',
+ cls: 'x-panel-body', // Default class not applied if Custom element specified
+ html: 'Message'
+ },
+ footerCfg: {
+ tag: 'h2',
+ cls: 'x-panel-footer', // same as the Default class
+ html: 'footer html'
+ },
+ footerCssClass: 'custom-footer', // additional css class, see {@link Ext.element#addClass addClass}
+ footerStyle: 'background-color:red' // see {@link #bodyStyle}
+});
+ *
+ * The example above also explicitly creates a {@link #footer}
with custom markup and
+ * styling applied.
+ */
+ /**
+ * @cfg {Object} headerCfg
+ * A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
+ * of this Panel's {@link #header} Element. See {@link #bodyCfg}
also.
+ */
+ /**
+ * @cfg {Object} bwrapCfg
+ * A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
+ * of this Panel's {@link #bwrap} Element. See {@link #bodyCfg}
also.
+ */
+ /**
+ * @cfg {Object} tbarCfg
+ * A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
+ * of this Panel's {@link #tbar} Element. See {@link #bodyCfg}
also.
+ */
+ /**
+ * @cfg {Object} bbarCfg
+ * A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
+ * of this Panel's {@link #bbar} Element. See {@link #bodyCfg}
also.
+ */
+ /**
+ * @cfg {Object} footerCfg
+ * A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
+ * of this Panel's {@link #footer} Element. See {@link #bodyCfg}
also.
+ */
+ /**
+ * @cfg {Boolean} closable
+ * Panels themselves do not directly support being closed, but some Panel subclasses do (like
+ * {@link Ext.Window}) or a Panel Class within an {@link Ext.TabPanel}. Specify true
+ * to enable closing in such situations. Defaults to false
.
+ */
+ /**
+ * The Panel's footer {@link Ext.Element Element}. Read-only.
+ * This Element is used to house the Panel's {@link #buttons}
or {@link #fbar}
.
+ *
Note: see the Note for {@link Ext.Component#el el}
also.
+ * @type Ext.Element
+ * @property footer
+ */
+ /**
+ * @cfg {Mixed} applyTo
+ * The id of the node, a DOM node or an existing Element corresponding to a DIV that is already present in
+ * the document that specifies some panel-specific structural markup. When applyTo
is used,
+ * constituent parts of the panel can be specified by CSS class name within the main element, and the panel
+ * will automatically create those components from that markup. Any required components not specified in the
+ * markup will be autogenerated if necessary.
+ * The following class names are supported (baseCls will be replaced by {@link #baseCls}):
+ * - baseCls + '-header'
+ * - baseCls + '-header-text'
+ * - baseCls + '-bwrap'
+ * - baseCls + '-tbar'
+ * - baseCls + '-body'
+ * - baseCls + '-bbar'
+ * - baseCls + '-footer'
+ * Using this config, a call to render() is not required. If applyTo is specified, any value passed for
+ * {@link #renderTo} will be ignored and the target element's parent node will automatically be used as the
+ * panel's container.
+ */
+ /**
+ * @cfg {Object/Array} tbar
+ * The top toolbar of the panel. This can be a {@link Ext.Toolbar} object, a toolbar config, or an array of
+ * buttons/button configs to be added to the toolbar. Note that this is not available as a property after render.
+ * To access the top toolbar after render, use {@link #getTopToolbar}.
+ * Note: Although a Toolbar may contain Field components, these will not be updated by a load
+ * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and
+ * so are not scanned to collect form items. However, the values will be submitted because form
+ * submission parameters are collected from the DOM tree.
+ */
+ /**
+ * @cfg {Object/Array} bbar
+ * The bottom toolbar of the panel. This can be a {@link Ext.Toolbar} object, a toolbar config, or an array of
+ * buttons/button configs to be added to the toolbar. Note that this is not available as a property after render.
+ * To access the bottom toolbar after render, use {@link #getBottomToolbar}.
+ * Note: Although a Toolbar may contain Field components, these will not be updated by a load
+ * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and
+ * so are not scanned to collect form items. However, the values will be submitted because form
+ * submission parameters are collected from the DOM tree.
+ */
+ /** @cfg {Object/Array} fbar
+ * A {@link Ext.Toolbar Toolbar} object, a Toolbar config, or an array of
+ * {@link Ext.Button Button}s/{@link Ext.Button Button} configs, describing a {@link Ext.Toolbar Toolbar} to be rendered into this Panel's footer element.
+ * After render, the fbar
property will be an {@link Ext.Toolbar Toolbar} instance.
+ * If {@link #buttons}
are specified, they will supersede the fbar
configuration property.
+ * The Panel's {@link #buttonAlign}
configuration affects the layout of these items, for example:
+ *
+var w = new Ext.Window({
+ height: 250,
+ width: 500,
+ bbar: new Ext.Toolbar({
+ items: [{
+ text: 'bbar Left'
+ },'->',{
+ text: 'bbar Right'
+ }]
+ }),
+ {@link #buttonAlign}: 'left', // anything but 'center' or 'right' and you can use '-', and '->'
+ // to control the alignment of fbar items
+ fbar: [{
+ text: 'fbar Left'
+ },'->',{
+ text: 'fbar Right'
+ }]
+}).show();
+ *
+ * Note: Although a Toolbar may contain Field components, these will not be updated by a load
+ * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and
+ * so are not scanned to collect form items. However, the values will be submitted because form
+ * submission parameters are collected from the DOM tree.
+ */
+ /**
+ * @cfg {Boolean} header
+ * true
to create the Panel's header element explicitly, false
to skip creating
+ * it. If a {@link #title}
is set the header will be created automatically, otherwise it will not.
+ * If a {@link #title}
is set but header
is explicitly set to false
, the header
+ * will not be rendered.
+ */
+ /**
+ * @cfg {Boolean} footer
+ * true
to create the footer element explicitly, false to skip creating it. The footer
+ * will be created automatically if {@link #buttons}
or a {@link #fbar}
have
+ * been configured. See {@link #bodyCfg}
for an example.
+ */
+ /**
+ * @cfg {String} title
+ * The title text to be used as innerHTML (html tags are accepted) to display in the panel
+ * {@link #header}
(defaults to ''). When a title
is specified the
+ * {@link #header}
element will automatically be created and displayed unless
+ * {@link #header} is explicitly set to false
. If you do not want to specify a
+ * title
at config time, but you may want one later, you must either specify a non-empty
+ * title
(a blank space ' ' will do) or header:true
so that the container
+ * element will get created.
+ */
+ /**
+ * @cfg {Array} buttons
+ * buttons
will be used as {@link Ext.Container#items items}
for the toolbar in
+ * the footer ({@link #fbar}
). Typically the value of this configuration property will be
+ * an array of {@link Ext.Button}s or {@link Ext.Button} configuration objects.
+ * If an item is configured with minWidth
or the Panel is configured with minButtonWidth
,
+ * that width will be applied to the item.
+ */
+ /**
+ * @cfg {Object/String/Function} autoLoad
+ * A valid url spec according to the Updater {@link Ext.Updater#update} method.
+ * If autoLoad is not null, the panel will attempt to load its contents
+ * immediately upon render.
+ * The URL will become the default URL for this panel's {@link #body} element,
+ * so it may be {@link Ext.Element#refresh refresh}ed at any time.
+ */
+ /**
+ * @cfg {Boolean} frame
+ * false
by default to render with plain 1px square borders. true
to render with
+ * 9 elements, complete with custom rounded corners (also see {@link Ext.Element#boxWrap}).
+ * The template generated for each condition is depicted below:
+ *
+// frame = false
+<div id="developer-specified-id-goes-here" class="x-panel">
+
+ <div class="x-panel-header"><span class="x-panel-header-text">Title: (frame:false)</span></div>
+
+ <div class="x-panel-bwrap">
+ <div class="x-panel-body"><p>html value goes here</p></div>
+ </div>
+</div>
+
+// frame = true (create 9 elements)
+<div id="developer-specified-id-goes-here" class="x-panel">
+ <div class="x-panel-tl"><div class="x-panel-tr"><div class="x-panel-tc">
+ <div class="x-panel-header"><span class="x-panel-header-text">Title: (frame:true)</span></div>
+ </div></div></div>
+
+ <div class="x-panel-bwrap">
+ <div class="x-panel-ml"><div class="x-panel-mr"><div class="x-panel-mc">
+ <div class="x-panel-body"><p>html value goes here</p></div>
+ </div></div></div>
+
+ <div class="x-panel-bl"><div class="x-panel-br"><div class="x-panel-bc"/>
+ </div></div></div>
+</div>
+ *
+ */
+ /**
+ * @cfg {Boolean} border
+ * True to display the borders of the panel's body element, false to hide them (defaults to true). By default,
+ * the border is a 2px wide inset border, but this can be further altered by setting {@link #bodyBorder} to false.
+ */
+ /**
+ * @cfg {Boolean} bodyBorder
+ * True to display an interior border on the body element of the panel, false to hide it (defaults to true).
+ * This only applies when {@link #border} == true. If border == true and bodyBorder == false, the border will display
+ * as a 1px wide inset border, giving the entire body element an inset appearance.
+ */
+ /**
+ * @cfg {String/Object/Function} bodyCssClass
+ * Additional css class selector to be applied to the {@link #body} element in the format expected by
+ * {@link Ext.Element#addClass} (defaults to null). See {@link #bodyCfg}.
+ */
+ /**
+ * @cfg {String/Object/Function} bodyStyle
+ * Custom CSS styles to be applied to the {@link #body} element in the format expected by
+ * {@link Ext.Element#applyStyles} (defaults to null). See {@link #bodyCfg}.
+ */
+ /**
+ * @cfg {String} iconCls
+ * The CSS class selector that specifies a background image to be used as the header icon (defaults to '').
+ * An example of specifying a custom icon class would be something like:
+ *
+// specify the property in the config for the class:
+ ...
+ iconCls: 'my-icon'
+
+// css class that specifies background image to be used as the icon image:
+.my-icon { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
+
+ */
+ /**
+ * @cfg {Boolean} collapsible
+ * True to make the panel collapsible and have the expand/collapse toggle button automatically rendered into
+ * the header tool button area, false to keep the panel statically sized with no button (defaults to false).
+ */
+ /**
+ * @cfg {Array} tools
+ * An array of tool button configs to be added to the header tool area. When rendered, each tool is
+ * stored as an {@link Ext.Element Element} referenced by a public property called tools.<tool-type>
+ * Each tool config may contain the following properties:
+ *
+ * - id : StringRequired. The type
+ * of tool to create. By default, this assigns a CSS class of the form
x-tool-<tool-type>
to the
+ * resulting tool Element. Ext provides CSS rules, and an icon sprite containing images for the tool types listed below.
+ * The developer may implement custom tools by supplying alternate CSS rules and background images:
+ *
+ * toggle
(Created by default when {@link #collapsible} is true
)
+ * close
+ * minimize
+ * maximize
+ * restore
+ * gear
+ * pin
+ * unpin
+ * right
+ * left
+ * up
+ * down
+ * refresh
+ * minus
+ * plus
+ * help
+ * search
+ * save
+ * print
+ *
+ * - handler : FunctionRequired. The function to
+ * call when clicked. Arguments passed are:
+ * - event : Ext.EventObjectThe click event.
+ * - toolEl : Ext.ElementThe tool Element.
+ * - panel : Ext.PanelThe host Panel
+ * - tc : ObjectThe tool configuration object
+ *
+ * - stopEvent : BooleanDefaults to true. Specify as false to allow click event to propagate.
+ * - scope : ObjectThe scope in which to call the handler.
+ * - qtip : String/ObjectA tip string, or
+ * a config argument to {@link Ext.QuickTip#register}
+ * - hidden : BooleanTrue to initially render hidden.
+ * - on : ObjectA listener config object specifiying
+ * event listeners in the format of an argument to {@link #addListener}
+ *
+ * Note that, apart from the toggle tool which is provided when a panel is collapsible, these
+ * tools only provide the visual button. Any required functionality must be provided by adding
+ * handlers that implement the necessary behavior.
+ * Example usage:
+ *
+tools:[{
+ id:'refresh',
+ qtip: 'Refresh form Data',
+ // hidden:true,
+ handler: function(event, toolEl, panel){
+ // refresh logic
+ }
+},
+{
+ id:'help',
+ qtip: 'Get Help',
+ handler: function(event, toolEl, panel){
+ // whatever
+ }
+}]
+
+ * For the custom id of 'help'
define two relevant css classes with a link to
+ * a 15x15 image:
+ *
+.x-tool-help {background-image: url(images/help.png);}
+.x-tool-help-over {background-image: url(images/help_over.png);}
+// if using an image sprite:
+.x-tool-help {background-image: url(images/help.png) no-repeat 0 0;}
+.x-tool-help-over {background-position:-15px 0;}
+
*/
/**
- * @cfg {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 {Ext.Template/Ext.XTemplate} toolTemplate
+ * A Template used to create {@link #tools} in the {@link #header} Element. Defaults to:
+new Ext.Template('<div class="x-tool x-tool-{id}"> </div>')
+ * This may may be overridden to provide a custom DOM structure for tools based upon a more
+ * complex XTemplate. The template's data is a single tool configuration object (Not the entire Array)
+ * as specified in {@link #tools}. In the following example an <a> tag is used to provide a
+ * visual indication when hovering over the tool:
+var win = new Ext.Window({
+ tools: [{
+ id: 'download',
+ href: '/MyPdfDoc.pdf'
+ }],
+ toolTemplate: new Ext.XTemplate(
+ '<tpl if="id==\'download\'">',
+ '<a class="x-tool x-tool-pdf" href="{href}"></a>',
+ '</tpl>',
+ '<tpl if="id!=\'download\'">',
+ '<div class="x-tool x-tool-{id}"> </div>',
+ '</tpl>'
+ ),
+ width:500,
+ height:300,
+ closeAction:'hide'
+});
+ * Note that the CSS class 'x-tool-pdf' should have an associated style rule which provides an
+ * appropriate background image, something like:
+
+ a.x-tool-pdf {background-image: url(../shared/extjs/images/pdf.gif)!important;}
+
*/
/**
- * @cfg {Object} bwrapCfg
- * A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
- * of this Panel's {@link #bwrap} Element. See {@link #bodyCfg}
also.
+ * @cfg {Boolean} hideCollapseTool
+ * true
to hide the expand/collapse toggle button when {@link #collapsible} == true
,
+ * false
to display it (defaults to false
).
*/
/**
- * @cfg {Object} tbarCfg
- * A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
- * of this Panel's {@link #tbar} Element. See {@link #bodyCfg}
also.
+ * @cfg {Boolean} titleCollapse
+ * true
to allow expanding and collapsing the panel (when {@link #collapsible} = true
)
+ * by clicking anywhere in the header bar, false
) to allow it only by clicking to tool button
+ * (defaults to false
)). If this panel is a child item of a border layout also see the
+ * {@link Ext.layout.BorderLayout.Region BorderLayout.Region}
+ * {@link Ext.layout.BorderLayout.Region#floatable floatable}
config option.
*/
+
/**
- * @cfg {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 {Mixed} floating
+ * This property is used to configure the underlying {@link Ext.Layer}. Acceptable values for this
+ * configuration property are:
+ * false
: Default.Display the panel inline where it is
+ * rendered.
+ * true
: Float the panel (absolute position it with automatic
+ * shimming and shadow).
+ * Setting floating to true will create an Ext.Layer for this panel and display the
+ * panel at negative offsets so that it is hidden.
+ * Since the panel will be absolute positioned, the position must be set explicitly
+ * after render (e.g., myPanel.setPosition(100,100);
).
+ * Note: when floating a panel you should always assign a fixed width,
+ * otherwise it will be auto width and will expand to fill to the right edge of the viewport.
+ *
+ * {@link Ext.Layer object}
: The specified object will be used
+ * as the configuration object for the {@link Ext.Layer} that will be created.
+ *
*/
/**
- * @cfg {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/String} shadow
+ * true
(or a valid Ext.Shadow {@link Ext.Shadow#mode} value) to display a shadow behind the
+ * panel, false
to display no shadow (defaults to 'sides'
). Note that this option
+ * only applies when {@link #floating} = true
.
*/
/**
- * @cfg {Boolean} closable
- * Panels themselves do not directly support being closed, but some Panel subclasses do (like
- * {@link Ext.Window}) or a Panel Class within an {@link Ext.TabPanel}. Specify true
- * to enable closing in such situations. Defaults to false
.
+ * @cfg {Number} shadowOffset
+ * The number of pixels to offset the shadow if displayed (defaults to 4
). Note that this
+ * option only applies when {@link #floating} = true
.
*/
/**
- * The Panel's footer {@link Ext.Element Element}. Read-only.
- * This Element is used to house the Panel's {@link #buttons}
or {@link #fbar}
.
- *
Note: see the Note for {@link Ext.Component#el el}
also.
- * @type Ext.Element
- * @property footer
+ * @cfg {Boolean} shim
+ * false
to disable the iframe shim in browsers which need one (defaults to true
).
+ * Note that this option only applies when {@link #floating} = true
.
*/
/**
- * @cfg {Mixed} applyTo
- * The id of the node, a DOM node or an existing Element corresponding to a DIV that is already present in
- * the document that specifies some panel-specific structural markup. When applyTo
is used,
- * constituent parts of the panel can be specified by CSS class name within the main element, and the panel
- * will automatically create those components from that markup. Any required components not specified in the
- * markup will be autogenerated if necessary.
- * The following class names are supported (baseCls will be replaced by {@link #baseCls}):
- * - baseCls + '-header'
- * - baseCls + '-header-text'
- * - baseCls + '-bwrap'
- * - baseCls + '-tbar'
- * - baseCls + '-body'
- * - baseCls + '-bbar'
- * - baseCls + '-footer'
- * Using this config, a call to render() is not required. If applyTo is specified, any value passed for
- * {@link #renderTo} will be ignored and the target element's parent node will automatically be used as the
- * panel's container.
+ * @cfg {Object/Array} keys
+ * A {@link Ext.KeyMap} config object (in the format expected by {@link Ext.KeyMap#addBinding}
+ * used to assign custom key handling to this panel (defaults to null
).
*/
/**
- * @cfg {Object/Array} tbar
- * The top toolbar of the panel. This can be a {@link Ext.Toolbar} object, a toolbar config, or an array of
- * buttons/button configs to be added to the toolbar. Note that this is not available as a property after render.
- * To access the top toolbar after render, use {@link #getTopToolbar}.
- * Note: Although a Toolbar may contain Field components, these will not be updated by a load
- * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and
- * so are not scanned to collect form items. However, the values will be submitted because form
- * submission parameters are collected from the DOM tree.
+ * @cfg {Boolean/Object} draggable
+ * true
to enable dragging of this Panel (defaults to false
).
+ * For custom drag/drop implementations, an Ext.Panel.DD config could also be passed
+ * in this config instead of true
. Ext.Panel.DD is an internal, undocumented class which
+ * moves a proxy Element around in place of the Panel's element, but provides no other behaviour
+ * during dragging or on drop. It is a subclass of {@link Ext.dd.DragSource}, so behaviour may be
+ * added by implementing the interface methods of {@link Ext.dd.DragDrop} e.g.:
+ *
+new Ext.Panel({
+ title: 'Drag me',
+ x: 100,
+ y: 100,
+ renderTo: Ext.getBody(),
+ floating: true,
+ frame: true,
+ width: 400,
+ height: 200,
+ draggable: {
+// Config option of Ext.Panel.DD class.
+// It's a floating Panel, so do not show a placeholder proxy in the original position.
+ insertProxy: false,
+
+// Called for each mousemove event while dragging the DD object.
+ onDrag : function(e){
+// Record the x,y position of the drag proxy so that we can
+// position the Panel at end of drag.
+ var pel = this.proxy.getEl();
+ this.x = pel.getLeft(true);
+ this.y = pel.getTop(true);
+
+// Keep the Shadow aligned if there is one.
+ var s = this.panel.getEl().shadow;
+ if (s) {
+ s.realign(this.x, this.y, pel.getWidth(), pel.getHeight());
+ }
+ },
+
+// Called on the mouseup event.
+ endDrag : function(e){
+ this.panel.setPosition(this.x, this.y);
+ }
+ }
+}).show();
+
*/
/**
- * @cfg {Object/Array} bbar
- * The bottom toolbar of the panel. This can be a {@link Ext.Toolbar} object, a toolbar config, or an array of
- * buttons/button configs to be added to the toolbar. Note that this is not available as a property after render.
- * To access the bottom toolbar after render, use {@link #getBottomToolbar}.
- * Note: Although a Toolbar may contain Field components, these will not be updated by a load
- * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and
- * so are not scanned to collect form items. However, the values will be submitted because form
- * submission parameters are collected from the DOM tree.
- */
- /** @cfg {Object/Array} fbar
- * A {@link Ext.Toolbar Toolbar} object, a Toolbar config, or an array of
- * {@link Ext.Button Button}s/{@link Ext.Button Button} configs, describing a {@link Ext.Toolbar Toolbar} to be rendered into this Panel's footer element.
- * After render, the fbar
property will be an {@link Ext.Toolbar Toolbar} instance.
- * If {@link #buttons}
are specified, they will supersede the fbar
configuration property.
- * The Panel's {@link #buttonAlign}
configuration affects the layout of these items, for example:
+ * @cfg {Boolean} disabled
+ * Render this panel disabled (default is false
). An important note when using the disabled
+ * config on panels is that IE will often fail to initialize the disabled mask element correectly if
+ * the panel's layout has not yet completed by the time the Panel is disabled during the render process.
+ * If you experience this issue, you may need to instead use the {@link #afterlayout} event to initialize
+ * the disabled state:
*
-var w = new Ext.Window({
- height: 250,
- width: 500,
- bbar: new Ext.Toolbar({
- items: [{
- text: 'bbar Left'
- },'->',{
- text: 'bbar Right'
- }]
- }),
- {@link #buttonAlign}: 'left', // anything but 'center' or 'right' and you can use '-', and '->'
- // to control the alignment of fbar items
- fbar: [{
- text: 'fbar Left'
- },'->',{
- text: 'fbar Right'
- }]
-}).show();
- *
- * Note: Although a Toolbar may contain Field components, these will not be updated by a load
- * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and
- * so are not scanned to collect form items. However, the values will be submitted because form
- * submission parameters are collected from the DOM tree.
+new Ext.Panel({
+ ...
+ listeners: {
+ 'afterlayout': {
+ fn: function(p){
+ p.disable();
+ },
+ single: true // important, as many layouts can occur
+ }
+ }
+});
+
+ */
+ /**
+ * @cfg {Boolean} autoHeight
+ * true
to use height:'auto', false
to use fixed height (defaults to false
).
+ * Note: Setting autoHeight: true
means that the browser will manage the panel's height
+ * based on its contents, and that Ext will not manage it at all. If the panel is within a layout that
+ * manages dimensions (fit
, border
, etc.) then setting autoHeight: true
+ * can cause issues with scrolling and will not generally work as expected since the panel will take
+ * on the height of its contents rather than the height required by the Ext layout.
+ */
+
+
+ /**
+ * @cfg {String} baseCls
+ * The base CSS class to apply to this panel's element (defaults to 'x-panel'
).
+ * Another option available by default is to specify 'x-plain'
which strips all styling
+ * except for required attributes for Ext layouts to function (e.g. overflow:hidden).
+ * See {@link #unstyled}
also.
+ */
+ baseCls : 'x-panel',
+ /**
+ * @cfg {String} collapsedCls
+ * A CSS class to add to the panel's element after it has been collapsed (defaults to
+ * 'x-panel-collapsed'
).
+ */
+ collapsedCls : 'x-panel-collapsed',
+ /**
+ * @cfg {Boolean} maskDisabled
+ * true
to mask the panel when it is {@link #disabled}, false
to not mask it (defaults
+ * to true
). Either way, the panel will always tell its contained elements to disable themselves
+ * when it is disabled, but masking the panel can provide an additional visual cue that the panel is
+ * disabled.
+ */
+ maskDisabled : true,
+ /**
+ * @cfg {Boolean} animCollapse
+ * true
to animate the transition when the panel is collapsed, false
to skip the
+ * animation (defaults to true
if the {@link Ext.Fx} class is available, otherwise false
).
*/
+ animCollapse : Ext.enableFx,
/**
- * @cfg {Boolean} header
- * true
to create the Panel's header element explicitly, false
to skip creating
- * it. If a {@link #title}
is set the header will be created automatically, otherwise it will not.
- * If a {@link #title}
is set but header
is explicitly set to false
, the header
- * will not be rendered.
+ * @cfg {Boolean} headerAsText
+ * true
to display the panel {@link #title}
in the {@link #header}
,
+ * false
to hide it (defaults to true
).
*/
+ headerAsText : true,
/**
- * @cfg {Boolean} footer
- * true
to create the footer element explicitly, false to skip creating it. The footer
- * will be created automatically if {@link #buttons}
or a {@link #fbar}
have
- * been configured. See {@link #bodyCfg}
for an example.
+ * @cfg {String} buttonAlign
+ * The alignment of any {@link #buttons} added to this panel. Valid values are 'right'
,
+ * 'left'
and 'center'
(defaults to 'right'
).
*/
+ buttonAlign : 'right',
/**
- * @cfg {String} title
- * The title text to be used as innerHTML (html tags are accepted) to display in the panel
- * {@link #header}
(defaults to ''). When a title
is specified the
- * {@link #header}
element will automatically be created and displayed unless
- * {@link #header} is explicitly set to false
. If you do not want to specify a
- * title
at config time, but you may want one later, you must either specify a non-empty
- * title
(a blank space ' ' will do) or header:true
so that the container
- * element will get created.
+ * @cfg {Boolean} collapsed
+ * true
to render the panel collapsed, false
to render it expanded (defaults to
+ * false
).
*/
+ collapsed : false,
/**
- * @cfg {Array} buttons
- * buttons
will be used as {@link Ext.Container#items items}
for the toolbar in
- * the footer ({@link #fbar}
). Typically the value of this configuration property will be
- * an array of {@link Ext.Button}s or {@link Ext.Button} configuration objects.
- * If an item is configured with minWidth
or the Panel is configured with minButtonWidth
,
- * that width will be applied to the item.
+ * @cfg {Boolean} collapseFirst
+ * true
to make sure the collapse/expand toggle button always renders first (to the left of)
+ * any other tools in the panel's title bar, false
to render it last (defaults to true
).
*/
+ collapseFirst : true,
/**
- * @cfg {Object/String/Function} autoLoad
- * A valid url spec according to the Updater {@link Ext.Updater#update} method.
- * If autoLoad is not null, the panel will attempt to load its contents
- * immediately upon render.
- * The URL will become the default URL for this panel's {@link #body} element,
- * so it may be {@link Ext.Element#refresh refresh}ed at any time.
+ * @cfg {Number} minButtonWidth
+ * Minimum width in pixels of all {@link #buttons} in this panel (defaults to 75
)
*/
+ minButtonWidth : 75,
/**
- * @cfg {Boolean} frame
- * false
by default to render with plain 1px square borders. true
to render with
- * 9 elements, complete with custom rounded corners (also see {@link Ext.Element#boxWrap}).
- * The template generated for each condition is depicted below:
+ * @cfg {Boolean} unstyled
+ * Overrides the {@link #baseCls}
setting to {@link #baseCls} = 'x-plain'
which renders
+ * the panel unstyled except for required attributes for Ext layouts to function (e.g. overflow:hidden).
+ */
+ /**
+ * @cfg {String} elements
+ * A comma-delimited list of panel elements to initialize when the panel is rendered. Normally, this list will be
+ * generated automatically based on the items added to the panel at config time, but sometimes it might be useful to
+ * make sure a structural element is rendered even if not specified at config time (for example, you may want
+ * to add a button or toolbar dynamically after the panel has been rendered). Adding those elements to this
+ * list will allocate the required placeholders in the panel when it is rendered. Valid values are
+ * header
+ * tbar
(top bar)
+ * body
+ * bbar
(bottom bar)
+ * footer
+ *
+ * Defaults to 'body
'.
+ */
+ elements : 'body',
+ /**
+ * @cfg {Boolean} preventBodyReset
+ * Defaults to false
. When set to true
, an extra css class 'x-panel-normal'
+ * will be added to the panel's element, effectively applying css styles suggested by the W3C
+ * (see http://www.w3.org/TR/CSS21/sample.html) to the Panel's body element (not the header,
+ * footer, etc.).
+ */
+ preventBodyReset : false,
+
+ /**
+ * @cfg {Number/String} padding
+ * A shortcut for setting a padding style on the body element. The value can either be
+ * a number to be applied to all sides, or a normal css string describing padding.
+ * Defaults to undefined.
*
-// frame = false
-<div id="developer-specified-id-goes-here" class="x-panel">
+ */
+ padding: undefined,
+
+ /** @cfg {String} resizeEvent
+ * The event to listen to for resizing in layouts. Defaults to 'bodyresize'.
+ */
+ resizeEvent: 'bodyresize',
+
+ // protected - these could be used to customize the behavior of the window,
+ // but changing them would not be useful without further mofifications and
+ // could lead to unexpected or undesirable results.
+ toolTarget : 'header',
+ collapseEl : 'bwrap',
+ slideAnchor : 't',
+ disabledClass : '',
+
+ // private, notify box this class will handle heights
+ deferHeight : true,
+ // private
+ expandDefaults: {
+ duration : 0.25
+ },
+ // private
+ collapseDefaults : {
+ duration : 0.25
+ },
+
+ // private
+ initComponent : function(){
+ Ext.Panel.superclass.initComponent.call(this);
+
+ this.addEvents(
+ /**
+ * @event bodyresize
+ * Fires after the Panel has been resized.
+ * @param {Ext.Panel} p the Panel which has been resized.
+ * @param {Number} width The Panel body's new width.
+ * @param {Number} height The Panel body's new height.
+ */
+ 'bodyresize',
+ /**
+ * @event titlechange
+ * Fires after the Panel title has been {@link #title set} or {@link #setTitle changed}.
+ * @param {Ext.Panel} p the Panel which has had its title changed.
+ * @param {String} The new title.
+ */
+ 'titlechange',
+ /**
+ * @event iconchange
+ * Fires after the Panel icon class has been {@link #iconCls set} or {@link #setIconClass changed}.
+ * @param {Ext.Panel} p the Panel which has had its {@link #iconCls icon class} changed.
+ * @param {String} The new icon class.
+ * @param {String} The old icon class.
+ */
+ 'iconchange',
+ /**
+ * @event collapse
+ * Fires after the Panel has been collapsed.
+ * @param {Ext.Panel} p the Panel that has been collapsed.
+ */
+ 'collapse',
+ /**
+ * @event expand
+ * Fires after the Panel has been expanded.
+ * @param {Ext.Panel} p The Panel that has been expanded.
+ */
+ 'expand',
+ /**
+ * @event beforecollapse
+ * Fires before the Panel is collapsed. A handler can return false to cancel the collapse.
+ * @param {Ext.Panel} p the Panel being collapsed.
+ * @param {Boolean} animate True if the collapse is animated, else false.
+ */
+ 'beforecollapse',
+ /**
+ * @event beforeexpand
+ * Fires before the Panel is expanded. A handler can return false to cancel the expand.
+ * @param {Ext.Panel} p The Panel being expanded.
+ * @param {Boolean} animate True if the expand is animated, else false.
+ */
+ 'beforeexpand',
+ /**
+ * @event beforeclose
+ * Fires before the Panel is closed. Note that Panels do not directly support being closed, but some
+ * Panel subclasses do (like {@link Ext.Window}) or a Panel within a Ext.TabPanel. This event only
+ * applies to such subclasses.
+ * A handler can return false to cancel the close.
+ * @param {Ext.Panel} p The Panel being closed.
+ */
+ 'beforeclose',
+ /**
+ * @event close
+ * Fires after the Panel is closed. Note that Panels do not directly support being closed, but some
+ * Panel subclasses do (like {@link Ext.Window}) or a Panel within a Ext.TabPanel.
+ * @param {Ext.Panel} p The Panel that has been closed.
+ */
+ 'close',
+ /**
+ * @event activate
+ * Fires after the Panel has been visually activated.
+ * Note that Panels do not directly support being activated, but some Panel subclasses
+ * do (like {@link Ext.Window}). Panels which are child Components of a TabPanel fire the
+ * activate and deactivate events under the control of the TabPanel.
+ * @param {Ext.Panel} p The Panel that has been activated.
+ */
+ 'activate',
+ /**
+ * @event deactivate
+ * Fires after the Panel has been visually deactivated.
+ * Note that Panels do not directly support being deactivated, but some Panel subclasses
+ * do (like {@link Ext.Window}). Panels which are child Components of a TabPanel fire the
+ * activate and deactivate events under the control of the TabPanel.
+ * @param {Ext.Panel} p The Panel that has been deactivated.
+ */
+ 'deactivate'
+ );
+
+ if(this.unstyled){
+ this.baseCls = 'x-plain';
+ }
+
+
+ this.toolbars = [];
+ // shortcuts
+ if(this.tbar){
+ this.elements += ',tbar';
+ this.topToolbar = this.createToolbar(this.tbar);
+ this.tbar = null;
+
+ }
+ if(this.bbar){
+ this.elements += ',bbar';
+ this.bottomToolbar = this.createToolbar(this.bbar);
+ this.bbar = null;
+ }
+
+ if(this.header === true){
+ this.elements += ',header';
+ this.header = null;
+ }else if(this.headerCfg || (this.title && this.header !== false)){
+ this.elements += ',header';
+ }
+
+ if(this.footerCfg || this.footer === true){
+ this.elements += ',footer';
+ this.footer = null;
+ }
+
+ if(this.buttons){
+ this.fbar = this.buttons;
+ this.buttons = null;
+ }
+ if(this.fbar){
+ this.createFbar(this.fbar);
+ }
+ if(this.autoLoad){
+ this.on('render', this.doAutoLoad, this, {delay:10});
+ }
+ },
+
+ // private
+ createFbar : function(fbar){
+ var min = this.minButtonWidth;
+ this.elements += ',footer';
+ this.fbar = this.createToolbar(fbar, {
+ buttonAlign: this.buttonAlign,
+ toolbarCls: 'x-panel-fbar',
+ enableOverflow: false,
+ defaults: function(c){
+ return {
+ minWidth: c.minWidth || min
+ };
+ }
+ });
+ // @compat addButton and buttons could possibly be removed
+ // @target 4.0
+ /**
+ * This Panel's Array of buttons as created from the {@link #buttons}
+ * config property. Read only.
+ * @type Array
+ * @property buttons
+ */
+ this.fbar.items.each(function(c){
+ c.minWidth = c.minWidth || this.minButtonWidth;
+ }, this);
+ this.buttons = this.fbar.items.items;
+ },
+
+ // private
+ createToolbar: function(tb, options){
+ var result;
+ // Convert array to proper toolbar config
+ if(Ext.isArray(tb)){
+ tb = {
+ items: tb
+ };
+ }
+ result = tb.events ? Ext.apply(tb, options) : this.createComponent(Ext.apply({}, tb, options), 'toolbar');
+ this.toolbars.push(result);
+ return result;
+ },
+
+ // private
+ createElement : function(name, pnode){
+ if(this[name]){
+ pnode.appendChild(this[name].dom);
+ return;
+ }
- <div class="x-panel-header"><span class="x-panel-header-text">Title: (frame:false)</span></div>
+ if(name === 'bwrap' || this.elements.indexOf(name) != -1){
+ if(this[name+'Cfg']){
+ this[name] = Ext.fly(pnode).createChild(this[name+'Cfg']);
+ }else{
+ var el = document.createElement('div');
+ el.className = this[name+'Cls'];
+ this[name] = Ext.get(pnode.appendChild(el));
+ }
+ if(this[name+'CssClass']){
+ this[name].addClass(this[name+'CssClass']);
+ }
+ if(this[name+'Style']){
+ this[name].applyStyles(this[name+'Style']);
+ }
+ }
+ },
- <div class="x-panel-bwrap">
- <div class="x-panel-body"><p>html value goes here</p></div>
- </div>
-</div>
+ // private
+ onRender : function(ct, position){
+ Ext.Panel.superclass.onRender.call(this, ct, position);
+ this.createClasses();
-// frame = true (create 9 elements)
-<div id="developer-specified-id-goes-here" class="x-panel">
- <div class="x-panel-tl"><div class="x-panel-tr"><div class="x-panel-tc">
- <div class="x-panel-header"><span class="x-panel-header-text">Title: (frame:true)</span></div>
- </div></div></div>
+ var el = this.el,
+ d = el.dom,
+ bw,
+ ts;
- <div class="x-panel-bwrap">
- <div class="x-panel-ml"><div class="x-panel-mr"><div class="x-panel-mc">
- <div class="x-panel-body"><p>html value goes here</p></div>
- </div></div></div>
- <div class="x-panel-bl"><div class="x-panel-br"><div class="x-panel-bc"/>
- </div></div></div>
-</div>
- *
- */
- /**
- * @cfg {Boolean} border
- * True to display the borders of the panel's body element, false to hide them (defaults to true). By default,
- * the border is a 2px wide inset border, but this can be further altered by setting {@link #bodyBorder} to false.
- */
- /**
- * @cfg {Boolean} bodyBorder
- * True to display an interior border on the body element of the panel, false to hide it (defaults to true).
- * This only applies when {@link #border} == true. If border == true and bodyBorder == false, the border will display
- * as a 1px wide inset border, giving the entire body element an inset appearance.
- */
- /**
- * @cfg {String/Object/Function} bodyCssClass
- * Additional css class selector to be applied to the {@link #body} element in the format expected by
- * {@link Ext.Element#addClass} (defaults to null). See {@link #bodyCfg}.
- */
+ if(this.collapsible && !this.hideCollapseTool){
+ this.tools = this.tools ? this.tools.slice(0) : [];
+ this.tools[this.collapseFirst?'unshift':'push']({
+ id: 'toggle',
+ handler : this.toggleCollapse,
+ scope: this
+ });
+ }
+
+ if(this.tools){
+ ts = this.tools;
+ this.elements += (this.header !== false) ? ',header' : '';
+ }
+ this.tools = {};
+
+ el.addClass(this.baseCls);
+ if(d.firstChild){ // existing markup
+ this.header = el.down('.'+this.headerCls);
+ this.bwrap = el.down('.'+this.bwrapCls);
+ var cp = this.bwrap ? this.bwrap : el;
+ this.tbar = cp.down('.'+this.tbarCls);
+ this.body = cp.down('.'+this.bodyCls);
+ this.bbar = cp.down('.'+this.bbarCls);
+ this.footer = cp.down('.'+this.footerCls);
+ this.fromMarkup = true;
+ }
+ if (this.preventBodyReset === true) {
+ el.addClass('x-panel-reset');
+ }
+ if(this.cls){
+ el.addClass(this.cls);
+ }
+
+ if(this.buttons){
+ this.elements += ',footer';
+ }
+
+ // This block allows for maximum flexibility and performance when using existing markup
+
+ // framing requires special markup
+ if(this.frame){
+ el.insertHtml('afterBegin', String.format(Ext.Element.boxMarkup, this.baseCls));
+
+ this.createElement('header', d.firstChild.firstChild.firstChild);
+ this.createElement('bwrap', d);
+
+ // append the mid and bottom frame to the bwrap
+ bw = this.bwrap.dom;
+ var ml = d.childNodes[1], bl = d.childNodes[2];
+ bw.appendChild(ml);
+ bw.appendChild(bl);
+
+ var mc = bw.firstChild.firstChild.firstChild;
+ this.createElement('tbar', mc);
+ this.createElement('body', mc);
+ this.createElement('bbar', mc);
+ this.createElement('footer', bw.lastChild.firstChild.firstChild);
+
+ if(!this.footer){
+ this.bwrap.dom.lastChild.className += ' x-panel-nofooter';
+ }
+ /*
+ * Store a reference to this element so:
+ * a) We aren't looking it up all the time
+ * b) The last element is reported incorrectly when using a loadmask
+ */
+ this.ft = Ext.get(this.bwrap.dom.lastChild);
+ this.mc = Ext.get(mc);
+ }else{
+ this.createElement('header', d);
+ this.createElement('bwrap', d);
+
+ // append the mid and bottom frame to the bwrap
+ bw = this.bwrap.dom;
+ this.createElement('tbar', bw);
+ this.createElement('body', bw);
+ this.createElement('bbar', bw);
+ this.createElement('footer', bw);
+
+ if(!this.header){
+ this.body.addClass(this.bodyCls + '-noheader');
+ if(this.tbar){
+ this.tbar.addClass(this.tbarCls + '-noheader');
+ }
+ }
+ }
+
+ if(Ext.isDefined(this.padding)){
+ this.body.setStyle('padding', this.body.addUnits(this.padding));
+ }
+
+ if(this.border === false){
+ this.el.addClass(this.baseCls + '-noborder');
+ this.body.addClass(this.bodyCls + '-noborder');
+ if(this.header){
+ this.header.addClass(this.headerCls + '-noborder');
+ }
+ if(this.footer){
+ this.footer.addClass(this.footerCls + '-noborder');
+ }
+ if(this.tbar){
+ this.tbar.addClass(this.tbarCls + '-noborder');
+ }
+ if(this.bbar){
+ this.bbar.addClass(this.bbarCls + '-noborder');
+ }
+ }
+
+ if(this.bodyBorder === false){
+ this.body.addClass(this.bodyCls + '-noborder');
+ }
+
+ this.bwrap.enableDisplayMode('block');
+
+ if(this.header){
+ this.header.unselectable();
+
+ // for tools, we need to wrap any existing header markup
+ if(this.headerAsText){
+ this.header.dom.innerHTML =
+ ''+this.header.dom.innerHTML+'';
+
+ if(this.iconCls){
+ this.setIconClass(this.iconCls);
+ }
+ }
+ }
+
+ if(this.floating){
+ this.makeFloating(this.floating);
+ }
+
+ if(this.collapsible && this.titleCollapse && this.header){
+ this.mon(this.header, 'click', this.toggleCollapse, this);
+ this.header.setStyle('cursor', 'pointer');
+ }
+ if(ts){
+ this.addTool.apply(this, ts);
+ }
+
+ // Render Toolbars.
+ if(this.fbar){
+ this.footer.addClass('x-panel-btns');
+ this.fbar.ownerCt = this;
+ this.fbar.render(this.footer);
+ this.footer.createChild({cls:'x-clear'});
+ }
+ if(this.tbar && this.topToolbar){
+ this.topToolbar.ownerCt = this;
+ this.topToolbar.render(this.tbar);
+ }
+ if(this.bbar && this.bottomToolbar){
+ this.bottomToolbar.ownerCt = this;
+ this.bottomToolbar.render(this.bbar);
+ }
+ },
+
/**
- * @cfg {String/Object/Function} bodyStyle
- * Custom CSS styles to be applied to the {@link #body} element in the format expected by
- * {@link Ext.Element#applyStyles} (defaults to null). See {@link #bodyCfg}.
+ * 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
*/
- /**
- * @cfg {String} iconCls
- * The CSS class selector that specifies a background image to be used as the header icon (defaults to '').
- * An example of specifying a custom icon class would be something like:
- *
-// specify the property in the config for the class:
- ...
- iconCls: 'my-icon'
+ 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);
+ },
-// css class that specifies background image to be used as the icon image:
-.my-icon { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
-
- */
- /**
- * @cfg {Boolean} collapsible
- * True to make the panel collapsible and have the expand/collapse toggle button automatically rendered into
- * the header tool button area, false to keep the panel statically sized with no button (defaults to false).
+ // 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;
+ },
+
/**
- * @cfg {Array} tools
- * An array of tool button configs to be added to the header tool area. When rendered, each tool is
- * stored as an {@link Ext.Element Element} referenced by a public property called tools.<tool-type>
- * Each tool config may contain the following properties:
- *
- * - id : StringRequired. The type
- * of tool to create. By default, this assigns a CSS class of the form
x-tool-<tool-type>
to the
- * resulting tool Element. Ext provides CSS rules, and an icon sprite containing images for the tool types listed below.
- * The developer may implement custom tools by supplying alternate CSS rules and background images:
- *
- * toggle
(Created by default when {@link #collapsible} is true
)
- * close
- * minimize
- * maximize
- * restore
- * gear
- * pin
- * unpin
- * right
- * left
- * up
- * down
- * refresh
- * minus
- * plus
- * help
- * search
- * save
- * print
- *
- * - handler : FunctionRequired. The function to
- * call when clicked. Arguments passed are:
- * - event : Ext.EventObjectThe click event.
- * - toolEl : Ext.ElementThe tool Element.
- * - panel : Ext.PanelThe host Panel
- * - tc : Ext.PanelThe tool configuration object
- *
- * - stopEvent : BooleanDefaults to true. Specify as false to allow click event to propagate.
- * - scope : ObjectThe scope in which to call the handler.
- * - qtip : String/ObjectA tip string, or
- * a config argument to {@link Ext.QuickTip#register}
- * - hidden : BooleanTrue to initially render hidden.
- * - on : ObjectA listener config object specifiying
- * event listeners in the format of an argument to {@link #addListener}
- *
- * Note that, apart from the toggle tool which is provided when a panel is collapsible, these
- * tools only provide the visual button. Any required functionality must be provided by adding
- * handlers that implement the necessary behavior.
- * Example usage:
- *
-tools:[{
- id:'refresh',
- qtip: 'Refresh form Data',
- // hidden:true,
- handler: function(event, toolEl, panel){
- // refresh logic
- }
-},
-{
- id:'help',
- qtip: 'Get Help',
- handler: function(event, toolEl, panel){
- // whatever
- }
-}]
-
- * For the custom id of 'help'
define two relevant css classes with a link to
- * a 15x15 image:
- *
-.x-tool-help {background-image: url(images/help.png);}
-.x-tool-help-over {background-image: url(images/help_over.png);}
-// if using an image sprite:
-.x-tool-help {background-image: url(images/help.png) no-repeat 0 0;}
-.x-tool-help-over {background-position:-15px 0;}
-
+ * 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;
+ },
+
/**
- * @cfg {Ext.Template/Ext.XTemplate} toolTemplate
- * A Template used to create {@link #tools} in the {@link #header} Element. Defaults to:
-new Ext.Template('<div class="x-tool x-tool-{id}"> </div>')
- * This may may be overridden to provide a custom DOM structure for tools based upon a more
- * complex XTemplate. The template's data is a single tool configuration object (Not the entire Array)
- * as specified in {@link #tools}. In the following example an <a> tag is used to provide a
- * visual indication when hovering over the tool:
-var win = new Ext.Window({
- tools: [{
- id: 'download',
- href: '/MyPdfDoc.pdf'
- }],
- toolTemplate: new Ext.XTemplate(
- '<tpl if="id==\'download\'">',
- '<a class="x-tool x-tool-pdf" href="{href}"></a>',
- '</tpl>',
- '<tpl if="id!=\'download\'">',
- '<div class="x-tool x-tool-{id}"> </div>',
- '</tpl>'
- ),
- width:500,
- height:300,
- closeAction:'hide'
-});
- * Note that the CSS class 'x-tool-pdf' should have an associated style rule which provides an
- * appropriate background image, something like:
-
- a.x-tool-pdf {background-image: url(../shared/extjs/images/pdf.gif)!important;}
-
+ * 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;
+ },
+
/**
- * @cfg {Boolean} hideCollapseTool
- * true
to hide the expand/collapse toggle button when {@link #collapsible} == true
,
- * false
to display it (defaults to false
).
+ * Adds a button to this panel. Note that this method must be called prior to rendering. The preferred
+ * approach is to add buttons via the {@link #buttons} config.
+ * @param {String/Object} config A valid {@link Ext.Button} config. A string will become the text for a default
+ * button config, an object will be treated as a button config object.
+ * @param {Function} handler The function to be called on button {@link Ext.Button#click}
+ * @param {Object} scope The scope (this
reference) in which the button handler function is executed. Defaults to the Button.
+ * @return {Ext.Button} The button that was added
*/
+ addButton : function(config, handler, scope){
+ if(!this.fbar){
+ this.createFbar([]);
+ }
+ if(handler){
+ if(Ext.isString(config)){
+ config = {text: config};
+ }
+ config = Ext.apply({
+ handler: handler,
+ scope: scope
+ }, config);
+ }
+ return this.fbar.add(config);
+ },
+
+ // private
+ addTool : function(){
+ if(!this.rendered){
+ if(!this.tools){
+ this.tools = [];
+ }
+ Ext.each(arguments, function(arg){
+ this.tools.push(arg);
+ }, this);
+ return;
+ }
+ // nowhere to render tools!
+ if(!this[this.toolTarget]){
+ return;
+ }
+ if(!this.toolTemplate){
+ // initialize the global tool template on first use
+ var tt = new Ext.Template(
+ ' '
+ );
+ tt.disableFormats = true;
+ tt.compile();
+ Ext.Panel.prototype.toolTemplate = tt;
+ }
+ for(var i = 0, a = arguments, len = a.length; i < len; i++) {
+ var tc = a[i];
+ if(!this.tools[tc.id]){
+ var overCls = 'x-tool-'+tc.id+'-over';
+ var t = this.toolTemplate.insertFirst(this[this.toolTarget], tc, true);
+ this.tools[tc.id] = t;
+ t.enableDisplayMode('block');
+ this.mon(t, 'click', this.createToolHandler(t, tc, overCls, this));
+ if(tc.on){
+ this.mon(t, tc.on);
+ }
+ if(tc.hidden){
+ t.hide();
+ }
+ if(tc.qtip){
+ if(Ext.isObject(tc.qtip)){
+ Ext.QuickTips.register(Ext.apply({
+ target: t.id
+ }, tc.qtip));
+ } else {
+ t.dom.qtip = tc.qtip;
+ }
+ }
+ t.addClassOnOver(overCls);
+ }
+ }
+ },
+
+ onLayout : function(shallow, force){
+ Ext.Panel.superclass.onLayout.apply(this, arguments);
+ if(this.hasLayout && this.toolbars.length > 0){
+ Ext.each(this.toolbars, function(tb){
+ tb.doLayout(undefined, force);
+ });
+ this.syncHeight();
+ }
+ },
+
+ syncHeight : function(){
+ var h = this.toolbarHeight,
+ bd = this.body,
+ lsh = this.lastSize.height,
+ sz;
+
+ if(this.autoHeight || !Ext.isDefined(lsh) || lsh == 'auto'){
+ return;
+ }
+
+
+ if(h != this.getToolbarHeight()){
+ h = Math.max(0, lsh - this.getFrameHeight());
+ bd.setHeight(h);
+ sz = bd.getSize();
+ this.toolbarHeight = this.getToolbarHeight();
+ this.onBodyResize(sz.width, sz.height);
+ }
+ },
+
+ // private
+ onShow : function(){
+ if(this.floating){
+ return this.el.show();
+ }
+ Ext.Panel.superclass.onShow.call(this);
+ },
+
+ // private
+ onHide : function(){
+ if(this.floating){
+ return this.el.hide();
+ }
+ Ext.Panel.superclass.onHide.call(this);
+ },
+
+ // private
+ createToolHandler : function(t, tc, overCls, panel){
+ return function(e){
+ t.removeClass(overCls);
+ if(tc.stopEvent !== false){
+ e.stopEvent();
+ }
+ if(tc.handler){
+ tc.handler.call(tc.scope || t, e, t, panel, tc);
+ }
+ };
+ },
+
+ // private
+ afterRender : function(){
+ if(this.floating && !this.hidden){
+ this.el.show();
+ }
+ if(this.title){
+ this.setTitle(this.title);
+ }
+ Ext.Panel.superclass.afterRender.call(this); // do sizing calcs last
+ if (this.collapsed) {
+ this.collapsed = false;
+ this.collapse(false);
+ }
+ this.initEvents();
+ },
+
+ // private
+ getKeyMap : function(){
+ if(!this.keyMap){
+ this.keyMap = new Ext.KeyMap(this.el, this.keys);
+ }
+ return this.keyMap;
+ },
+
+ // private
+ initEvents : function(){
+ if(this.keys){
+ this.getKeyMap();
+ }
+ if(this.draggable){
+ this.initDraggable();
+ }
+ if(this.toolbars.length > 0){
+ Ext.each(this.toolbars, function(tb){
+ tb.doLayout();
+ tb.on({
+ scope: this,
+ afterlayout: this.syncHeight,
+ remove: this.syncHeight
+ });
+ }, this);
+ this.syncHeight();
+ }
+
+ },
+
+ // private
+ initDraggable : function(){
+ /**
+ * If this Panel is configured {@link #draggable}, this property will contain
+ * an instance of {@link Ext.dd.DragSource} which handles dragging the Panel.
+ * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
+ * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
+ * @type Ext.dd.DragSource.
+ * @property dd
+ */
+ this.dd = new Ext.Panel.DD(this, Ext.isBoolean(this.draggable) ? null : this.draggable);
+ },
+
+ // private
+ beforeEffect : function(anim){
+ if(this.floating){
+ this.el.beforeAction();
+ }
+ if(anim !== false){
+ this.el.addClass('x-panel-animated');
+ }
+ },
+
+ // private
+ afterEffect : function(anim){
+ this.syncShadow();
+ this.el.removeClass('x-panel-animated');
+ },
+
+ // private - wraps up an animation param with internal callbacks
+ createEffect : function(a, cb, scope){
+ var o = {
+ scope:scope,
+ block:true
+ };
+ if(a === true){
+ o.callback = cb;
+ return o;
+ }else if(!a.callback){
+ o.callback = cb;
+ }else { // wrap it up
+ o.callback = function(){
+ cb.call(scope);
+ Ext.callback(a.callback, a.scope);
+ };
+ }
+ return Ext.applyIf(o, a);
+ },
+
/**
- * @cfg {Boolean} titleCollapse
- * true
to allow expanding and collapsing the panel (when {@link #collapsible} = true
)
- * by clicking anywhere in the header bar, false
) to allow it only by clicking to tool button
- * (defaults to false
)). If this panel is a child item of a border layout also see the
- * {@link Ext.layout.BorderLayout.Region BorderLayout.Region}
- * {@link Ext.layout.BorderLayout.Region#floatable floatable}
config option.
+ * Collapses the panel body so that it becomes hidden. Fires the {@link #beforecollapse} event which will
+ * cancel the collapse action if it returns false.
+ * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
+ * {@link #animCollapse} panel config)
+ * @return {Ext.Panel} this
*/
+ collapse : function(animate){
+ if(this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforecollapse', this, animate) === false){
+ return;
+ }
+ var doAnim = animate === true || (animate !== false && this.animCollapse);
+ this.beforeEffect(doAnim);
+ this.onCollapse(doAnim, animate);
+ return this;
+ },
+
+ // private
+ onCollapse : function(doAnim, animArg){
+ if(doAnim){
+ this[this.collapseEl].slideOut(this.slideAnchor,
+ Ext.apply(this.createEffect(animArg||true, this.afterCollapse, this),
+ this.collapseDefaults));
+ }else{
+ this[this.collapseEl].hide(this.hideMode);
+ this.afterCollapse(false);
+ }
+ },
+
+ // private
+ afterCollapse : function(anim){
+ this.collapsed = true;
+ this.el.addClass(this.collapsedCls);
+ if(anim !== false){
+ this[this.collapseEl].hide(this.hideMode);
+ }
+ this.afterEffect(anim);
+
+ // Reset lastSize of all sub-components so they KNOW they are in a collapsed container
+ this.cascade(function(c) {
+ if (c.lastSize) {
+ c.lastSize = { width: undefined, height: undefined };
+ }
+ });
+ this.fireEvent('collapse', this);
+ },
+
/**
- * @cfg {Boolean} autoScroll
- * true
to use overflow:'auto' on the panel's body element and show scroll bars automatically when
- * necessary, false
to clip any overflowing content (defaults to false
).
+ * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will
+ * cancel the expand action if it returns false.
+ * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
+ * {@link #animCollapse} panel config)
+ * @return {Ext.Panel} this
*/
+ expand : function(animate){
+ if(!this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforeexpand', this, animate) === false){
+ return;
+ }
+ var doAnim = animate === true || (animate !== false && this.animCollapse);
+ this.el.removeClass(this.collapsedCls);
+ this.beforeEffect(doAnim);
+ this.onExpand(doAnim, animate);
+ return this;
+ },
+
+ // private
+ onExpand : function(doAnim, animArg){
+ if(doAnim){
+ this[this.collapseEl].slideIn(this.slideAnchor,
+ Ext.apply(this.createEffect(animArg||true, this.afterExpand, this),
+ this.expandDefaults));
+ }else{
+ this[this.collapseEl].show(this.hideMode);
+ this.afterExpand(false);
+ }
+ },
+
+ // private
+ afterExpand : function(anim){
+ this.collapsed = false;
+ if(anim !== false){
+ this[this.collapseEl].show(this.hideMode);
+ }
+ this.afterEffect(anim);
+ if (this.deferLayout) {
+ delete this.deferLayout;
+ this.doLayout(true);
+ }
+ this.fireEvent('expand', this);
+ },
+
/**
- * @cfg {Mixed} floating
- * This property is used to configure the underlying {@link Ext.Layer}. Acceptable values for this
- * configuration property are:
- * false
: Default.Display the panel inline where it is
- * rendered.
- * true
: Float the panel (absolute position it with automatic
- * shimming and shadow).
- * Setting floating to true will create an Ext.Layer for this panel and display the
- * panel at negative offsets so that it is hidden.
- * Since the panel will be absolute positioned, the position must be set explicitly
- * after render (e.g., myPanel.setPosition(100,100);
).
- * Note: when floating a panel you should always assign a fixed width,
- * otherwise it will be auto width and will expand to fill to the right edge of the viewport.
- *
- * {@link Ext.Layer object}
: The specified object will be used
- * as the configuration object for the {@link Ext.Layer} that will be created.
- *
+ * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel.
+ * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
+ * {@link #animCollapse} panel config)
+ * @return {Ext.Panel} this
*/
+ toggleCollapse : function(animate){
+ this[this.collapsed ? 'expand' : 'collapse'](animate);
+ return this;
+ },
+
+ // private
+ onDisable : function(){
+ if(this.rendered && this.maskDisabled){
+ this.el.mask();
+ }
+ Ext.Panel.superclass.onDisable.call(this);
+ },
+
+ // private
+ onEnable : function(){
+ if(this.rendered && this.maskDisabled){
+ this.el.unmask();
+ }
+ Ext.Panel.superclass.onEnable.call(this);
+ },
+
+ // private
+ onResize : function(adjWidth, adjHeight, rawWidth, rawHeight){
+ var w = adjWidth,
+ h = adjHeight;
+
+ if(Ext.isDefined(w) || Ext.isDefined(h)){
+ if(!this.collapsed){
+ // First, set the the Panel's body width.
+ // If we have auto-widthed it, get the resulting full offset width so we can size the Toolbars to match
+ // The Toolbars must not buffer this resize operation because we need to know their heights.
+
+ if(Ext.isNumber(w)){
+ this.body.setWidth(w = this.adjustBodyWidth(w - this.getFrameWidth()));
+ } else if (w == 'auto') {
+ w = this.body.setWidth('auto').dom.offsetWidth;
+ } else {
+ w = this.body.dom.offsetWidth;
+ }
+
+ if(this.tbar){
+ this.tbar.setWidth(w);
+ if(this.topToolbar){
+ this.topToolbar.setSize(w);
+ }
+ }
+ if(this.bbar){
+ this.bbar.setWidth(w);
+ if(this.bottomToolbar){
+ this.bottomToolbar.setSize(w);
+ // The bbar does not move on resize without this.
+ if (Ext.isIE) {
+ this.bbar.setStyle('position', 'static');
+ this.bbar.setStyle('position', '');
+ }
+ }
+ }
+ if(this.footer){
+ this.footer.setWidth(w);
+ if(this.fbar){
+ this.fbar.setSize(Ext.isIE ? (w - this.footer.getFrameWidth('lr')) : 'auto');
+ }
+ }
+
+ // At this point, the Toolbars must be layed out for getFrameHeight to find a result.
+ if(Ext.isNumber(h)){
+ h = Math.max(0, h - this.getFrameHeight());
+ //h = Math.max(0, h - (this.getHeight() - this.body.getHeight()));
+ this.body.setHeight(h);
+ }else if(h == 'auto'){
+ this.body.setHeight(h);
+ }
+
+ if(this.disabled && this.el._mask){
+ this.el._mask.setSize(this.el.dom.clientWidth, this.el.getHeight());
+ }
+ }else{
+ // Adds an event to set the correct height afterExpand. This accounts for the deferHeight flag in panel
+ this.queuedBodySize = {width: w, height: h};
+ if(!this.queuedExpand && this.allowQueuedExpand !== false){
+ this.queuedExpand = true;
+ this.on('expand', function(){
+ delete this.queuedExpand;
+ this.onResize(this.queuedBodySize.width, this.queuedBodySize.height);
+ }, this, {single:true});
+ }
+ }
+ this.onBodyResize(w, h);
+ }
+ this.syncShadow();
+ Ext.Panel.superclass.onResize.call(this, adjWidth, adjHeight, rawWidth, rawHeight);
+
+ },
+
+ // private
+ onBodyResize: function(w, h){
+ this.fireEvent('bodyresize', this, w, h);
+ },
+
+ // private
+ getToolbarHeight: function(){
+ var h = 0;
+ if(this.rendered){
+ Ext.each(this.toolbars, function(tb){
+ h += tb.getHeight();
+ }, this);
+ }
+ return h;
+ },
+
+ // deprecate
+ adjustBodyHeight : function(h){
+ return h;
+ },
+
+ // private
+ adjustBodyWidth : function(w){
+ return w;
+ },
+
+ // private
+ onPosition : function(){
+ this.syncShadow();
+ },
+
/**
- * @cfg {Boolean/String} shadow
- * true
(or a valid Ext.Shadow {@link Ext.Shadow#mode} value) to display a shadow behind the
- * panel, false
to display no shadow (defaults to 'sides'
). Note that this option
- * only applies when {@link #floating} = true
.
+ * Returns the width in pixels of the framing elements of this panel (not including the body width). To
+ * retrieve the body width see {@link #getInnerWidth}.
+ * @return {Number} The frame width
*/
+ getFrameWidth : function(){
+ var w = this.el.getFrameWidth('lr') + this.bwrap.getFrameWidth('lr');
+
+ if(this.frame){
+ var l = this.bwrap.dom.firstChild;
+ w += (Ext.fly(l).getFrameWidth('l') + Ext.fly(l.firstChild).getFrameWidth('r'));
+ w += this.mc.getFrameWidth('lr');
+ }
+ return w;
+ },
+
/**
- * @cfg {Number} shadowOffset
- * The number of pixels to offset the shadow if displayed (defaults to 4
). Note that this
- * option only applies when {@link #floating} = true
.
+ * Returns the height in pixels of the framing elements of this panel (including any top and bottom bars and
+ * header and footer elements, but not including the body height). To retrieve the body height see {@link #getInnerHeight}.
+ * @return {Number} The frame height
*/
+ getFrameHeight : function() {
+ var h = this.el.getFrameWidth('tb') + this.bwrap.getFrameWidth('tb');
+ h += (this.tbar ? this.tbar.getHeight() : 0) +
+ (this.bbar ? this.bbar.getHeight() : 0);
+
+ if(this.frame){
+ h += this.el.dom.firstChild.offsetHeight + this.ft.dom.offsetHeight + this.mc.getFrameWidth('tb');
+ }else{
+ h += (this.header ? this.header.getHeight() : 0) +
+ (this.footer ? this.footer.getHeight() : 0);
+ }
+ return h;
+ },
+
/**
- * @cfg {Boolean} shim
- * false
to disable the iframe shim in browsers which need one (defaults to true
).
- * Note that this option only applies when {@link #floating} = true
.
+ * Returns the width in pixels of the body element (not including the width of any framing elements).
+ * For the frame width see {@link #getFrameWidth}.
+ * @return {Number} The body width
*/
+ getInnerWidth : function(){
+ return this.getSize().width - this.getFrameWidth();
+ },
+
/**
- * @cfg {String/Object} html
- * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the panel's body
- * content (defaults to ''). The HTML content is added by the Panel's {@link #afterRender} method,
- * and so the document will not contain this HTML at the time the {@link #render} event is fired.
- * This content is inserted into the body before any configured {@link #contentEl} is appended.
+ * Returns the height in pixels of the body element (not including the height of any framing elements).
+ * For the frame height see {@link #getFrameHeight}.
+ * @return {Number} The body height
*/
+ getInnerHeight : function(){
+ return this.body.getHeight();
+ /* Deprecate
+ return this.getSize().height - this.getFrameHeight();
+ */
+ },
+
+ // private
+ syncShadow : function(){
+ if(this.floating){
+ this.el.sync(true);
+ }
+ },
+
+ // private
+ getLayoutTarget : function(){
+ return this.body;
+ },
+
+ // private
+ getContentTarget : function(){
+ return this.body;
+ },
+
/**
- * @cfg {String} contentEl
- * Optional. Specify an existing HTML element, or the id
of an existing HTML element to use as this Panel's
- * {@link #body}
content.
- *
- * - Description :
- * This config option is used to take an existing HTML element and place it in the body
- * of a new panel (it simply moves the specified DOM element into the body element of the Panel
- * after the Panel is rendered to use as the content (it is not going to be the actual panel itself).
- * - Notes :
- * The specified HTML element is appended to the Panel's {@link #body} Element by the
- * Panel's
afterRender
method after any configured {@link #html HTML} has
- * been inserted, and so the document will not contain this element at the time the
- * {@link #render} event is fired.
- * The specified HTML element used will not participate in any {@link Ext.Container#layout layout}
- * scheme that the Panel may use. It is just HTML. Layouts operate on child {@link Ext.Container#items items}
.
- * Add either the x-hidden
or the x-hide-display
CSS class to
- * prevent a brief flicker of the content before it is rendered to the panel.
- *
+ * Sets the title text for the panel and optionally the {@link #iconCls icon class}.
+ * In order to be able to set the title, a header element must have been created
+ * for the Panel. This is triggered either by configuring the Panel with a non-blank {@link #title}
,
+ * or configuring it with {@link #header}: true
.
+ * @param {String} title The title text to set
+ * @param {String} iconCls (optional) {@link #iconCls iconCls} A user-defined CSS class that provides the icon image for this panel
*/
+ setTitle : function(title, iconCls){
+ this.title = title;
+ if(this.header && this.headerAsText){
+ this.header.child('span').update(title);
+ }
+ if(iconCls){
+ this.setIconClass(iconCls);
+ }
+ this.fireEvent('titlechange', this, title);
+ return this;
+ },
+
/**
- * @cfg {Object/Array} keys
- * A {@link Ext.KeyMap} config object (in the format expected by {@link Ext.KeyMap#addBinding}
- * used to assign custom key handling to this panel (defaults to null
).
+ * Get the {@link Ext.Updater} for this panel. Enables you to perform Ajax updates of this panel's body.
+ * @return {Ext.Updater} The Updater
*/
- /**
- * @cfg {Boolean/Object} draggable
- * true
to enable dragging of this Panel (defaults to false
).
- * For custom drag/drop implementations, an Ext.Panel.DD config could also be passed
- * in this config instead of true
. Ext.Panel.DD is an internal, undocumented class which
- * moves a proxy Element around in place of the Panel's element, but provides no other behaviour
- * during dragging or on drop. It is a subclass of {@link Ext.dd.DragSource}, so behaviour may be
- * added by implementing the interface methods of {@link Ext.dd.DragDrop} e.g.:
- *
-new Ext.Panel({
- title: 'Drag me',
- x: 100,
- y: 100,
- renderTo: Ext.getBody(),
- floating: true,
- frame: true,
- width: 400,
- height: 200,
- draggable: {
-// Config option of Ext.Panel.DD class.
-// It's a floating Panel, so do not show a placeholder proxy in the original position.
- insertProxy: false,
+ getUpdater : function(){
+ return this.body.getUpdater();
+ },
-// Called for each mousemove event while dragging the DD object.
- onDrag : function(e){
-// Record the x,y position of the drag proxy so that we can
-// position the Panel at end of drag.
- var pel = this.proxy.getEl();
- this.x = pel.getLeft(true);
- this.y = pel.getTop(true);
+ /**
+ * Loads this content panel immediately with content returned from an XHR call.
+ * @param {Object/String/Function} config A config object containing any of the following options:
+
+panel.load({
+ url: 'your-url.php',
+ params: {param1: 'foo', param2: 'bar'}, // or a URL encoded string
+ callback: yourFunction,
+ scope: yourObject, // optional scope for the callback
+ discardUrl: false,
+ nocache: false,
+ text: 'Loading...',
+ timeout: 30,
+ scripts: false
+});
+
+ * The only required property is url. The optional properties nocache, text and scripts
+ * are shorthand for disableCaching, indicatorText and loadScripts and are used to set their
+ * associated property on this panel Updater instance.
+ * @return {Ext.Panel} this
+ */
+ load : function(){
+ var um = this.body.getUpdater();
+ um.update.apply(um, arguments);
+ return this;
+ },
-// Keep the Shadow aligned if there is one.
- var s = this.panel.getEl().shadow;
- if (s) {
- s.realign(this.x, this.y, pel.getWidth(), pel.getHeight());
+ // private
+ beforeDestroy : function(){
+ Ext.Panel.superclass.beforeDestroy.call(this);
+ if(this.header){
+ this.header.removeAllListeners();
+ }
+ if(this.tools){
+ for(var k in this.tools){
+ Ext.destroy(this.tools[k]);
}
- },
+ }
+ if(this.toolbars.length > 0){
+ Ext.each(this.toolbars, function(tb){
+ tb.un('afterlayout', this.syncHeight, this);
+ tb.un('remove', this.syncHeight, this);
+ }, this);
+ }
+ if(Ext.isArray(this.buttons)){
+ while(this.buttons.length) {
+ Ext.destroy(this.buttons[0]);
+ }
+ }
+ if(this.rendered){
+ Ext.destroy(
+ this.ft,
+ this.header,
+ this.footer,
+ this.tbar,
+ this.bbar,
+ this.body,
+ this.mc,
+ this.bwrap,
+ this.dd
+ );
+ if (this.fbar) {
+ Ext.destroy(
+ this.fbar,
+ this.fbar.el
+ );
+ }
+ }
+ Ext.destroy(this.toolbars);
+ },
-// Called on the mouseup event.
- endDrag : function(e){
- this.panel.setPosition(this.x, this.y);
+ // private
+ createClasses : function(){
+ this.headerCls = this.baseCls + '-header';
+ this.headerTextCls = this.baseCls + '-header-text';
+ this.bwrapCls = this.baseCls + '-bwrap';
+ this.tbarCls = this.baseCls + '-tbar';
+ this.bodyCls = this.baseCls + '-body';
+ this.bbarCls = this.baseCls + '-bbar';
+ this.footerCls = this.baseCls + '-footer';
+ },
+
+ // private
+ createGhost : function(cls, useShim, appendTo){
+ var el = document.createElement('div');
+ el.className = 'x-panel-ghost ' + (cls ? cls : '');
+ if(this.header){
+ el.appendChild(this.el.dom.firstChild.cloneNode(true));
}
- }
-}).show();
-
- */
- /**
- * @cfg {Boolean} disabled
- * Render this panel disabled (default is false
). An important note when using the disabled
- * config on panels is that IE will often fail to initialize the disabled mask element correectly if
- * the panel's layout has not yet completed by the time the Panel is disabled during the render process.
- * If you experience this issue, you may need to instead use the {@link #afterlayout} event to initialize
- * the disabled state:
- *
-new Ext.Panel({
- ...
- listeners: {
- 'afterlayout': {
- fn: function(p){
- p.disable();
- },
- single: true // important, as many layouts can occur
+ Ext.fly(el.appendChild(document.createElement('ul'))).setHeight(this.bwrap.getHeight());
+ el.style.width = this.el.dom.offsetWidth + 'px';;
+ if(!appendTo){
+ this.container.dom.appendChild(el);
+ }else{
+ Ext.getDom(appendTo).appendChild(el);
+ }
+ if(useShim !== false && this.el.useShim !== false){
+ var layer = new Ext.Layer({shadow:false, useDisplay:true, constrain:false}, el);
+ layer.show();
+ return layer;
+ }else{
+ return new Ext.Element(el);
}
- }
-});
-
- */
+ },
+
+ // private
+ doAutoLoad : function(){
+ var u = this.body.getUpdater();
+ if(this.renderer){
+ u.setRenderer(this.renderer);
+ }
+ u.update(Ext.isObject(this.autoLoad) ? this.autoLoad : {url: this.autoLoad});
+ },
+
/**
- * @cfg {Boolean} autoHeight
- * true
to use height:'auto', false
to use fixed height (defaults to false
).
- * Note: Setting autoHeight: true
means that the browser will manage the panel's height
- * based on its contents, and that Ext will not manage it at all. If the panel is within a layout that
- * manages dimensions (fit
, border
, etc.) then setting autoHeight: true
- * can cause issues with scrolling and will not generally work as expected since the panel will take
- * on the height of its contents rather than the height required by the Ext layout.
+ * Retrieve a tool by id.
+ * @param {String} id
+ * @return {Object} tool
*/
+ 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 {String} baseCls
- * The base CSS class to apply to this panel's element (defaults to 'x-panel'
).
- * Another option available by default is to specify 'x-plain'
which strips all styling
- * except for required attributes for Ext layouts to function (e.g. overflow:hidden).
- * See {@link #unstyled}
also.
+ * @cfg {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.
*/
- baseCls : 'x-panel',
+ allowBlur: true,
/**
- * @cfg {String} collapsedCls
- * A CSS class to add to the panel's element after it has been collapsed (defaults to
- * 'x-panel-collapsed'
).
+ * @cfg {Boolean/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)
*/
- collapsedCls : 'x-panel-collapsed',
/**
- * @cfg {Boolean} maskDisabled
- * true
to mask the panel when it is {@link #disabled}, false
to not mask it (defaults
- * to true
). Either way, the panel will always tell its contained elements to disable themselves
- * when it is disabled, but masking the panel can provide an additional visual cue that the panel is
- * disabled.
+ * @cfg {Boolean} 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)
*/
- maskDisabled : true,
/**
- * @cfg {Boolean} animCollapse
- * true
to animate the transition when the panel is collapsed, false
to skip the
- * animation (defaults to true
if the {@link Ext.Fx} class is available, otherwise false
).
+ * @cfg {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.
*/
- animCollapse : Ext.enableFx,
/**
- * @cfg {Boolean} headerAsText
- * true
to display the panel {@link #title}
in the {@link #header}
,
- * false
to hide it (defaults to true
).
+ * @cfg {Boolean} hideEl
+ * False to keep the bound element visible while the editor is displayed (defaults to true)
*/
- headerAsText : true,
/**
- * @cfg {String} buttonAlign
- * The alignment of any {@link #buttons} added to this panel. Valid values are 'right'
,
- * 'left'
and 'center'
(defaults to 'right'
).
+ * @cfg {Mixed} value
+ * The data value of the underlying field (defaults to "")
*/
- buttonAlign : 'right',
+ value : "",
/**
- * @cfg {Boolean} collapsed
- * true
to render the panel collapsed, false
to render it expanded (defaults to
- * false
).
+ * @cfg {String} alignment
+ * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "c-c?").
*/
- collapsed : false,
+ alignment: "c-c?",
/**
- * @cfg {Boolean} collapseFirst
- * true
to make sure the collapse/expand toggle button always renders first (to the left of)
- * any other tools in the panel's title bar, false
to render it last (defaults to true
).
+ * @cfg {Array} offsets
+ * The offsets to use when aligning (see {@link Ext.Element#alignTo} for more details. Defaults to [0, 0].
*/
- collapseFirst : true,
+ offsets: [0, 0],
/**
- * @cfg {Number} minButtonWidth
- * Minimum width in pixels of all {@link #buttons} in this panel (defaults to 75
)
+ * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop"
+ * for bottom-right shadow (defaults to "frame")
*/
- minButtonWidth : 75,
+ shadow : "frame",
/**
- * @cfg {Boolean} unstyled
- * Overrides the {@link #baseCls}
setting to {@link #baseCls} = 'x-plain'
which renders
- * the panel unstyled except for required attributes for Ext layouts to function (e.g. overflow:hidden).
+ * @cfg {Boolean} constrain True to constrain the editor to the viewport
*/
+ constrain : false,
/**
- * @cfg {String} elements
- * A comma-delimited list of panel elements to initialize when the panel is rendered. Normally, this list will be
- * generated automatically based on the items added to the panel at config time, but sometimes it might be useful to
- * make sure a structural element is rendered even if not specified at config time (for example, you may want
- * to add a button or toolbar dynamically after the panel has been rendered). Adding those elements to this
- * list will allocate the required placeholders in the panel when it is rendered. Valid values are
- * header
- * tbar
(top bar)
- * body
- * bbar
(bottom bar)
- * footer
- *
- * Defaults to 'body
'.
+ * @cfg {Boolean} swallowKeys Handle the keydown/keypress events so they don't propagate (defaults to true)
*/
- elements : 'body',
+ swallowKeys : true,
/**
- * @cfg {Boolean} preventBodyReset
- * Defaults to false
. When set to true
, an extra css class 'x-panel-normal'
- * will be added to the panel's element, effectively applying css styles suggested by the W3C
- * (see http://www.w3.org/TR/CSS21/sample.html) to the Panel's body element (not the header,
- * footer, etc.).
+ * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed. Defaults to true.
*/
- preventBodyReset : false,
-
- /** @cfg {String} resizeEvent
- * The event to listen to for resizing in layouts. Defaults to 'bodyresize'.
+ completeOnEnter : true,
+ /**
+ * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed. Defaults to true.
*/
- resizeEvent: 'bodyresize',
-
- // protected - these could be used to customize the behavior of the window,
- // but changing them would not be useful without further mofifications and
- // could lead to unexpected or undesirable results.
- toolTarget : 'header',
- collapseEl : 'bwrap',
- slideAnchor : 't',
- disabledClass : '',
-
- // private, notify box this class will handle heights
- deferHeight : true,
- // private
- expandDefaults: {
- duration : 0.25
- },
- // private
- collapseDefaults : {
- duration : 0.25
- },
+ cancelOnEsc : true,
+ /**
+ * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false)
+ */
+ updateEl : false,
- // private
initComponent : function(){
- Ext.Panel.superclass.initComponent.call(this);
-
+ Ext.Editor.superclass.initComponent.call(this);
this.addEvents(
/**
- * @event bodyresize
- * Fires after the Panel has been resized.
- * @param {Ext.Panel} p the Panel which has been resized.
- * @param {Number} width The Panel's new width.
- * @param {Number} height The Panel's new height.
- */
- 'bodyresize',
- /**
- * @event titlechange
- * Fires after the Panel title has been {@link #title set} or {@link #setTitle changed}.
- * @param {Ext.Panel} p the Panel which has had its title changed.
- * @param {String} The new title.
- */
- 'titlechange',
- /**
- * @event iconchange
- * Fires after the Panel icon class has been {@link #iconCls set} or {@link #setIconClass changed}.
- * @param {Ext.Panel} p the Panel which has had its {@link #iconCls icon class} changed.
- * @param {String} The new icon class.
- * @param {String} The old icon class.
- */
- 'iconchange',
- /**
- * @event collapse
- * Fires after the Panel has been collapsed.
- * @param {Ext.Panel} p the Panel that has been collapsed.
- */
- 'collapse',
- /**
- * @event expand
- * Fires after the Panel has been expanded.
- * @param {Ext.Panel} p The Panel that has been expanded.
- */
- 'expand',
- /**
- * @event beforecollapse
- * Fires before the Panel is collapsed. A handler can return false to cancel the collapse.
- * @param {Ext.Panel} p the Panel being collapsed.
- * @param {Boolean} animate True if the collapse is animated, else false.
+ * @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
*/
- 'beforecollapse',
+ "beforestartedit",
/**
- * @event beforeexpand
- * Fires before the Panel is expanded. A handler can return false to cancel the expand.
- * @param {Ext.Panel} p The Panel being expanded.
- * @param {Boolean} animate True if the expand is animated, else false.
+ * @event 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
*/
- 'beforeexpand',
+ "startedit",
/**
- * @event beforeclose
- * Fires before the Panel is closed. Note that Panels do not directly support being closed, but some
- * Panel subclasses do (like {@link Ext.Window}) or a Panel within a Ext.TabPanel. This event only
- * applies to such subclasses.
- * A handler can return false to cancel the close.
- * @param {Ext.Panel} p The Panel being closed.
+ * @event 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
*/
- 'beforeclose',
+ "beforecomplete",
/**
- * @event close
- * Fires after the Panel is closed. Note that Panels do not directly support being closed, but some
- * Panel subclasses do (like {@link Ext.Window}) or a Panel within a Ext.TabPanel.
- * @param {Ext.Panel} p The Panel that has been closed.
+ * @event 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
*/
- 'close',
+ "complete",
/**
- * @event activate
- * Fires after the Panel has been visually activated.
- * Note that Panels do not directly support being activated, but some Panel subclasses
- * do (like {@link Ext.Window}). Panels which are child Components of a TabPanel fire the
- * activate and deactivate events under the control of the TabPanel.
- * @param {Ext.Panel} p The Panel that has been activated.
+ * @event 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
*/
- 'activate',
+ "canceledit",
/**
- * @event deactivate
- * Fires after the Panel has been visually deactivated.
- * Note that Panels do not directly support being deactivated, but some Panel subclasses
- * do (like {@link Ext.Window}). Panels which are child Components of a TabPanel fire the
- * activate and deactivate events under the control of the TabPanel.
- * @param {Ext.Panel} p The Panel that has been deactivated.
+ * @event 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
*/
- 'deactivate'
+ "specialkey"
);
+ },
- if(this.unstyled){
- this.baseCls = 'x-plain';
+ // private
+ onRender : function(ct, position){
+ this.el = new Ext.Layer({
+ shadow: this.shadow,
+ cls: "x-editor",
+ parentEl : ct,
+ shim : this.shim,
+ shadowOffset: this.shadowOffset || 4,
+ id: this.id,
+ constrain: this.constrain
+ });
+ if(this.zIndex){
+ this.el.setZIndex(this.zIndex);
}
-
- // shortcuts
- if(this.tbar){
- this.elements += ',tbar';
- if(Ext.isObject(this.tbar)){
- this.topToolbar = this.tbar;
- }
- delete this.tbar;
+ this.el.setStyle("overflow", Ext.isGecko ? "auto" : "hidden");
+ if(this.field.msgTarget != 'title'){
+ this.field.msgTarget = 'qtip';
}
- if(this.bbar){
- this.elements += ',bbar';
- if(Ext.isObject(this.bbar)){
- this.bottomToolbar = this.bbar;
- }
- delete this.bbar;
+ 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});
}
-
- if(this.header === true){
- this.elements += ',header';
- delete this.header;
- }else if(this.headerCfg || (this.title && this.header !== false)){
- this.elements += ',header';
+ this.field.render(this.el).show();
+ this.field.getEl().dom.name = '';
+ if(this.swallowKeys){
+ this.field.el.swallowEvent([
+ 'keypress', // *** Opera
+ 'keydown' // *** all other browsers
+ ]);
}
+ },
- if(this.footerCfg || this.footer === true){
- this.elements += ',footer';
- delete this.footer;
+ // 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();
+ }
}
+ this.fireEvent('specialkey', field, e);
+ },
- if(this.buttons){
- this.elements += ',footer';
- var btns = this.buttons;
- /**
- * This Panel's Array of buttons as created from the {@link #buttons}
- * config property. Read only.
- * @type Array
- * @property buttons
- */
- this.buttons = [];
- Ext.each(btns, function(btn){
- if(btn.render){ // button instance
- this.buttons.push(btn);
- }else if(btn.xtype){
- this.buttons.push(Ext.create(btn, 'button'));
- }else{
- this.addButton(btn);
- }
- }, 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();
}
- if(this.fbar){
- this.elements += ',footer';
+ this.boundEl = Ext.get(el);
+ var v = value !== undefined ? value : this.boundEl.dom.innerHTML;
+ if(!this.rendered){
+ this.render(this.parentEl || document.body);
}
- if(this.autoLoad){
- this.on('render', this.doAutoLoad, this, {delay:10});
+ 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();
}
},
// private
- createElement : function(name, pnode){
- if(this[name]){
- pnode.appendChild(this[name].dom);
- return;
+ doAutoSize : function(){
+ if(this.autoSize){
+ var sz = this.boundEl.getSize(),
+ fs = this.field.getSize();
+
+ switch(this.autoSize){
+ case "width":
+ this.setSize(sz.width, fs.height);
+ break;
+ case "height":
+ this.setSize(fs.width, sz.height);
+ break;
+ case "none":
+ this.setSize(fs.width, fs.height);
+ break;
+ default:
+ this.setSize(sz.width, sz.height);
+ }
}
+ },
- if(name === 'bwrap' || this.elements.indexOf(name) != -1){
- if(this[name+'Cfg']){
- this[name] = Ext.fly(pnode).createChild(this[name+'Cfg']);
- }else{
- var el = document.createElement('div');
- el.className = this[name+'Cls'];
- this[name] = Ext.get(pnode.appendChild(el));
+ /**
+ * Sets the height and width of this editor.
+ * @param {Number} width The new width
+ * @param {Number} height The new height
+ */
+ setSize : function(w, h){
+ delete this.field.lastSize;
+ this.field.setSize(w, h);
+ if(this.el){
+ // IE7 in strict mode doesn't size properly.
+ if(Ext.isGecko2 || Ext.isOpera || (Ext.isIE7 && Ext.isStrict)){
+ // prevent layer scrollbars
+ this.el.setSize(w, h);
}
- if(this[name+'CssClass']){
- this[name].addClass(this[name+'CssClass']);
+ this.el.sync();
+ }
+ },
+
+ /**
+ * 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);
+ },
+
+ /**
+ * 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;
+ }
+ // Assert combo values first
+ if (this.field.assertValue) {
+ this.field.assertValue();
+ }
+ var v = this.getValue();
+ if(!this.field.isValid()){
+ if(this.revertInvalid !== false){
+ this.cancelEdit(remainVisible);
}
- if(this[name+'Style']){
- this[name].applyStyles(this[name+'Style']);
+ return;
+ }
+ if(String(v) === String(this.startValue) && this.ignoreNoChange){
+ this.hideEdit(remainVisible);
+ return;
+ }
+ if(this.fireEvent("beforecomplete", this, v, this.startValue) !== false){
+ v = this.getValue();
+ if(this.updateEl && this.boundEl){
+ this.boundEl.update(v);
}
+ this.hideEdit(remainVisible);
+ this.fireEvent("complete", this, v, this.startValue);
}
},
// private
- onRender : function(ct, position){
- Ext.Panel.superclass.onRender.call(this, ct, position);
- this.createClasses();
+ onShow : function(){
+ this.el.show();
+ if(this.hideEl !== false){
+ this.boundEl.hide();
+ }
+ this.field.show().focus(false, true);
+ this.fireEvent("startedit", this.boundEl, this.startValue);
+ },
- var el = this.el,
- d = el.dom,
- bw,
- ts;
-
-
- if(this.collapsible && !this.hideCollapseTool){
- this.tools = this.tools ? this.tools.slice(0) : [];
- this.tools[this.collapseFirst?'unshift':'push']({
- id: 'toggle',
- handler : this.toggleCollapse,
- scope: this
- });
+ /**
+ * 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
+ hideEdit: function(remainVisible){
+ if(remainVisible !== true){
+ this.editing = false;
+ this.hide();
}
-
- if(this.tools){
- ts = this.tools;
- this.elements += (this.header !== false) ? ',header' : '';
+ },
+
+ // 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();
}
- this.tools = {};
-
- el.addClass(this.baseCls);
- if(d.firstChild){ // existing markup
- this.header = el.down('.'+this.headerCls);
- this.bwrap = el.down('.'+this.bwrapCls);
- var cp = this.bwrap ? this.bwrap : el;
- this.tbar = cp.down('.'+this.tbarCls);
- this.body = cp.down('.'+this.bodyCls);
- this.bbar = cp.down('.'+this.bbarCls);
- this.footer = cp.down('.'+this.footerCls);
- this.fromMarkup = true;
+ },
+
+ // private
+ onHide : function(){
+ if(this.editing){
+ this.completeEdit();
+ return;
}
- if (this.preventBodyReset === true) {
- el.addClass('x-panel-reset');
+ this.field.blur();
+ if(this.field.collapse){
+ this.field.collapse();
}
- if(this.cls){
- el.addClass(this.cls);
+ this.el.hide();
+ if(this.hideEl !== false){
+ this.boundEl.show();
}
+ },
- if(this.buttons){
- this.elements += ',footer';
- }
+ /**
+ * 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);
+ },
- // This block allows for maximum flexibility and performance when using existing markup
+ /**
+ * Gets the data value of the editor
+ * @return {Mixed} The data value
+ */
+ getValue : function(){
+ return this.field.getValue();
+ },
- // framing requires special markup
- if(this.frame){
- el.insertHtml('afterBegin', String.format(Ext.Element.boxMarkup, this.baseCls));
+ beforeDestroy : function(){
+ Ext.destroyMembers(this, 'field');
- this.createElement('header', d.firstChild.firstChild.firstChild);
- this.createElement('bwrap', d);
+ 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');
- // 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);
+cp.on('select', function(palette, selColor){
+ // do something with selColor
+});
+
+ * @constructor
+ * Create a new ColorPalette
+ * @param {Object} config The config object
+ * @xtype colorpalette
+ */
+Ext.ColorPalette = Ext.extend(Ext.Component, {
+ /**
+ * @cfg {String} tpl An existing XTemplate instance to be used in place of the default template for rendering the component.
+ */
+ /**
+ * @cfg {String} itemCls
+ * The CSS class to apply to the containing element (defaults to 'x-color-palette')
+ */
+ itemCls : 'x-color-palette',
+ /**
+ * @cfg {String} value
+ * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol). Note that
+ * the hex codes are case-sensitive.
+ */
+ value : null,
+ /**
+ * @cfg {String} clickEvent
+ * The DOM event that will cause a color to be selected. This can be any valid event name (dblclick, contextmenu).
+ * Defaults to 'click'.
+ */
+ clickEvent :'click',
+ // private
+ ctype : 'Ext.ColorPalette',
- var mc = bw.firstChild.firstChild.firstChild;
- this.createElement('tbar', mc);
- this.createElement('body', mc);
- this.createElement('bbar', mc);
- this.createElement('footer', bw.lastChild.firstChild.firstChild);
+ /**
+ * @cfg {Boolean} allowReselect If set to true then reselecting a color that is already selected fires the {@link #select} event
+ */
+ allowReselect : false,
- 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
+ /**
+ * 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
: ColorPaletteThe {@link #palette Ext.ColorPalette}.
+ * color
: StringThe 6-digit color hex code (without the # symbol).
+ *
+ */
+ /**
+ * @cfg {Object} scope
+ * The scope (this reference) in which the {@link #handler}
+ * function will be called. Defaults to this ColorPalette instance.
+ */
+
+ // private
+ initComponent : function(){
+ Ext.ColorPalette.superclass.initComponent.call(this);
+ this.addEvents(
+ /**
+ * @event select
+ * Fires when a color is selected
+ * @param {ColorPalette} this
+ * @param {String} color The 6-digit color hex code (without the # symbol)
*/
- this.ft = Ext.get(this.bwrap.dom.lastChild);
- this.mc = Ext.get(this.bwrap.dom.firstChild.firstChild.firstChild);
- }else{
- this.createElement('header', d);
- this.createElement('bwrap', d);
+ 'select'
+ );
- // 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.handler){
+ this.on('select', this.handler, this.scope, true);
+ }
+ },
- if(!this.header){
- this.body.addClass(this.bodyCls + '-noheader');
- if(this.tbar){
- this.tbar.addClass(this.tbarCls + '-noheader');
- }
- }
+ // private
+ onRender : function(container, position){
+ this.autoEl = {
+ tag: 'div',
+ cls: this.itemCls
+ };
+ Ext.ColorPalette.superclass.onRender.call(this, container, position);
+ var t = this.tpl || new Ext.XTemplate(
+ ' '
+ );
+ t.overwrite(this.el, this.colors);
+ this.mon(this.el, this.clickEvent, this.handleClick, this, {delegate: 'a'});
+ if(this.clickEvent != 'click'){
+ this.mon(this.el, 'click', Ext.emptyFn, this, {delegate: 'a', preventDefault: true});
}
+ },
- if(Ext.isDefined(this.padding)){
- this.body.setStyle('padding', this.body.addUnits(this.padding));
+ // private
+ afterRender : function(){
+ Ext.ColorPalette.superclass.afterRender.call(this);
+ if(this.value){
+ var s = this.value;
+ this.value = null;
+ this.select(s, true);
}
+ },
- if(this.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
+ handleClick : function(e, t){
+ e.preventDefault();
+ if(!this.disabled){
+ var c = t.className.match(/(?:^|\s)color-(.{6})(?:\s|$)/)[1];
+ this.select(c.toUpperCase());
+ }
+ },
+
+ /**
+ * Selects the specified color in the palette (fires the {@link #select} event)
+ * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
+ * @param {Boolean} suppressEvent (optional) True to stop the select event from firing. Defaults to false.
+ */
+ select : function(color, suppressEvent){
+ color = color.replace('#', '');
+ if(color != this.value || this.allowReselect){
+ var el = this.el;
+ if(this.value){
+ el.child('a.color-'+this.value).removeClass('x-color-palette-sel');
}
- if(this.bbar){
- this.bbar.addClass(this.bbarCls + '-noborder');
+ el.child('a.color-'+color).addClass('x-color-palette-sel');
+ this.value = color;
+ if(suppressEvent !== true){
+ this.fireEvent('select', this, color);
}
}
+ }
+
+ /**
+ * @cfg {String} autoEl @hide
+ */
+});
+Ext.reg('colorpalette', Ext.ColorPalette);/**
+ * @class Ext.DatePicker
+ * @extends Ext.Component
+ * A popup date picker. This class is used by the {@link Ext.form.DateField DateField} class
+ * to allow browsing and selection of valid dates.
+ * All the string values documented below may be overridden by including an Ext locale file in
+ * your page.
+ * @constructor
+ * Create a new DatePicker
+ * @param {Object} config The config object
+ * @xtype datepicker
+ */
+Ext.DatePicker = Ext.extend(Ext.BoxComponent, {
+ /**
+ * @cfg {String} todayText
+ * The text to display on the button that selects the current date (defaults to 'Today'
)
+ */
+ todayText : 'Today',
+ /**
+ * @cfg {String} okText
+ * The text to display on the ok button (defaults to ' OK '
to give the user extra clicking room)
+ */
+ okText : ' OK ',
+ /**
+ * @cfg {String} cancelText
+ * The text to display on the cancel button (defaults to 'Cancel'
)
+ */
+ cancelText : 'Cancel',
+ /**
+ * @cfg {Function} handler
+ * Optional. A function that will handle the select event of this picker.
+ * The handler is passed the following parameters:
+ * picker
: DatePickerThis DatePicker.
+ * date
: DateThe selected date.
+ *
+ */
+ /**
+ * @cfg {Object} scope
+ * The scope (this
reference) in which the {@link #handler}
+ * function will be called. Defaults to this DatePicker instance.
+ */
+ /**
+ * @cfg {String} todayTip
+ * A string used to format the message for displaying in a tooltip over the button that
+ * selects the current date. Defaults to '{0} (Spacebar)'
where
+ * the {0}
token is replaced by today's date.
+ */
+ todayTip : '{0} (Spacebar)',
+ /**
+ * @cfg {String} minText
+ * The error text to display if the minDate validation fails (defaults to 'This date is before the minimum date'
)
+ */
+ minText : 'This date is before the minimum date',
+ /**
+ * @cfg {String} maxText
+ * The error text to display if the maxDate validation fails (defaults to 'This date is after the maximum date'
)
+ */
+ maxText : 'This date is after the maximum date',
+ /**
+ * @cfg {String} format
+ * The default date format string which can be overriden for localization support. The format must be
+ * valid according to {@link Date#parseDate} (defaults to 'm/d/y'
).
+ */
+ format : 'm/d/y',
+ /**
+ * @cfg {String} disabledDaysText
+ * The tooltip to display when the date falls on a disabled day (defaults to 'Disabled'
)
+ */
+ disabledDaysText : 'Disabled',
+ /**
+ * @cfg {String} disabledDatesText
+ * The tooltip text to display when the date falls on a disabled date (defaults to 'Disabled'
)
+ */
+ disabledDatesText : 'Disabled',
+ /**
+ * @cfg {Array} monthNames
+ * An array of textual month names which can be overriden for localization support (defaults to Date.monthNames)
+ */
+ monthNames : Date.monthNames,
+ /**
+ * @cfg {Array} dayNames
+ * An array of textual day names which can be overriden for localization support (defaults to Date.dayNames)
+ */
+ dayNames : Date.dayNames,
+ /**
+ * @cfg {String} nextText
+ * The next month navigation button tooltip (defaults to 'Next Month (Control+Right)'
)
+ */
+ nextText : 'Next Month (Control+Right)',
+ /**
+ * @cfg {String} prevText
+ * The previous month navigation button tooltip (defaults to 'Previous Month (Control+Left)'
)
+ */
+ prevText : 'Previous Month (Control+Left)',
+ /**
+ * @cfg {String} monthYearText
+ * The header month selector tooltip (defaults to 'Choose a month (Control+Up/Down to move years)'
)
+ */
+ monthYearText : 'Choose a month (Control+Up/Down to move years)',
+ /**
+ * @cfg {Number} startDay
+ * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
+ */
+ startDay : 0,
+ /**
+ * @cfg {Boolean} showToday
+ * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar
+ * that selects the current date (defaults to true
).
+ */
+ showToday : true,
+ /**
+ * @cfg {Date} minDate
+ * Minimum allowable date (JavaScript date object, defaults to null)
+ */
+ /**
+ * @cfg {Date} maxDate
+ * Maximum allowable date (JavaScript date object, defaults to null)
+ */
+ /**
+ * @cfg {Array} disabledDays
+ * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
+ */
+ /**
+ * @cfg {RegExp} disabledDatesRE
+ * JavaScript regular expression used to disable a pattern of dates (defaults to null). The {@link #disabledDates}
+ * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
+ * disabledDates value.
+ */
+ /**
+ * @cfg {Array} disabledDates
+ * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular
+ * expression so they are very powerful. Some examples:
+ *
+ * - ['03/08/2003', '09/16/2003'] would disable those exact dates
+ * - ['03/08', '09/16'] would disable those days for every year
+ * - ['^03/08'] would only match the beginning (useful if you are using short years)
+ * - ['03/../2006'] would disable every day in March 2006
+ * - ['^03'] would disable every day in every March
+ *
+ * Note that the format of the dates included in the array should exactly match the {@link #format} config.
+ * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to
+ * escape the dot when restricting dates. For example: ['03\\.08\\.03'].
+ */
- if(this.bodyBorder === false){
- this.body.addClass(this.bodyCls + '-noborder');
- }
+ // private
+ // Set by other components to stop the picker focus being updated when the value changes.
+ focusOnSelect: true,
- this.bwrap.enableDisplayMode('block');
+ // 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
- if(this.header){
- this.header.unselectable();
+ // private
+ initComponent : function(){
+ Ext.DatePicker.superclass.initComponent.call(this);
- // for tools, we need to wrap any existing header markup
- if(this.headerAsText){
- this.header.dom.innerHTML =
- ''+this.header.dom.innerHTML+'';
+ this.value = this.value ?
+ this.value.clearTime(true) : new Date().clearTime();
- if(this.iconCls){
- this.setIconClass(this.iconCls);
- }
- }
- }
+ this.addEvents(
+ /**
+ * @event select
+ * Fires when a date is selected
+ * @param {DatePicker} this DatePicker
+ * @param {Date} date The selected date
+ */
+ 'select'
+ );
- if(this.floating){
- this.makeFloating(this.floating);
+ if(this.handler){
+ this.on('select', this.handler, this.scope || this);
}
- 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);
- }
+ this.initDisabledDays();
+ },
- if(this.buttons && this.buttons.length > 0){
- this.fbar = new Ext.Toolbar({
- items: this.buttons,
- toolbarCls: 'x-panel-fbar'
- });
- }
- this.toolbars = [];
- if(this.fbar){
- this.fbar = Ext.create(this.fbar, 'toolbar');
- this.fbar.enableOverflow = false;
- if(this.fbar.items){
- this.fbar.items.each(function(c){
- c.minWidth = c.minWidth || this.minButtonWidth;
- }, this);
- }
- this.fbar.toolbarCls = 'x-panel-fbar';
+ // private
+ initDisabledDays : function(){
+ if(!this.disabledDatesRE && this.disabledDates){
+ var dd = this.disabledDates,
+ len = dd.length - 1,
+ re = '(?:';
- var bct = this.footer.createChild({cls: 'x-panel-btns x-panel-btns-'+this.buttonAlign});
- this.fbar.ownerCt = this;
- this.fbar.render(bct);
- bct.createChild({cls:'x-clear'});
- this.toolbars.push(this.fbar);
+ 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 + ')');
}
+ },
- if(this.tbar && this.topToolbar){
- if(Ext.isArray(this.topToolbar)){
- this.topToolbar = new Ext.Toolbar(this.topToolbar);
- }else if(!this.topToolbar.events){
- this.topToolbar = Ext.create(this.topToolbar, 'toolbar');
- }
- this.topToolbar.ownerCt = this;
- this.topToolbar.render(this.tbar);
- this.toolbars.push(this.topToolbar);
- }
- if(this.bbar && this.bottomToolbar){
- if(Ext.isArray(this.bottomToolbar)){
- this.bottomToolbar = new Ext.Toolbar(this.bottomToolbar);
- }else if(!this.bottomToolbar.events){
- this.bottomToolbar = Ext.create(this.bottomToolbar, 'toolbar');
- }
- this.bottomToolbar.ownerCt = this;
- this.bottomToolbar.render(this.bbar);
- this.toolbars.push(this.bottomToolbar);
+ /**
+ * Replaces any existing disabled dates with new values and refreshes the DatePicker.
+ * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config
+ * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
+ */
+ setDisabledDates : function(dd){
+ if(Ext.isArray(dd)){
+ this.disabledDates = dd;
+ this.disabledDatesRE = null;
+ }else{
+ this.disabledDatesRE = dd;
}
+ this.initDisabledDays();
+ this.update(this.value, true);
},
/**
- * Sets the CSS class that provides the icon image for this panel. This method will replace any existing
- * icon class if one has already been set and fire the {@link #iconchange} event after completion.
- * @param {String} cls The new CSS class name
+ * Replaces any existing disabled 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.
*/
- 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
- });
- }
- }
- }
- this.fireEvent('iconchange', this, cls, old);
+ setDisabledDays : function(dd){
+ this.disabledDays = dd;
+ this.update(this.value, true);
},
- // private
- makeFloating : function(cfg){
- this.floating = true;
- this.el = new Ext.Layer(
- Ext.isObject(cfg) ? cfg : {
- shadow: Ext.isDefined(this.shadow) ? this.shadow : 'sides',
- shadowOffset: this.shadowOffset,
- constrain:false,
- shim: this.shim === false ? false : undefined
- }, this.el
- );
+ /**
+ * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
+ * @param {Date} value The minimum date that can be selected
+ */
+ setMinDate : function(dt){
+ this.minDate = dt;
+ this.update(this.value, true);
},
/**
- * Returns the {@link Ext.Toolbar toolbar} from the top ({@link #tbar}
) section of the panel.
- * @return {Ext.Toolbar} The toolbar
+ * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
+ * @param {Date} value The maximum date that can be selected
*/
- getTopToolbar : function(){
- return this.topToolbar;
+ setMaxDate : function(dt){
+ this.maxDate = dt;
+ this.update(this.value, true);
},
/**
- * Returns the {@link Ext.Toolbar toolbar} from the bottom ({@link #bbar}
) section of the panel.
- * @return {Ext.Toolbar} The toolbar
+ * Sets the value of the date field
+ * @param {Date} value The date to set
*/
- getBottomToolbar : function(){
- return this.bottomToolbar;
+ setValue : function(value){
+ this.value = value.clearTime(true);
+ this.update(this.value);
},
/**
- * Adds a button to this panel. Note that this method must be called prior to rendering. The preferred
- * approach is to add buttons via the {@link #buttons} config.
- * @param {String/Object} config A valid {@link Ext.Button} config. A string will become the text for a default
- * button config, an object will be treated as a button config object.
- * @param {Function} handler The function to be called on button {@link Ext.Button#click}
- * @param {Object} scope The scope to use for the button handler function
- * @return {Ext.Button} The button that was added
+ * Gets the current selected value of the date field
+ * @return {Date} The selected date
*/
- addButton : function(config, handler, scope){
- var bc = {
- handler: handler,
- scope: scope,
- minWidth: this.minButtonWidth,
- hideParent:true
- };
- if(Ext.isString(config)){
- bc.text = config;
- }else{
- Ext.apply(bc, config);
+ getValue : function(){
+ return this.value;
+ },
+
+ // 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();
}
- var btn = new Ext.Button(bc);
- if(!this.buttons){
- this.buttons = [];
+
+ },
+
+ // 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();
+ });
}
- this.buttons.push(btn);
- return btn;
},
// private
- addTool : function(){
- if(!this.rendered){
- if(!this.tools){
- this.tools = [];
- }
- Ext.each(arguments, function(arg){
- this.tools.push(arg)
- }, this);
- return;
+ 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);
}
- // nowhere to render tools!
- if(!this[this.toolTarget]){
- return;
+ },
+
+ // private
+ onRender : function(container, position){
+ var m = [
+ ''];
+ for(var i = 0; i < 6; i++){
+ buf.push(
+ '', Date.getShortMonthName(i), ' ',
+ '', Date.getShortMonthName(i + 6), ' ',
+ i === 0 ?
+ ' ' :
+ ' '
+ );
+ }
+ buf.push(
+ ' ',
+ '
'
+ );
+ this.monthPicker.update(buf.join(''));
+
+ this.mon(this.monthPicker, 'click', this.onMonthClick, this);
+ this.mon(this.monthPicker, 'dblclick', this.onMonthDblClick, this);
+
+ this.mpMonths = this.monthPicker.select('td.x-date-mp-month');
+ this.mpYears = this.monthPicker.select('td.x-date-mp-year');
+
+ this.mpMonths.each(function(m, a, i){
+ i += 1;
+ if((i%2) === 0){
+ m.dom.xmonth = 5 + Math.round(i * 0.5);
+ }else{
+ m.dom.xmonth = Math.round((i-1) * 0.5);
+ }
});
- this.syncHeight();
}
},
- syncHeight : function(){
- var h = this.toolbarHeight,
- bd = this.body,
- lsh = this.lastSize.height;
-
- if(this.autoHeight || !Ext.isDefined(lsh) || lsh == 'auto'){
- return;
- }
-
-
- 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
+ showMonthPicker : function(){
+ if(!this.disabled){
+ this.createMonthPicker();
+ var size = this.el.getSize();
+ this.monthPicker.setSize(size);
+ this.monthPicker.child('table').setSize(size);
+
+ this.mpSelMonth = (this.activeDate || this.value).getMonth();
+ this.updateMPMonth(this.mpSelMonth);
+ this.mpSelYear = (this.activeDate || this.value).getFullYear();
+ this.updateMPYear(this.mpSelYear);
+
+ this.monthPicker.slideIn('t', {duration:0.2});
}
},
// private
- onShow : function(){
- if(this.floating){
- return this.el.show();
+ updateMPYear : function(y){
+ this.mpyear = y;
+ var ys = this.mpYears.elements;
+ for(var i = 1; i <= 10; i++){
+ var td = ys[i-1], y2;
+ if((i%2) === 0){
+ y2 = y + Math.round(i * 0.5);
+ td.firstChild.innerHTML = y2;
+ td.xyear = y2;
+ }else{
+ y2 = y - (5-Math.round(i * 0.5));
+ td.firstChild.innerHTML = y2;
+ td.xyear = y2;
+ }
+ this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel');
}
- Ext.Panel.superclass.onShow.call(this);
},
// private
- onHide : function(){
- if(this.floating){
- return this.el.hide();
- }
- Ext.Panel.superclass.onHide.call(this);
+ updateMPMonth : function(sm){
+ this.mpMonths.each(function(m, a, i){
+ m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel');
+ });
},
// 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);
- }
- };
+ selectMPMonth : function(m){
+
},
// private
- afterRender : function(){
- if(this.floating && !this.hidden){
- this.el.show();
+ onMonthClick : function(e, t){
+ e.stopEvent();
+ var el = new Ext.Element(t), pn;
+ if(el.is('button.x-date-mp-cancel')){
+ this.hideMonthPicker();
}
- if(this.title){
- this.setTitle(this.title);
+ 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();
}
- this.setAutoScroll();
- if(this.html){
- this.body.update(Ext.isObject(this.html) ?
- Ext.DomHelper.markup(this.html) :
- this.html);
- delete this.html;
+ 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;
}
- if(this.contentEl){
- var ce = Ext.getDom(this.contentEl);
- Ext.fly(ce).removeClass(['x-hidden', 'x-hide-display']);
- this.body.dom.appendChild(ce);
+ 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;
}
- if(this.collapsed){
- this.collapsed = false;
- this.collapse(false);
+ 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);
}
- Ext.Panel.superclass.afterRender.call(this); // do sizing calcs last
- this.initEvents();
},
// private
- setAutoScroll : function(){
- if(this.rendered && this.autoScroll){
- var el = this.body || this.el;
- if(el){
- el.setOverflow('auto');
- }
+ onMonthDblClick : function(e, t){
+ e.stopEvent();
+ var el = new Ext.Element(t), pn;
+ if((pn = el.up('td.x-date-mp-month', 2))){
+ this.update(new Date(this.mpSelYear, pn.dom.xmonth, (this.activeDate || this.value).getDate()));
+ this.hideMonthPicker();
+ }
+ else if((pn = el.up('td.x-date-mp-year', 2))){
+ this.update(new Date(pn.dom.xyear, this.mpSelMonth, (this.activeDate || this.value).getDate()));
+ this.hideMonthPicker();
}
},
// private
- getKeyMap : function(){
- if(!this.keyMap){
- this.keyMap = new Ext.KeyMap(this.el, this.keys);
+ hideMonthPicker : function(disableAnim){
+ if(this.monthPicker){
+ if(disableAnim === true){
+ this.monthPicker.hide();
+ }else{
+ this.monthPicker.slideOut('t', {duration:0.2});
+ }
}
- return this.keyMap;
},
// private
- initEvents : function(){
- if(this.keys){
- this.getKeyMap();
- }
- if(this.draggable){
- this.initDraggable();
- }
- if(this.toolbars.length > 0){
- Ext.each(this.toolbars, function(tb){
- tb.doLayout();
- tb.on({
- scope: this,
- afterlayout: this.syncHeight,
- remove: this.syncHeight
- });
- }, this);
- if(!this.ownerCt){
- this.syncHeight();
- }
- }
+ showPrevMonth : function(e){
+ this.update(this.activeDate.add('mo', -1));
},
// 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);
+ showNextMonth : function(e){
+ this.update(this.activeDate.add('mo', 1));
},
// private
- beforeEffect : function(anim){
- if(this.floating){
- this.el.beforeAction();
- }
- if(anim !== false){
- this.el.addClass('x-panel-animated');
- }
+ showPrevYear : function(){
+ this.update(this.activeDate.add('y', -1));
},
// private
- afterEffect : function(anim){
- this.syncShadow();
- if(anim !== false){
- this.el.removeClass('x-panel-animated');
+ showNextYear : function(){
+ this.update(this.activeDate.add('y', 1));
+ },
+
+ // private
+ handleMouseWheel : function(e){
+ e.stopEvent();
+ if(!this.disabled){
+ var delta = e.getWheelDelta();
+ if(delta > 0){
+ this.showPrevMonth();
+ } else if(delta < 0){
+ this.showNextMonth();
+ }
}
},
- // private - 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
+ 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);
}
- return Ext.applyIf(o, a);
},
- /**
- * Collapses the panel body so that it becomes hidden. Fires the {@link #beforecollapse} event which will
- * cancel the collapse action if it returns false.
- * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
- * {@link #animCollapse} panel config)
- * @return {Ext.Panel} this
- */
- collapse : function(animate){
- if(this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforecollapse', this, animate) === false){
- return;
+ // private
+ selectToday : function(){
+ if(this.todayBtn && !this.todayBtn.disabled){
+ this.setValue(new Date().clearTime());
+ this.fireEvent('select', this, this.value);
}
- 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.afterCollapse(false);
+ update : function(date, forceRefresh){
+ if(this.rendered){
+ var vd = this.activeDate, vis = this.isVisible();
+ this.activeDate = date;
+ if(!forceRefresh && vd && this.el){
+ var t = date.getTime();
+ if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
+ this.cells.removeClass('x-date-selected');
+ this.cells.each(function(c){
+ if(c.dom.firstChild.dateValue == t){
+ c.addClass('x-date-selected');
+ if(vis && !this.cancelFocus){
+ Ext.fly(c.dom.firstChild).focus(50);
+ }
+ return false;
+ }
+ }, this);
+ return;
+ }
+ }
+ var days = date.getDaysInMonth(),
+ firstOfMonth = date.getFirstDateOfMonth(),
+ startingPos = firstOfMonth.getDay()-this.startDay;
+
+ if(startingPos < 0){
+ startingPos += 7;
+ }
+ days += startingPos;
+
+ var pm = date.add('mo', -1),
+ prevStart = pm.getDaysInMonth()-startingPos,
+ cells = this.cells.elements,
+ textEls = this.textNodes,
+ // convert everything to numbers so it's fast
+ d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart, this.initHour)),
+ today = new Date().clearTime().getTime(),
+ sel = date.clearTime(true).getTime(),
+ min = this.minDate ? this.minDate.clearTime(true) : Number.NEGATIVE_INFINITY,
+ max = this.maxDate ? this.maxDate.clearTime(true) : Number.POSITIVE_INFINITY,
+ ddMatch = this.disabledDatesRE,
+ ddText = this.disabledDatesText,
+ ddays = this.disabledDays ? this.disabledDays.join('') : false,
+ ddaysText = this.disabledDaysText,
+ format = this.format;
+
+ if(this.showToday){
+ var td = new Date().clearTime(),
+ disable = (td < min || td > max ||
+ (ddMatch && format && ddMatch.test(td.dateFormat(format))) ||
+ (ddays && ddays.indexOf(td.getDay()) != -1));
+
+ if(!this.disabled){
+ this.todayBtn.setDisabled(disable);
+ this.todayKeyListener[disable ? 'disable' : 'enable']();
+ }
+ }
+
+ var setCellClass = function(cal, cell){
+ cell.title = '';
+ var t = d.clearTime(true).getTime();
+ cell.firstChild.dateValue = t;
+ if(t == today){
+ cell.className += ' x-date-today';
+ cell.title = cal.todayText;
+ }
+ if(t == sel){
+ cell.className += ' x-date-selected';
+ if(vis){
+ Ext.fly(cell.firstChild).focus(50);
+ }
+ }
+ // disabling
+ if(t < min) {
+ cell.className = ' x-date-disabled';
+ cell.title = cal.minText;
+ return;
+ }
+ if(t > max) {
+ cell.className = ' x-date-disabled';
+ cell.title = cal.maxText;
+ return;
+ }
+ if(ddays){
+ if(ddays.indexOf(d.getDay()) != -1){
+ cell.title = ddaysText;
+ cell.className = ' x-date-disabled';
+ }
+ }
+ if(ddMatch && format){
+ var fvalue = d.dateFormat(format);
+ if(ddMatch.test(fvalue)){
+ cell.title = ddText.replace('%0', fvalue);
+ cell.className = ' x-date-disabled';
+ }
+ }
+ };
+
+ var i = 0;
+ for(; i < startingPos; i++) {
+ textEls[i].innerHTML = (++prevStart);
+ d.setDate(d.getDate()+1);
+ cells[i].className = 'x-date-prevday';
+ setCellClass(this, cells[i]);
+ }
+ for(; i < days; i++){
+ var intDay = i - startingPos + 1;
+ textEls[i].innerHTML = (intDay);
+ d.setDate(d.getDate()+1);
+ cells[i].className = 'x-date-active';
+ setCellClass(this, cells[i]);
+ }
+ var extraDays = 0;
+ for(; i < 42; i++) {
+ textEls[i].innerHTML = (++extraDays);
+ d.setDate(d.getDate()+1);
+ cells[i].className = 'x-date-nextday';
+ setCellClass(this, cells[i]);
+ }
+
+ this.mbtn.setText(this.monthNames[date.getMonth()] + ' ' + date.getFullYear());
+
+ if(!this.internalRender){
+ var main = this.el.dom.firstChild,
+ w = main.offsetWidth;
+ this.el.setWidth(w + this.el.getBorderWidth('lr'));
+ Ext.fly(main).setWidth(w);
+ this.internalRender = true;
+ // opera does not respect the auto grow header center column
+ // then, after it gets a width opera refuses to recalculate
+ // without a second pass
+ if(Ext.isOpera && !this.secondPass){
+ main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + 'px';
+ this.secondPass = true;
+ this.update.defer(10, this, [date]);
+ }
+ }
}
},
// private
- afterCollapse : function(anim){
- this.collapsed = true;
- this.el.addClass(this.collapsedCls);
- this.afterEffect(anim);
- this.fireEvent('collapse', this);
+ beforeDestroy : function() {
+ if(this.rendered){
+ Ext.destroy(
+ this.keyNav,
+ this.monthPicker,
+ this.eventEl,
+ this.mbtn,
+ this.nextRepeater,
+ this.prevRepeater,
+ this.cells.el,
+ this.todayBtn
+ );
+ delete this.textNodes;
+ delete this.cells.elements;
+ }
+ }
+
+ /**
+ * @cfg {String} autoEl @hide
+ */
+});
+
+Ext.reg('datepicker', Ext.DatePicker);
+/**
+ * @class Ext.LoadMask
+ * A simple utility class for generically masking elements while loading data. If the {@link #store}
+ * config option is specified, the masking will be automatically synchronized with the store's loading
+ * process and the mask element will be cached for reuse. For all other elements, this mask will replace the
+ * element's Updater load indicator and will be destroyed after the initial load.
+ * Example usage:
+ *
+// Basic mask:
+var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
+myMask.show();
+
+ * @constructor
+ * Create a new LoadMask
+ * @param {Mixed} el The element or DOM node, or its id
+ * @param {Object} config The config object
+ */
+Ext.LoadMask = function(el, config){
+ this.el = Ext.get(el);
+ Ext.apply(this, config);
+ if(this.store){
+ this.store.on({
+ scope: this,
+ beforeload: this.onBeforeLoad,
+ load: this.onLoad,
+ exception: this.onLoad
+ });
+ this.removeMask = Ext.value(this.removeMask, false);
+ }else{
+ var um = this.el.getUpdater();
+ um.showLoadIndicator = false; // disable the default indicator
+ um.on({
+ scope: this,
+ beforeupdate: this.onBeforeLoad,
+ update: this.onLoad,
+ failure: this.onLoad
+ });
+ this.removeMask = Ext.value(this.removeMask, true);
+ }
+};
+
+Ext.LoadMask.prototype = {
+ /**
+ * @cfg {Ext.data.Store} store
+ * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
+ * hidden on either load sucess, or load fail.
+ */
+ /**
+ * @cfg {Boolean} removeMask
+ * True to create a single-use mask that is automatically destroyed after loading (useful for page loads),
+ * False to persist the mask element reference for multiple uses (e.g., for paged data widgets). Defaults to false.
+ */
+ /**
+ * @cfg {String} msg
+ * The text to display in a centered loading message box (defaults to 'Loading...')
+ */
+ msg : 'Loading...',
+ /**
+ * @cfg {String} msgCls
+ * The CSS class to apply to the loading message element (defaults to "x-mask-loading")
+ */
+ msgCls : 'x-mask-loading',
+
+ /**
+ * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false)
+ * @type Boolean
+ */
+ disabled: false,
+
+ /**
+ * Disables the mask to prevent it from being displayed
+ */
+ disable : function(){
+ this.disabled = true;
},
/**
- * 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
+ * Enables the mask so that it can be displayed
*/
- 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;
+ enable : function(){
+ this.disabled = false;
},
// private
- onExpand : function(doAnim, animArg){
- if(doAnim){
- this[this.collapseEl].slideIn(this.slideAnchor,
- Ext.apply(this.createEffect(animArg||true, this.afterExpand, this),
- this.expandDefaults));
- }else{
- this[this.collapseEl].show();
- this.afterExpand(false);
- }
+ onLoad : function(){
+ this.el.unmask(this.removeMask);
},
// private
- afterExpand : function(anim){
- this.collapsed = false;
- this.afterEffect(anim);
- if(Ext.isDefined(this.deferLayout)){
- this.doLayout(true);
+ onBeforeLoad : function(){
+ if(!this.disabled){
+ this.el.mask(this.msg, this.msgCls);
}
- this.fireEvent('expand', this);
},
/**
- * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel.
- * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
- * {@link #animCollapse} panel config)
- * @return {Ext.Panel} this
+ * Show this LoadMask over the configured Element.
*/
- toggleCollapse : function(animate){
- this[this.collapsed ? 'expand' : 'collapse'](animate);
- return this;
+ show: function(){
+ this.onBeforeLoad();
},
- // private
- onDisable : function(){
- if(this.rendered && this.maskDisabled){
- this.el.mask();
- }
- Ext.Panel.superclass.onDisable.call(this);
+ /**
+ * Hide this LoadMask.
+ */
+ hide: function(){
+ this.onLoad();
},
// private
- onEnable : function(){
- if(this.rendered && this.maskDisabled){
- this.el.unmask();
+ 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.Panel.superclass.onEnable.call(this);
- },
-
- // private
- onResize : function(w, h){
- if(Ext.isDefined(w) || Ext.isDefined(h)){
- if(!this.collapsed){
- if(Ext.isNumber(w)){
- w = this.adjustBodyWidth(w - this.getFrameWidth());
- 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);
- }
- }
- if(this.fbar){
- var f = this.fbar,
- fWidth = 1,
- strict = Ext.isStrict;
- if(this.buttonAlign == 'left'){
- fWidth = w - f.container.getFrameWidth('lr');
- }else{
- //center/right alignment off in webkit
- if(Ext.isIE || Ext.isWebKit){
- //center alignment ok on webkit.
- //right broken in both, center on IE
- if(!(this.buttonAlign == 'center' && Ext.isWebKit) && (!strict || (!Ext.isIE8 && strict))){
- (function(){
- f.setWidth(f.getEl().child('.x-toolbar-ct').getWidth());
- }).defer(1);
- }else{
- fWidth = 'auto';
- }
- }else{
- fWidth = 'auto';
- }
- }
- f.setWidth(fWidth);
- }
- this.body.setWidth(w);
- }else if(w == 'auto'){
- this.body.setWidth(w);
- }
-
- 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);
- }
+ }
+};Ext.ns('Ext.slider');
- if(this.disabled && this.el._mask){
- this.el._mask.setSize(this.el.dom.clientWidth, this.el.getHeight());
- }
- }else{
- this.queuedBodySize = {width: w, height: h};
- if(!this.queuedExpand && this.allowQueuedExpand !== false){
- this.queuedExpand = true;
- this.on('expand', function(){
- delete this.queuedExpand;
- this.onResize(this.queuedBodySize.width, this.queuedBodySize.height);
- this.doLayout();
- }, this, {single:true});
- }
- }
- this.onBodyResize(w, h);
- }
- this.syncShadow();
- },
-
- // private
- onBodyResize: function(w, h){
- this.fireEvent('bodyresize', this, w, h);
- },
+/**
+ * @class Ext.slider.Thumb
+ * @extends Object
+ * Represents a single thumb element on a Slider. This would not usually be created manually and would instead
+ * be created internally by an {@link Ext.slider.MultiSlider Ext.Slider}.
+ */
+Ext.slider.Thumb = Ext.extend(Object, {
- // private
- getToolbarHeight: function(){
- var h = 0;
- if(this.rendered){
- Ext.each(this.toolbars, function(tb){
- h += tb.getHeight();
- }, this);
- }
- return h;
- },
+ /**
+ * True while the thumb is in a drag operation
+ * @type Boolean
+ */
+ dragging: false,
- // private
- adjustBodyHeight : function(h){
- return h;
- },
+ /**
+ * @constructor
+ * @cfg {Ext.slider.MultiSlider} slider The Slider to render to (required)
+ */
+ constructor: function(config) {
+ /**
+ * @property slider
+ * @type Ext.slider.MultiSlider
+ * The slider this thumb is contained within
+ */
+ Ext.apply(this, config || {}, {
+ cls: 'x-slider-thumb',
- // private
- adjustBodyWidth : function(w){
- return w;
- },
+ /**
+ * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings
+ */
+ constrain: false
+ });
- // private
- onPosition : function(){
- this.syncShadow();
+ Ext.slider.Thumb.superclass.constructor.call(this, config);
+
+ if (this.slider.vertical) {
+ Ext.apply(this, Ext.slider.Thumb.Vertical);
+ }
},
/**
- * 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
+ * Renders the thumb into a slider
*/
- getFrameWidth : function(){
- var w = this.el.getFrameWidth('lr') + this.bwrap.getFrameWidth('lr');
+ render: function() {
+ this.el = this.slider.innerEl.insertFirst({cls: this.cls});
- if(this.frame){
- var l = this.bwrap.dom.firstChild;
- w += (Ext.fly(l).getFrameWidth('l') + Ext.fly(l.firstChild).getFrameWidth('r'));
- w += this.mc.getFrameWidth('lr');
- }
- return w;
+ this.initEvents();
},
/**
- * 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
+ * Enables the thumb if it is currently disabled
*/
- 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;
+ enable: function() {
+ this.disabled = false;
+ this.el.removeClass(this.slider.disabledClass);
},
/**
- * 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
+ * Disables the thumb if it is currently enabled
*/
- getInnerWidth : function(){
- return this.getSize().width - this.getFrameWidth();
+ disable: function() {
+ this.disabled = true;
+ this.el.addClass(this.slider.disabledClass);
},
/**
- * 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
+ * Sets up an Ext.dd.DragTracker for this thumb
*/
- getInnerHeight : function(){
- return this.getSize().height - this.getFrameHeight();
- },
+ initEvents: function() {
+ var el = this.el;
- // private
- syncShadow : function(){
- if(this.floating){
- this.el.sync(true);
- }
- },
+ el.addClassOnOver('x-slider-thumb-over');
- // private
- getLayoutTarget : function(){
- return this.body;
+ this.tracker = new Ext.dd.DragTracker({
+ onBeforeStart: this.onBeforeDragStart.createDelegate(this),
+ onStart : this.onDragStart.createDelegate(this),
+ onDrag : this.onDrag.createDelegate(this),
+ onEnd : this.onDragEnd.createDelegate(this),
+ tolerance : 3,
+ autoStart : 300
+ });
+
+ this.tracker.initEl(el);
},
/**
- * Sets the title text for the panel and optionally the {@link #iconCls icon class}.
- * In order to be able to set the title, a header element must have been created
- * for the Panel. This is triggered either by configuring the Panel with a non-blank {@link #title}
,
- * or configuring it with {@link #header}: true
.
- * @param {String} title The title text to set
- * @param {String} iconCls (optional) {@link #iconCls iconCls} A user-defined CSS class that provides the icon image for this panel
+ * @private
+ * This is tied into the internal Ext.dd.DragTracker. If the slider is currently disabled,
+ * this returns false to disable the DragTracker too.
+ * @return {Boolean} False if the slider is currently disabled
*/
- setTitle : function(title, iconCls){
- this.title = title;
- if(this.header && this.headerAsText){
- this.header.child('span').update(title);
- }
- if(iconCls){
- this.setIconClass(iconCls);
+ onBeforeDragStart : function(e) {
+ if (this.disabled) {
+ return false;
+ } else {
+ this.slider.promoteThumb(this);
+ return true;
}
- this.fireEvent('titlechange', this, title);
- return this;
},
/**
- * Get the {@link Ext.Updater} for this panel. Enables you to perform Ajax updates of this panel's body.
- * @return {Ext.Updater} The Updater
+ * @private
+ * This is tied into the internal Ext.dd.DragTracker's onStart template method. Adds the drag CSS class
+ * to the thumb and fires the 'dragstart' event
*/
- getUpdater : function(){
- return this.body.getUpdater();
- },
+ onDragStart: function(e){
+ this.el.addClass('x-slider-thumb-drag');
+ this.dragging = true;
+ this.dragStartValue = this.value;
- /**
- * Loads this content panel immediately with content returned from an XHR call.
- * @param {Object/String/Function} config A config object containing any of the following options:
-
-panel.load({
- url: 'your-url.php',
- params: {param1: 'foo', param2: 'bar'}, // or a URL encoded string
- callback: yourFunction,
- scope: yourObject, // optional scope for the callback
- discardUrl: false,
- nocache: false,
- text: 'Loading...',
- timeout: 30,
- scripts: false
-});
-
- * The only required property is url. The optional properties nocache, text and scripts
- * are shorthand for disableCaching, indicatorText and loadScripts and are used to set their
- * associated property on this panel Updater instance.
- * @return {Ext.Panel} this
- */
- load : function(){
- var um = this.body.getUpdater();
- um.update.apply(um, arguments);
- return this;
+ this.slider.fireEvent('dragstart', this.slider, e, this);
},
- // private
- beforeDestroy : function(){
- if(this.header){
- this.header.removeAllListeners();
- if(this.headerAsText){
- Ext.Element.uncache(this.header.child('span'));
- }
- }
- Ext.Element.uncache(
- this.ft,
- this.mc,
- this.header,
- this.tbar,
- this.bbar,
- this.footer,
- this.body,
- this.bwrap
- );
- if(this.tools){
- for(var k in this.tools){
- Ext.destroy(this.tools[k]);
- }
- }
- if(this.buttons){
- for(var b in this.buttons){
- Ext.destroy(this.buttons[b]);
- }
- }
- if(this.rendered){
- Ext.destroy(this.toolbars);
- }else{
- Ext.destroy(this.topToolbar, this.bottomToolbar);
+ /**
+ * @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
+ */
+ 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;
}
- Ext.Panel.superclass.beforeDestroy.call(this);
- },
- // private
- createClasses : function(){
- this.headerCls = this.baseCls + '-header';
- this.headerTextCls = this.baseCls + '-header-text';
- this.bwrapCls = this.baseCls + '-bwrap';
- this.tbarCls = this.baseCls + '-tbar';
- this.bodyCls = this.baseCls + '-body';
- this.bbarCls = this.baseCls + '-bbar';
- this.footerCls = this.baseCls + '-footer';
+ slider.setValue(index, newValue, false);
+ slider.fireEvent('drag', slider, e, this);
},
- // private
- createGhost : function(cls, useShim, appendTo){
- var el = document.createElement('div');
- el.className = 'x-panel-ghost ' + (cls ? cls : '');
- if(this.header){
- el.appendChild(this.el.dom.firstChild.cloneNode(true));
- }
- Ext.fly(el.appendChild(document.createElement('ul'))).setHeight(this.bwrap.getHeight());
- el.style.width = this.el.dom.offsetWidth + 'px';;
- if(!appendTo){
- this.container.dom.appendChild(el);
- }else{
- Ext.getDom(appendTo).appendChild(el);
- }
- if(useShim !== false && this.el.useShim !== false){
- var layer = new Ext.Layer({shadow:false, useDisplay:true, constrain:false}, el);
- layer.show();
- return layer;
- }else{
- return new Ext.Element(el);
- }
+ getNewValue: function() {
+ var slider = this.slider,
+ pos = slider.innerEl.translatePoints(this.tracker.getXY());
+
+ return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision);
},
- // private
- doAutoLoad : function(){
- var u = this.body.getUpdater();
- if(this.renderer){
- u.setRenderer(this.renderer);
+ /**
+ * @private
+ * This is tied to the internal Ext.dd.DragTracker's onEnd template method. Removes the drag CSS class and
+ * fires the 'changecomplete' event with the new value
+ */
+ onDragEnd: function(e) {
+ var slider = this.slider,
+ value = this.value;
+
+ this.el.removeClass('x-slider-thumb-drag');
+
+ this.dragging = false;
+ slider.fireEvent('dragend', slider, e);
+
+ if (this.dragStartValue != value) {
+ slider.fireEvent('changecomplete', slider, value, this);
}
- u.update(Ext.isObject(this.autoLoad) ? this.autoLoad : {url: this.autoLoad});
},
-
+
/**
- * Retrieve a tool by id.
- * @param {String} id
- * @return {Object} tool
+ * @private
+ * Destroys the thumb
*/
- getTool : function(id) {
- return this.tools[id];
+ destroy: function(){
+ Ext.destroyMembers(this, 'tracker', 'el');
}
+});
/**
- * @cfg {String} autoEl @hide
- */
+ * @class Ext.slider.MultiSlider
+ * @extends Ext.BoxComponent
+ * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking and animation. Can be added as an item to any container. Example usage:
+
+new Ext.Slider({
+ renderTo: Ext.getBody(),
+ width: 200,
+ value: 50,
+ increment: 10,
+ minValue: 0,
+ maxValue: 100
});
-Ext.reg('panel', Ext.Panel);
-/**
- * @class Ext.Editor
- * @extends Ext.Component
- * A base editor field that handles displaying/hiding on demand and has some built-in sizing and event handling logic.
- * @constructor
- * Create a new Editor
- * @param {Object} config The config object
- * @xtype editor
- */
-Ext.Editor = function(field, config){
- if(field.field){
- this.field = Ext.create(field.field, 'textfield');
- config = Ext.apply({}, field); // copy so we don't disturb original config
- delete config.field;
- }else{
- this.field = field;
- }
- Ext.Editor.superclass.constructor.call(this, config);
-};
+
+ * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
+
+new Ext.Slider({
+ renderTo: Ext.getBody(),
+ width: 200,
+ values: [25, 50, 75],
+ minValue: 0,
+ maxValue: 100,
-Ext.extend(Ext.Editor, Ext.Component, {
- /**
- * @cfg {Ext.form.Field} field
- * The Field object (or descendant) or config object for field
- */
- /**
- * @cfg {Boolean} allowBlur
- * True to {@link #completeEdit complete the editing process} if in edit mode when the
- * field is blurred. Defaults to false.
- */
+ //this defaults to true, setting to false allows the thumbs to pass each other
+ {@link #constrainThumbs}: false
+});
+
+ */
+Ext.slider.MultiSlider = Ext.extend(Ext.BoxComponent, {
/**
- * @cfg {Boolean/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 {Number} value The value to initialize the slider with. Defaults to minValue.
*/
/**
- * @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} vertical Orient the Slider vertically rather than horizontally, defaults to false.
*/
+ vertical: false,
/**
- * @cfg {Boolean} ignoreNoChange
- * True to skip the edit completion process (no save, no events fired) if the user completes an edit and
- * the value has not changed (defaults to false). Applies only to string values - edits for other data types
- * will never be ignored.
+ * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0.
*/
+ minValue: 0,
/**
- * @cfg {Boolean} hideEl
- * False to keep the bound element visible while the editor is displayed (defaults to true)
+ * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100.
*/
+ maxValue: 100,
/**
- * @cfg {Mixed} value
- * The data value of the underlying field (defaults to "")
+ * @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.
*/
- value : "",
+ decimalPrecision: 0,
/**
- * @cfg {String} alignment
- * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "c-c?").
+ * @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.
*/
- alignment: "c-c?",
+ keyIncrement: 1,
/**
- * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop"
- * for bottom-right shadow (defaults to "frame")
+ * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
*/
- shadow : "frame",
+ increment: 0,
+
/**
- * @cfg {Boolean} constrain True to constrain the editor to the viewport
+ * @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
*/
- constrain : false,
+ clickRange: [5,15],
+
/**
- * @cfg {Boolean} swallowKeys Handle the keydown/keypress events so they don't propagate (defaults to true)
+ * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
*/
- swallowKeys : true,
+ clickToChange : true,
/**
- * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed. Defaults to true.
+ * @cfg {Boolean} animate Turn on or off animation. Defaults to true
*/
- completeOnEnter : true,
+ animate: true,
/**
- * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed. Defaults to true.
+ * @cfg {Boolean} constrainThumbs True to disallow thumbs from overlapping one another. Defaults to true
*/
- cancelOnEsc : true,
+ constrainThumbs: true,
+
/**
- * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false)
+ * @private
+ * @property topThumbZIndex
+ * @type Number
+ * The number used internally to set the z index of the top thumb (see promoteThumb for details)
*/
- updateEl : false,
+ topThumbZIndex: 10000,
+ // private override
initComponent : function(){
- Ext.Editor.superclass.initComponent.call(this);
+ if(!Ext.isDefined(this.value)){
+ this.value = this.minValue;
+ }
+
+ /**
+ * @property thumbs
+ * @type Array
+ * Array containing references to each thumb
+ */
+ this.thumbs = [];
+
+ Ext.slider.MultiSlider.superclass.initComponent.call(this);
+
+ this.keyIncrement = Math.max(this.increment, this.keyIncrement);
this.addEvents(
/**
- * @event beforestartedit
- * Fires when editing is initiated, but before the value changes. Editing can be canceled by returning
- * false from the handler of this event.
- * @param {Editor} this
- * @param {Ext.Element} boundEl The underlying element bound to this editor
- * @param {Mixed} value The field value being set
+ * @event beforechange
+ * Fires before the slider value is changed. By returning false from an event handler,
+ * you can cancel the event and prevent the slider from changing.
+ * @param {Ext.slider.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.
*/
- "beforestartedit",
+ 'beforechange',
+
/**
- * @event startedit
- * Fires when this editor is displayed
- * @param {Ext.Element} boundEl The underlying element bound to this editor
- * @param {Mixed} value The starting field value
+ * @event change
+ * Fires when the slider value is changed.
+ * @param {Ext.slider.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
*/
- "startedit",
+ 'change',
+
/**
- * @event beforecomplete
- * Fires after a change has been made to the field, but before the change is reflected in the underlying
- * field. Saving the change to the field can be canceled by returning false from the handler of this event.
- * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
- * event will not fire since no edit actually occurred.
- * @param {Editor} this
- * @param {Mixed} value The current field value
- * @param {Mixed} startValue The original field value
+ * @event changecomplete
+ * Fires when the slider value is changed by the user and any drag operations have completed.
+ * @param {Ext.slider.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
*/
- "beforecomplete",
+ 'changecomplete',
+
/**
- * @event complete
- * Fires after editing is complete and any changed value has been written to the underlying field.
- * @param {Editor} this
- * @param {Mixed} value The current field value
- * @param {Mixed} startValue The original field value
+ * @event dragstart
+ * Fires after a drag operation has started.
+ * @param {Ext.slider.MultiSlider} slider The slider
+ * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
*/
- "complete",
+ 'dragstart',
+
/**
- * @event canceledit
- * Fires after editing has been canceled and the editor's value has been reset.
- * @param {Editor} this
- * @param {Mixed} value The user-entered field value that was discarded
- * @param {Mixed} startValue The original field value that was set back into the editor after cancel
+ * @event drag
+ * Fires continuously during the drag operation while the mouse is moving.
+ * @param {Ext.slider.MultiSlider} slider The slider
+ * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
*/
- "canceledit",
+ 'drag',
+
/**
- * @event specialkey
- * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. You can check
- * {@link Ext.EventObject#getKey} to determine which key was pressed.
- * @param {Ext.form.Field} this
- * @param {Ext.EventObject} e The event object
+ * @event dragend
+ * Fires after the drag operation has completed.
+ * @param {Ext.slider.MultiSlider} slider The slider
+ * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
*/
- "specialkey"
+ 'dragend'
);
- },
-
- // private
- onRender : function(ct, position){
- this.el = new Ext.Layer({
- shadow: this.shadow,
- cls: "x-editor",
- parentEl : ct,
- shim : this.shim,
- shadowOffset: this.shadowOffset || 4,
- id: this.id,
- constrain: this.constrain
- });
- if(this.zIndex){
- this.el.setZIndex(this.zIndex);
- }
- this.el.setStyle("overflow", Ext.isGecko ? "auto" : "hidden");
- if(this.field.msgTarget != 'title'){
- this.field.msgTarget = 'qtip';
- }
- this.field.inEditor = true;
- this.mon(this.field, {
- scope: this,
- blur: this.onBlur,
- specialkey: this.onSpecialKey
- });
- if(this.field.grow){
- this.mon(this.field, "autosize", this.el.sync, this.el, {delay:1});
- }
- this.field.render(this.el).show();
- this.field.getEl().dom.name = '';
- if(this.swallowKeys){
- this.field.el.swallowEvent([
- 'keypress', // *** Opera
- 'keydown' // *** all other browsers
- ]);
- }
- },
- // 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();
- }
- }
- this.fireEvent('specialkey', field, e);
- },
+ /**
+ * @property values
+ * @type Array
+ * Array of values to initalize the thumbs with
+ */
+ if (this.values == undefined || Ext.isEmpty(this.values)) this.values = [0];
- /**
- * 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.setValue(v);
- this.doAutoSize();
- this.el.alignTo(this.boundEl, this.alignment);
- this.editing = true;
- this.show();
+ var values = this.values;
+
+ for (var i=0; i < values.length; i++) {
+ this.addThumb(values[i]);
+ }
+
+ if(this.vertical){
+ Ext.apply(this, Ext.slider.Vertical);
}
},
- // private
- doAutoSize : function(){
- if(this.autoSize){
- var sz = this.boundEl.getSize(),
- fs = this.field.getSize();
+ /**
+ * Creates a new thumb and adds it to the slider
+ * @param {Number} value The initial value to set on the thumb. Defaults to 0
+ */
+ addThumb: function(value) {
+ var thumb = new Ext.slider.Thumb({
+ value : value,
+ slider : this,
+ index : this.thumbs.length,
+ constrain: this.constrainThumbs
+ });
+ this.thumbs.push(thumb);
- 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);
- }
- }
+ //render the thumb now if needed
+ if (this.rendered) thumb.render();
},
/**
- * Sets the height and width of this editor.
- * @param {Number} width The new width
- * @param {Number} height The new height
+ * @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
*/
- 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);
+ 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 = '';
}
- this.el.sync();
+
+ 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();
},
/**
- * Realigns the editor to the bound field based on the current alignment config value.
+ * @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.
*/
- realign : function(){
- this.el.alignTo(this.boundEl, this.alignment);
+ initEvents : function(){
+ this.mon(this.el, {
+ scope : this,
+ mousedown: this.onMouseDown,
+ keydown : this.onKeyDown
+ });
+
+ this.focusEl.swallowEvent("click", true);
},
/**
- * 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)
+ * @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
*/
- completeEdit : function(remainVisible){
- if(!this.editing){
- return;
- }
- var v = this.getValue();
- if(!this.field.isValid()){
- if(this.revertInvalid !== false){
- this.cancelEdit(remainVisible);
- }
+ onMouseDown : function(e){
+ if(this.disabled){
return;
}
- if(String(v) === String(this.startValue) && this.ignoreNoChange){
- this.hideEdit(remainVisible);
- 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.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);
+
+ if (this.clickToChange && !thumbClicked) {
+ var local = this.innerEl.translatePoints(e.getXY());
+ this.onClickChange(local);
}
+ this.focus();
},
- // private
- onShow : function(){
- this.el.show();
- if(this.hideEl !== false){
- this.boundEl.hide();
+ /**
+ * @private
+ * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Vertical.
+ * Only changes the value if the click was within this.clickRange.
+ * @param {Object} local Object containing top and left values for the click event.
+ */
+ onClickChange : function(local) {
+ if (local.top > this.clickRange[0] && local.top < this.clickRange[1]) {
+ //find the nearest thumb to the click event
+ var thumb = this.getNearest(local, 'left'),
+ index = thumb.index;
+
+ this.setValue(index, Ext.util.Format.round(this.reverseValue(local.left), this.decimalPrecision), undefined, true);
}
- this.field.show().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)
+ * @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
*/
- cancelEdit : function(remainVisible){
- if(this.editing){
- var v = this.getValue();
- this.setValue(this.startValue);
- this.hideEdit(remainVisible);
- this.fireEvent("canceledit", this, v, this.startValue);
+ getNearest: function(local, prop) {
+ var localValue = prop == 'top' ? this.innerEl.getHeight() - local[prop] : local[prop],
+ clickValue = this.reverseValue(localValue),
+ nearestDistance = (this.maxValue - this.minValue) + 5, //add a small fudge for the end of the slider
+ index = 0,
+ nearest = null;
+
+ for (var i=0; i < this.thumbs.length; i++) {
+ var thumb = this.thumbs[i],
+ value = thumb.value,
+ dist = Math.abs(value - clickValue);
+
+ if (Math.abs(dist <= nearestDistance)) {
+ nearest = thumb;
+ index = i;
+ nearestDistance = dist;
+ }
}
+ return nearest;
},
-
- // private
- hideEdit: function(remainVisible){
- if(remainVisible !== true){
- this.editing = false;
- this.hide();
+
+ /**
+ * @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;
+ }
+ 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
- onBlur : function(){
- if(this.allowBlur !== true && this.editing){
- this.completeEdit();
+ /**
+ * @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;
+ }
+ 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
- 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();
- }
+ afterRender : function(){
+ Ext.slider.MultiSlider.superclass.afterRender.apply(this, arguments);
+
+ for (var i=0; i < this.thumbs.length; i++) {
+ var thumb = this.thumbs[i];
+
+ if (thumb.value !== undefined) {
+ var v = this.normalizeValue(thumb.value);
+
+ if (v !== thumb.value) {
+ // delete this.value;
+ this.setValue(i, v, false);
+ } else {
+ this.moveThumb(i, this.translateValue(v), false);
+ }
+ }
+ };
},
/**
- * Sets the data value of the editor
- * @param {Mixed} value Any valid value supported by the underlying field
+ * @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
*/
- setValue : function(v){
- this.field.setValue(v);
+ getRatio : function(){
+ var w = this.innerEl.getWidth(),
+ v = this.maxValue - this.minValue;
+ return v == 0 ? w : (w/v);
},
/**
- * Gets the data value of the editor
- * @return {Mixed} The data value
+ * @private
+ * Returns a snapped, constrained value when given a desired value
+ * @param {Number} value Raw number value
+ * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
*/
- getValue : function(){
- return this.field.getValue();
+ normalizeValue : function(v){
+ v = this.doSnap(v);
+ v = Ext.util.Format.round(v, this.decimalPrecision);
+ v = v.constrain(this.minValue, this.maxValue);
+ return v;
},
- beforeDestroy : function(){
- Ext.destroy(this.field);
- this.field = null;
- }
-});
-Ext.reg('editor', Ext.Editor);/**
- * @class Ext.ColorPalette
- * @extends Ext.Component
- * Simple color palette class for choosing colors. The palette can be rendered to any container.
- * Here's an example of typical usage:
- *
-var cp = new Ext.ColorPalette({value:'993300'}); // initial selected color
-cp.render('my-div');
-
-cp.on('select', function(palette, selColor){
- // do something with selColor
-});
-
- * @constructor
- * Create a new ColorPalette
- * @param {Object} config The config object
- * @xtype colorpalette
- */
-Ext.ColorPalette = function(config){
- Ext.ColorPalette.superclass.constructor.call(this, config);
- this.addEvents(
- /**
- * @event select
- * Fires when a color is selected
- * @param {ColorPalette} this
- * @param {String} color The 6-digit color hex code (without the # symbol)
- */
- 'select'
- );
-
- if(this.handler){
- this.on('select', this.handler, this.scope, true);
- }
-};
-Ext.extend(Ext.ColorPalette, Ext.Component, {
- /**
- * @cfg {String} tpl An existing XTemplate instance to be used in place of the default template for rendering the component.
- */
/**
- * @cfg {String} itemCls
- * The CSS class to apply to the containing element (defaults to 'x-color-palette')
+ * 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
*/
- itemCls : 'x-color-palette',
+ 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();
+ },
+
/**
- * @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.
+ * 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
*/
- value : null,
- clickEvent :'click',
- // private
- ctype : 'Ext.ColorPalette',
+ 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();
+ },
/**
- * @cfg {Boolean} allowReselect If set to true then reselecting a color that is already selected fires the {@link #select} event
+ * 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
*/
- allowReselect : false,
+ setValue : function(index, v, animate, changeComplete) {
+ var thumb = this.thumbs[index],
+ el = thumb.el;
- /**
- * 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
-
+ v = this.normalizeValue(v);
-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
+ 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);
+ }
+ }
+ }
+ },
+
+ /**
+ * @private
*/
- 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'
- ],
+ translateValue : function(v) {
+ var ratio = this.getRatio();
+ return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
+ },
/**
- * @cfg {Function} handler
- * Optional. A function that will handle the select event of this palette.
- * The handler is passed the following parameters:
- * palette
: ColorPaletteThe {@link #palette Ext.ColorPalette}.
- * color
: StringThe 6-digit color hex code (without the # symbol).
- *
+ * @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;
+ },
+
/**
- * @cfg {Object} scope
- * The scope (this reference) in which the {@link #handler}
- * function will be called. Defaults to this ColorPalette instance.
+ * @private
+ * @param {Number} index Index of the thumb to move
*/
+ moveThumb: function(index, v, animate){
+ var thumb = this.thumbs[index].el;
- // private
- onRender : function(container, position){
- var t = this.tpl || new Ext.XTemplate(
- ' '
- );
- var el = document.createElement('div');
- el.id = this.getId();
- el.className = this.itemCls;
- t.overwrite(el, this.colors);
- container.dom.insertBefore(el, position);
- this.el = Ext.get(el);
- this.mon(this.el, this.clickEvent, this.handleClick, this, {delegate: 'a'});
- if(this.clickEvent != 'click'){
- this.mon(this.el, 'click', Ext.emptyFn, this, {delegate: 'a', preventDefault: true});
+ if(!animate || this.animate === false){
+ thumb.setLeft(v);
+ }else{
+ thumb.shift({left: v, stopFx: true, duration:.35});
}
},
// private
- afterRender : function(){
- Ext.ColorPalette.superclass.afterRender.call(this);
- if(this.value){
- var s = this.value;
- this.value = null;
- this.select(s);
+ focus : function(){
+ this.focusEl.focus(10);
+ },
+
+ // 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);
},
- // private
- handleClick : function(e, t){
- e.preventDefault();
- if(!this.disabled){
- var c = t.className.match(/(?:^|\s)color-(.{6})(?:\s|$)/)[1];
- this.select(c.toUpperCase());
+ //private
+ onDisable: function(){
+ Ext.slider.MultiSlider.superclass.onDisable.call(this);
+
+ for (var i=0; i < this.thumbs.length; i++) {
+ var thumb = this.thumbs[i],
+ el = thumb.el;
+
+ thumb.disable();
+
+ if(Ext.isIE){
+ //IE breaks when using overflow visible and opacity other than 1.
+ //Create a place holder for the thumb and display it.
+ var xy = el.getXY();
+ el.hide();
+
+ this.innerEl.addClass(this.disabledClass).dom.disabled = true;
+
+ if (!this.thumbHolder) {
+ this.thumbHolder = this.endEl.createChild({cls: 'x-slider-thumb ' + this.disabledClass});
+ }
+
+ this.thumbHolder.show().setXY(xy);
+ }
+ }
+ },
+
+ //private
+ onEnable: function(){
+ Ext.slider.MultiSlider.superclass.onEnable.call(this);
+
+ for (var i=0; i < this.thumbs.length; i++) {
+ var thumb = this.thumbs[i],
+ el = thumb.el;
+
+ thumb.enable();
+
+ if (Ext.isIE) {
+ this.innerEl.removeClass(this.disabledClass).dom.disabled = false;
+
+ if (this.thumbHolder) this.thumbHolder.hide();
+
+ el.show();
+ this.syncThumb();
+ }
}
},
/**
- * 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)
+ * 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.
*/
- 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');
+ syncThumb : function() {
+ if (this.rendered) {
+ for (var i=0; i < this.thumbs.length; i++) {
+ this.moveThumb(i, this.translateValue(this.thumbs[i].value));
}
- el.child('a.color-'+color).addClass('x-color-palette-sel');
- this.value = color;
- this.fireEvent('select', this, color);
}
- }
+ },
/**
- * @cfg {String} autoEl @hide
+ * 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;
+ },
+
+ /**
+ * Returns an array of values - one for the location of each thumb
+ * @return {Array} The set of thumb values
*/
+ getValues: function() {
+ var values = [];
+
+ for (var i=0; i < this.thumbs.length; i++) {
+ values.push(this.thumbs[i].value);
+ }
+
+ return values;
+ },
+
+ // private
+ beforeDestroy : function(){
+ var thumbs = this.thumbs;
+ for(var i = 0, len = thumbs.length; i < len; ++i){
+ thumbs[i].destroy();
+ thumbs[i] = null;
+ }
+ Ext.destroyMembers(this, 'endEl', 'innerEl', 'focusEl', 'thumbHolder');
+ Ext.slider.MultiSlider.superclass.beforeDestroy.call(this);
+ }
});
-Ext.reg('colorpalette', Ext.ColorPalette);
-/**
- * @class Ext.DatePicker
- * @extends Ext.Component
- * Simple date picker class.
- * @constructor
- * Create a new DatePicker
- * @param {Object} config The config object
- * @xtype datepicker
- */
-Ext.DatePicker = Ext.extend(Ext.BoxComponent, {
- /**
- * @cfg {String} todayText
- * The text to display on the button that selects the current date (defaults to 'Today')
- */
- todayText : 'Today',
- /**
- * @cfg {String} okText
- * The text to display on the ok button (defaults to ' OK ' to give the user extra clicking room)
- */
- okText : ' OK ',
- /**
- * @cfg {String} cancelText
- * The text to display on the cancel button (defaults to 'Cancel')
- */
- cancelText : 'Cancel',
- /**
- * @cfg {Function} handler
- * Optional. A function that will handle the select event of this picker.
- * The handler is passed the following parameters:
- * picker
: DatePickerThe Ext.DatePicker.
- * date
: DateThe selected date.
- *
- */
- /**
- * @cfg {Object} scope
- * The scope (this reference) in which the {@link #handler}
- * function will be called. Defaults to this DatePicker instance.
- */
- /**
- * @cfg {String} todayTip
- * The tooltip to display for the button that selects the current date (defaults to '{current date} (Spacebar)')
- */
- todayTip : '{0} (Spacebar)',
- /**
- * @cfg {String} minText
- * The error text to display if the minDate validation fails (defaults to 'This date is before the minimum date')
- */
- minText : 'This date is before the minimum date',
- /**
- * @cfg {String} maxText
- * The error text to display if the maxDate validation fails (defaults to 'This date is after the maximum date')
- */
- maxText : 'This date is after the maximum date',
- /**
- * @cfg {String} format
- * The default date format string which can be overriden for localization support. The format must be
- * valid according to {@link Date#parseDate} (defaults to 'm/d/y').
- */
- format : 'm/d/y',
- /**
- * @cfg {String} disabledDaysText
- * The tooltip to display when the date falls on a disabled day (defaults to 'Disabled')
- */
- disabledDaysText : 'Disabled',
- /**
- * @cfg {String} disabledDatesText
- * The tooltip text to display when the date falls on a disabled date (defaults to 'Disabled')
- */
- disabledDatesText : 'Disabled',
- /**
- * @cfg {Array} monthNames
- * An array of textual month names which can be overriden for localization support (defaults to Date.monthNames)
- */
- monthNames : Date.monthNames,
- /**
- * @cfg {Array} dayNames
- * An array of textual day names which can be overriden for localization support (defaults to Date.dayNames)
- */
- dayNames : Date.dayNames,
- /**
- * @cfg {String} nextText
- * The next month navigation button tooltip (defaults to 'Next Month (Control+Right)')
- */
- nextText : 'Next Month (Control+Right)',
- /**
- * @cfg {String} prevText
- * The previous month navigation button tooltip (defaults to 'Previous Month (Control+Left)')
- */
- prevText : 'Previous Month (Control+Left)',
- /**
- * @cfg {String} monthYearText
- * The header month selector tooltip (defaults to 'Choose a month (Control+Up/Down to move years)')
- */
- monthYearText : 'Choose a month (Control+Up/Down to move years)',
- /**
- * @cfg {Number} startDay
- * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
- */
- startDay : 0,
- /**
- * @cfg {Boolean} showToday
- * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar
- * that selects the current date (defaults to true).
- */
- showToday : true,
- /**
- * @cfg {Date} minDate
- * Minimum allowable date (JavaScript date object, defaults to null)
- */
- /**
- * @cfg {Date} maxDate
- * Maximum allowable date (JavaScript date object, defaults to null)
- */
- /**
- * @cfg {Array} disabledDays
- * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
- */
- /**
- * @cfg {RegExp} disabledDatesRE
- * JavaScript regular expression used to disable a pattern of dates (defaults to null). The {@link #disabledDates}
- * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
- * disabledDates value.
- */
- /**
- * @cfg {Array} disabledDates
- * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular
- * expression so they are very powerful. Some examples:
- *
- * - ['03/08/2003', '09/16/2003'] would disable those exact dates
- * - ['03/08', '09/16'] would disable those days for every year
- * - ['^03/08'] would only match the beginning (useful if you are using short years)
- * - ['03/../2006'] would disable every day in March 2006
- * - ['^03'] would disable every day in every March
- *
- * Note that the format of the dates included in the array should exactly match the {@link #format} config.
- * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to
- * escape the dot when restricting dates. For example: ['03\\.08\\.03'].
- */
-
- // private
- initComponent : function(){
- Ext.DatePicker.superclass.initComponent.call(this);
-
- this.value = this.value ?
- this.value.clearTime(true) : new Date().clearTime();
-
- this.addEvents(
- /**
- * @event select
- * Fires when a date is selected
- * @param {DatePicker} this
- * @param {Date} date The selected date
- */
- 'select'
- );
-
- if(this.handler){
- this.on('select', this.handler, this.scope || this);
- }
-
- this.initDisabledDays();
- },
-
- // private
- initDisabledDays : function(){
- if(!this.disabledDatesRE && this.disabledDates){
- var dd = this.disabledDates,
- len = dd.length - 1,
- re = '(?:';
-
- Ext.each(dd, function(d, i){
- re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i];
- if(i != len){
- re += '|';
- }
- }, this);
- this.disabledDatesRE = new RegExp(re + ')');
- }
- },
-
- /**
- * Replaces any existing disabled dates with new values and refreshes the DatePicker.
- * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config
- * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
- */
- setDisabledDates : function(dd){
- if(Ext.isArray(dd)){
- this.disabledDates = dd;
- this.disabledDatesRE = null;
- }else{
- this.disabledDatesRE = dd;
- }
- this.initDisabledDays();
- this.update(this.value, true);
- },
-
- /**
- * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
- * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config
- * for details on supported values.
- */
- setDisabledDays : function(dd){
- this.disabledDays = dd;
- this.update(this.value, true);
- },
-
- /**
- * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
- * @param {Date} value The minimum date that can be selected
- */
- setMinDate : function(dt){
- this.minDate = dt;
- this.update(this.value, true);
- },
-
- /**
- * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
- * @param {Date} value The maximum date that can be selected
- */
- setMaxDate : function(dt){
- this.maxDate = dt;
- this.update(this.value, true);
- },
-
- /**
- * Sets the value of the date field
- * @param {Date} value The date to set
- */
- setValue : function(value){
- this.value = value.clearTime(true);
- this.update(this.value);
- },
-
- /**
- * Gets the current selected value of the date field
- * @return {Date} The selected date
- */
- getValue : function(){
- return this.value;
- },
-
- // private
- focus : function(){
- this.update(this.activeDate);
- },
-
- // private
- onEnable: function(initial){
- Ext.DatePicker.superclass.onEnable.call(this);
- this.doDisabled(false);
- this.update(initial ? this.value : this.activeDate);
- if(Ext.isIE){
- this.el.repaint();
- }
-
- },
-
- // private
- onDisable : function(){
- Ext.DatePicker.superclass.onDisable.call(this);
- this.doDisabled(true);
- if(Ext.isIE && !Ext.isIE8){
- /* Really strange problem in IE6/7, when disabled, have to explicitly
- * repaint each of the nodes to get them to display correctly, simply
- * calling repaint on the main element doesn't appear to be enough.
- */
- Ext.each([].concat(this.textNodes, this.el.query('th span')), function(el){
- Ext.fly(el).repaint();
- });
- }
- },
-
- // private
- doDisabled : function(disabled){
- this.keyNav.setDisabled(disabled);
- this.prevRepeater.setDisabled(disabled);
- this.nextRepeater.setDisabled(disabled);
- if(this.showToday){
- this.todayKeyListener.setDisabled(disabled);
- this.todayBtn.setDisabled(disabled);
- }
- },
-
- // private
- onRender : function(container, position){
- var m = [
- '',
- ' ',
- ''],
- dn = this.dayNames,
- i;
- for(i = 0; i < 7; i++){
- var d = this.startDay+i;
- if(d > 6){
- d = d-7;
- }
- m.push('', dn[d].substr(0,1), ' ');
- }
- m[m.length] = ' ';
- for(i = 0; i < 42; i++) {
- if(i % 7 === 0 && i !== 0){
- m[m.length] = ' ';
- }
- m[m.length] = ' ';
- }
- m.push('
',
- this.showToday ? ' ' : '',
- '
');
-
- var el = document.createElement('div');
- el.className = 'x-date-picker';
- el.innerHTML = m.join('');
-
- container.dom.insertBefore(el, position);
-
- this.el = Ext.get(el);
- this.eventEl = Ext.get(el.firstChild);
-
- this.prevRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-left a'), {
- handler: this.showPrevMonth,
- scope: this,
- preventDefault:true,
- stopDefault:true
- });
-
- this.nextRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-right a'), {
- handler: this.showNextMonth,
- scope: this,
- preventDefault:true,
- stopDefault:true
- });
-
- this.monthPicker = this.el.down('div.x-date-mp');
- this.monthPicker.enableDisplayMode('block');
-
- this.keyNav = new Ext.KeyNav(this.eventEl, {
- 'left' : function(e){
- if(e.ctrlKey){
- this.showPrevMonth();
- }else{
- this.update(this.activeDate.add('d', -1));
- }
- },
-
- 'right' : function(e){
- if(e.ctrlKey){
- this.showNextMonth();
- }else{
- this.update(this.activeDate.add('d', 1));
- }
- },
-
- 'up' : function(e){
- if(e.ctrlKey){
- this.showNextYear();
- }else{
- this.update(this.activeDate.add('d', -7));
- }
- },
-
- 'down' : function(e){
- if(e.ctrlKey){
- this.showPrevYear();
- }else{
- this.update(this.activeDate.add('d', 7));
- }
- },
-
- 'pageUp' : function(e){
- this.showNextMonth();
- },
-
- 'pageDown' : function(e){
- this.showPrevMonth();
- },
-
- 'enter' : function(e){
- e.stopPropagation();
- return true;
- },
-
- scope : this
- });
-
- this.el.unselectable();
-
- this.cells = this.el.select('table.x-date-inner tbody td');
- this.textNodes = this.el.query('table.x-date-inner tbody span');
-
- this.mbtn = new Ext.Button({
- text: ' ',
- tooltip: this.monthYearText,
- renderTo: this.el.child('td.x-date-middle', true)
- });
- this.mbtn.el.child('em').addClass('x-btn-arrow');
-
- if(this.showToday){
- this.todayKeyListener = this.eventEl.addKeyListener(Ext.EventObject.SPACE, this.selectToday, this);
- var today = (new Date()).dateFormat(this.format);
- this.todayBtn = new Ext.Button({
- renderTo: this.el.child('td.x-date-bottom', true),
- text: String.format(this.todayText, today),
- tooltip: String.format(this.todayTip, today),
- handler: this.selectToday,
- scope: this
- });
- }
- this.mon(this.eventEl, 'mousewheel', this.handleMouseWheel, this);
- this.mon(this.eventEl, 'click', this.handleDateClick, this, {delegate: 'a.x-date-date'});
- this.mon(this.mbtn, 'click', this.showMonthPicker, this);
- this.onEnable(true);
- },
-
- // private
- createMonthPicker : function(){
- if(!this.monthPicker.dom.firstChild){
- var buf = [''];
- for(var i = 0; i < 6; i++){
- buf.push(
- '', Date.getShortMonthName(i), ' ',
- '', Date.getShortMonthName(i + 6), ' ',
- i === 0 ?
- ' ' :
- ' '
- );
- }
- buf.push(
- ' ',
- '
'
- );
- this.monthPicker.update(buf.join(''));
-
- this.mon(this.monthPicker, 'click', this.onMonthClick, this);
- this.mon(this.monthPicker, 'dblclick', this.onMonthDblClick, this);
-
- this.mpMonths = this.monthPicker.select('td.x-date-mp-month');
- this.mpYears = this.monthPicker.select('td.x-date-mp-year');
-
- this.mpMonths.each(function(m, a, i){
- i += 1;
- if((i%2) === 0){
- m.dom.xmonth = 5 + Math.round(i * 0.5);
- }else{
- m.dom.xmonth = Math.round((i-1) * 0.5);
- }
- });
- }
- },
-
- // private
- showMonthPicker : function(){
- if(!this.disabled){
- this.createMonthPicker();
- var size = this.el.getSize();
- this.monthPicker.setSize(size);
- this.monthPicker.child('table').setSize(size);
-
- this.mpSelMonth = (this.activeDate || this.value).getMonth();
- this.updateMPMonth(this.mpSelMonth);
- this.mpSelYear = (this.activeDate || this.value).getFullYear();
- this.updateMPYear(this.mpSelYear);
-
- this.monthPicker.slideIn('t', {duration:0.2});
- }
- },
-
- // private
- updateMPYear : function(y){
- this.mpyear = y;
- var ys = this.mpYears.elements;
- for(var i = 1; i <= 10; i++){
- var td = ys[i-1], y2;
- if((i%2) === 0){
- y2 = y + Math.round(i * 0.5);
- td.firstChild.innerHTML = y2;
- td.xyear = y2;
- }else{
- y2 = y - (5-Math.round(i * 0.5));
- td.firstChild.innerHTML = y2;
- td.xyear = y2;
- }
- this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel');
- }
- },
-
- // private
- updateMPMonth : function(sm){
- this.mpMonths.each(function(m, a, i){
- m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel');
- });
- },
-
- // private
- selectMPMonth : function(m){
-
- },
-
- // private
- onMonthClick : function(e, t){
- e.stopEvent();
- var el = new Ext.Element(t), pn;
- if(el.is('button.x-date-mp-cancel')){
- this.hideMonthPicker();
- }
- else if(el.is('button.x-date-mp-ok')){
- var d = new Date(this.mpSelYear, this.mpSelMonth, (this.activeDate || this.value).getDate());
- if(d.getMonth() != this.mpSelMonth){
- // 'fix' the JS rolling date conversion if needed
- d = new Date(this.mpSelYear, this.mpSelMonth, 1).getLastDateOfMonth();
- }
- this.update(d);
- this.hideMonthPicker();
- }
- else if((pn = el.up('td.x-date-mp-month', 2))){
- this.mpMonths.removeClass('x-date-mp-sel');
- pn.addClass('x-date-mp-sel');
- this.mpSelMonth = pn.dom.xmonth;
- }
- else if((pn = el.up('td.x-date-mp-year', 2))){
- this.mpYears.removeClass('x-date-mp-sel');
- pn.addClass('x-date-mp-sel');
- this.mpSelYear = pn.dom.xyear;
- }
- else if(el.is('a.x-date-mp-prev')){
- this.updateMPYear(this.mpyear-10);
- }
- else if(el.is('a.x-date-mp-next')){
- this.updateMPYear(this.mpyear+10);
- }
- },
-
- // private
- onMonthDblClick : function(e, t){
- e.stopEvent();
- var el = new Ext.Element(t), pn;
- if((pn = el.up('td.x-date-mp-month', 2))){
- this.update(new Date(this.mpSelYear, pn.dom.xmonth, (this.activeDate || this.value).getDate()));
- this.hideMonthPicker();
- }
- else if((pn = el.up('td.x-date-mp-year', 2))){
- this.update(new Date(pn.dom.xyear, this.mpSelMonth, (this.activeDate || this.value).getDate()));
- this.hideMonthPicker();
- }
- },
-
- // private
- hideMonthPicker : function(disableAnim){
- if(this.monthPicker){
- if(disableAnim === true){
- this.monthPicker.hide();
- }else{
- this.monthPicker.slideOut('t', {duration:0.2});
- }
- }
- },
-
- // private
- showPrevMonth : function(e){
- this.update(this.activeDate.add('mo', -1));
- },
-
- // private
- showNextMonth : function(e){
- this.update(this.activeDate.add('mo', 1));
- },
-
- // private
- showPrevYear : function(){
- this.update(this.activeDate.add('y', -1));
- },
-
- // private
- showNextYear : function(){
- this.update(this.activeDate.add('y', 1));
- },
-
- // private
- handleMouseWheel : function(e){
- e.stopEvent();
- if(!this.disabled){
- var delta = e.getWheelDelta();
- if(delta > 0){
- this.showPrevMonth();
- } else if(delta < 0){
- this.showNextMonth();
- }
- }
- },
-
- // private
- handleDateClick : function(e, t){
- e.stopEvent();
- if(!this.disabled && t.dateValue && !Ext.fly(t.parentNode).hasClass('x-date-disabled')){
- this.setValue(new Date(t.dateValue));
- this.fireEvent('select', this, this.value);
- }
- },
-
- // private
- selectToday : function(){
- if(this.todayBtn && !this.todayBtn.disabled){
- this.setValue(new Date().clearTime());
- this.fireEvent('select', this, this.value);
- }
- },
-
- // private
- update : function(date, forceRefresh){
- 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){
- Ext.fly(c.dom.firstChild).focus(50);
- }
- return false;
- }
- });
- 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]);
- }
- }
- }
- },
-
- // private
- beforeDestroy : function() {
- if(this.rendered){
- this.keyNav.disable();
- this.keyNav = null;
- Ext.destroy(
- this.leftClickRpt,
- this.rightClickRpt,
- this.monthPicker,
- this.eventEl,
- this.mbtn,
- this.todayBtn
- );
- }
- }
-
- /**
- * @cfg {String} autoEl @hide
- */
-});
-
-Ext.reg('datepicker', Ext.DatePicker);
+
+Ext.reg('multislider', Ext.slider.MultiSlider);
+
/**
- * @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();
+ * @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
+});
- * @constructor
- * Create a new LoadMask
- * @param {Mixed} el The element or DOM node, or its id
- * @param {Object} config The config object
+ * The class Ext.slider.SingleSlider is aliased to Ext.Slider for backwards compatibility.
*/
-Ext.LoadMask = function(el, config){
- this.el = Ext.get(el);
- Ext.apply(this, config);
- if(this.store){
- this.store.on('beforeload', this.onBeforeLoad, this);
- this.store.on('load', this.onLoad, this);
- this.store.on('exception', this.onLoad, this);
- this.removeMask = Ext.value(this.removeMask, false);
- }else{
- var um = this.el.getUpdater();
- um.showLoadIndicator = false; // disable the default indicator
- um.on('beforeupdate', this.onBeforeLoad, this);
- um.on('update', this.onLoad, this);
- um.on('failure', this.onLoad, this);
- this.removeMask = Ext.value(this.removeMask, true);
- }
-};
+Ext.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);
+ },
-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.
+ * 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);
+ },
+
/**
- * @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.
+ * 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);
+ },
+
/**
- * @cfg {String} msg
- * The text to display in a centered loading message box (defaults to 'Loading...')
+ * 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.
*/
- msg : 'Loading...',
+ syncThumb : function() {
+ return Ext.slider.SingleSlider.superclass.syncThumb.apply(this, [0].concat(arguments));
+ },
+
+ // private
+ getNearest : function(){
+ // Since there's only 1 thumb, it's always the nearest
+ return this.thumbs[0];
+ }
+});
+
+//backwards compatibility
+Ext.Slider = Ext.slider.SingleSlider;
+
+Ext.reg('slider', Ext.slider.SingleSlider);
+
+// private class to support vertical sliders
+Ext.slider.Vertical = {
+ onResize : function(w, h){
+ this.innerEl.setHeight(h - (this.el.getPadding('t') + this.endEl.getPadding('b')));
+ this.syncThumb();
+ },
+
+ getRatio : function(){
+ var h = this.innerEl.getHeight(),
+ v = this.maxValue - this.minValue;
+ return h/v;
+ },
+
+ moveThumb: function(index, v, animate) {
+ var thumb = this.thumbs[index],
+ el = thumb.el;
+
+ if (!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 {String} msgCls
- * The CSS class to apply to the loading message element (defaults to "x-mask-loading")
- */
- msgCls : 'x-mask-loading',
+ * @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);
+ }
+ },
/**
- * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false)
- * @type Boolean
+ * 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
*/
- disabled: false,
+ 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;
+ },
/**
- * Disables the mask to prevent it from being displayed
- */
- disable : function(){
- this.disabled = true;
+ * 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;
},
/**
- * Enables the mask so that it can be displayed
+ * Returns true if the progress bar is currently in a {@link #wait} operation
+ * @return {Boolean} True if waiting, else false
*/
- enable : function(){
- this.disabled = false;
+ isWaiting : function(){
+ return this.waitTimer !== null;
},
- // private
- onLoad : function(){
- this.el.unmask(this.removeMask);
+ /**
+ * 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;
},
-
- // private
- onBeforeLoad : function(){
- if(!this.disabled){
- this.el.mask(this.msg, this.msgCls);
+
+ /**
+ * Synchronizes the inner bar width to the proper proportion of the total componet width based
+ * on the current progress {@link #value}. This will be called automatically when the ProgressBar
+ * is resized by a layout, but if it is rendered auto width, this method can be called from
+ * another resize handler to sync the ProgressBar if necessary.
+ */
+ syncProgressBar : function(){
+ if(this.value){
+ this.updateProgress(this.value, this.text);
}
+ return this;
},
/**
- * Show this LoadMask over the configured Element.
+ * Sets the size of the progress bar.
+ * @param {Number} width The new width in pixels
+ * @param {Number} height The new height in pixels
+ * @return {Ext.ProgressBar} this
*/
- show: function(){
- this.onBeforeLoad();
+ setSize : function(w, h){
+ Ext.ProgressBar.superclass.setSize.call(this, w, h);
+ if(this.textTopEl){
+ var inner = this.el.dom.firstChild;
+ this.textEl.setSize(inner.offsetWidth, inner.offsetHeight);
+ }
+ this.syncProgressBar();
+ return this;
},
/**
- * Hide this LoadMask.
+ * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress
+ * bar will also be hidden (using the {@link #hideMode} property internally).
+ * @param {Boolean} hide (optional) True to hide the progress bar (defaults to false)
+ * @return {Ext.ProgressBar} this
*/
- hide: function(){
- this.onLoad();
+ reset : function(hide){
+ this.updateProgress(0);
+ if(this.textTopEl){
+ this.textTopEl.addClass('x-hidden');
+ }
+ this.clearTimer();
+ if(hide === true){
+ this.hide();
+ }
+ return this;
},
-
+
// private
- destroy : function(){
- if(this.store){
- this.store.un('beforeload', this.onBeforeLoad, this);
- this.store.un('load', this.onLoad, this);
- this.store.un('exception', this.onLoad, this);
- }else{
- var um = this.el.getUpdater();
- um.un('beforeupdate', this.onBeforeLoad, this);
- um.un('update', this.onLoad, this);
- um.un('failure', this.onLoad, this);
+ clearTimer : function(){
+ if(this.waitTimer){
+ this.waitTimer.onStop = null; //prevent recursion
+ Ext.TaskMgr.stop(this.waitTimer);
+ this.waitTimer = null;
}
+ },
+
+ onDestroy: function(){
+ this.clearTimer();
+ if(this.rendered){
+ if(this.textEl.isComposite){
+ this.textEl.clear();
+ }
+ Ext.destroyMembers(this, 'textEl', 'progressBar', 'textTopEl');
+ }
+ Ext.ProgressBar.superclass.onDestroy.call(this);
}
-};/**
- * @class Ext.Slider
- * @extends Ext.BoxComponent
- * Slider which supports vertical or horizontal orientation, keyboard adjustments,
- * configurable snapping, axis clicking and animation. Can be added as an item to
- * any container. Example usage:
-
-new Ext.Slider({
- renderTo: Ext.getBody(),
- width: 200,
- value: 50,
- increment: 10,
- minValue: 0,
- maxValue: 100
-});
-
- */
-Ext.Slider = Ext.extend(Ext.BoxComponent, {
- /**
- * @cfg {Number} value The value to initialize the slider with. Defaults to minValue.
- */
- /**
- * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false.
- */
- vertical: false,
- /**
- * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0.
- */
- minValue: 0,
- /**
- * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100.
- */
- maxValue: 100,
- /**
- * @cfg {Number/Boolean} decimalPrecision.
- * The number of decimal places to which to round the Slider's value. Defaults to 0.
- * To disable rounding, configure as false.
- */
- decimalPrecision: 0,
- /**
- * @cfg {Number} keyIncrement How many units to change the Slider when adjusting with keyboard navigation. Defaults to 1. If the increment config is larger, it will be used instead.
- */
- keyIncrement: 1,
- /**
- * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
- */
- increment: 0,
- // private
- clickRange: [5,15],
- /**
- * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
- */
- clickToChange : true,
- /**
- * @cfg {Boolean} animate Turn on or off animation. Defaults to true
- */
- animate: true,
-
- /**
- * True while the thumb is in a drag operation
- * @type boolean
- */
- dragging: false,
-
- // private override
- initComponent : function(){
- if(!Ext.isDefined(this.value)){
- this.value = this.minValue;
- }
- Ext.Slider.superclass.initComponent.call(this);
- this.keyIncrement = Math.max(this.increment, this.keyIncrement);
- this.addEvents(
- /**
- * @event beforechange
- * Fires before the slider value is changed. By returning false from an event handler,
- * you can cancel the event and prevent the slider from changing.
- * @param {Ext.Slider} slider The slider
- * @param {Number} newValue The new value which the slider is being changed to.
- * @param {Number} oldValue The old value which the slider was previously.
- */
- 'beforechange',
- /**
- * @event change
- * Fires when the slider value is changed.
- * @param {Ext.Slider} slider The slider
- * @param {Number} newValue The new value which the slider has been changed to.
- */
- 'change',
- /**
- * @event changecomplete
- * Fires when the slider value is changed by the user and any drag operations have completed.
- * @param {Ext.Slider} slider The slider
- * @param {Number} newValue The new value which the slider has been changed to.
- */
- 'changecomplete',
- /**
- * @event dragstart
- * Fires after a drag operation has started.
- * @param {Ext.Slider} slider The slider
- * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
- */
- 'dragstart',
- /**
- * @event drag
- * Fires continuously during the drag operation while the mouse is moving.
- * @param {Ext.Slider} slider The slider
- * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
- */
- 'drag',
- /**
- * @event dragend
- * Fires after the drag operation has completed.
- * @param {Ext.Slider} slider The slider
- * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
- */
- 'dragend'
- );
-
- if(this.vertical){
- Ext.apply(this, Ext.Slider.Vertical);
- }
- },
-
- // private override
- onRender : function(){
- this.autoEl = {
- cls: 'x-slider ' + (this.vertical ? 'x-slider-vert' : 'x-slider-horz'),
- cn:{cls:'x-slider-end',cn:{cls:'x-slider-inner',cn:[{cls:'x-slider-thumb'},{tag:'a', cls:'x-slider-focus', href:"#", tabIndex: '-1', hidefocus:'on'}]}}
- };
- Ext.Slider.superclass.onRender.apply(this, arguments);
- this.endEl = this.el.first();
- this.innerEl = this.endEl.first();
- this.thumb = this.innerEl.first();
- this.halfThumb = (this.vertical ? this.thumb.getHeight() : this.thumb.getWidth())/2;
- this.focusEl = this.thumb.next();
- this.initEvents();
- },
-
- // private override
- initEvents : function(){
- this.thumb.addClassOnOver('x-slider-thumb-over');
- this.mon(this.el, {
- scope: this,
- mousedown: this.onMouseDown,
- keydown: this.onKeyDown
- });
-
- this.focusEl.swallowEvent("click", true);
-
- this.tracker = new Ext.dd.DragTracker({
- onBeforeStart: this.onBeforeDragStart.createDelegate(this),
- onStart: this.onDragStart.createDelegate(this),
- onDrag: this.onDrag.createDelegate(this),
- onEnd: this.onDragEnd.createDelegate(this),
- tolerance: 3,
- autoStart: 300
- });
- this.tracker.initEl(this.thumb);
- this.on('beforedestroy', this.tracker.destroy, this.tracker);
- },
-
- // private override
- onMouseDown : function(e){
- if(this.disabled) {return;}
- if(this.clickToChange && e.target != this.thumb.dom){
- var local = this.innerEl.translatePoints(e.getXY());
- this.onClickChange(local);
- }
- this.focus();
- },
-
- // private
- onClickChange : function(local){
- if(local.top > this.clickRange[0] && local.top < this.clickRange[1]){
- this.setValue(Ext.util.Format.round(this.reverseValue(local.left), this.decimalPrecision), undefined, true);
- }
- },
-
- // private
- onKeyDown : function(e){
- if(this.disabled){e.preventDefault();return;}
- var k = e.getKey();
- switch(k){
- case e.UP:
- case e.RIGHT:
- e.stopEvent();
- if(e.ctrlKey){
- this.setValue(this.maxValue, undefined, true);
- }else{
- this.setValue(this.value+this.keyIncrement, undefined, true);
- }
- break;
- case e.DOWN:
- case e.LEFT:
- e.stopEvent();
- if(e.ctrlKey){
- this.setValue(this.minValue, undefined, true);
- }else{
- this.setValue(this.value-this.keyIncrement, undefined, true);
- }
- break;
- default:
- e.preventDefault();
- }
- },
-
- // private
- doSnap : function(value){
- if(!this.increment || this.increment == 1 || !value) {
- return value;
- }
- var newValue = value, inc = this.increment;
- var m = value % inc;
- if(m != 0){
- newValue -= m;
- if(m * 2 > inc){
- newValue += inc;
- }else if(m * 2 < -inc){
- newValue -= inc;
- }
- }
- return newValue.constrain(this.minValue, this.maxValue);
- },
-
- // private
- afterRender : function(){
- Ext.Slider.superclass.afterRender.apply(this, arguments);
- if(this.value !== undefined){
- var v = this.normalizeValue(this.value);
- if(v !== this.value){
- delete this.value;
- this.setValue(v, false);
- }else{
- this.moveThumb(this.translateValue(v), false);
- }
- }
- },
-
- // private
- getRatio : function(){
- var w = this.innerEl.getWidth();
- var v = this.maxValue - this.minValue;
- return v == 0 ? w : (w/v);
- },
-
- // private
- normalizeValue : function(v){
- v = this.doSnap(v);
- v = Ext.util.Format.round(v, this.decimalPrecision);
- v = v.constrain(this.minValue, this.maxValue);
- return v;
- },
-
- /**
- * Programmatically sets the value of the Slider. Ensures that the value is constrained within
- * the minValue and maxValue.
- * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
- * @param {Boolean} animate Turn on or off animation, defaults to true
- */
- setValue : function(v, animate, changeComplete){
- v = this.normalizeValue(v);
- if(v !== this.value && this.fireEvent('beforechange', this, v, this.value) !== false){
- this.value = v;
- this.moveThumb(this.translateValue(v), animate !== false);
- this.fireEvent('change', this, v);
- if(changeComplete){
- this.fireEvent('changecomplete', this, v);
- }
- }
- },
-
- // private
- translateValue : function(v){
- var ratio = this.getRatio();
- return (v * ratio)-(this.minValue * ratio)-this.halfThumb;
- },
-
- reverseValue : function(pos){
- var ratio = this.getRatio();
- return (pos+this.halfThumb+(this.minValue * ratio))/ratio;
- },
-
- // private
- moveThumb: function(v, animate){
- if(!animate || this.animate === false){
- this.thumb.setLeft(v);
- }else{
- this.thumb.shift({left: v, stopFx: true, duration:.35});
- }
- },
-
- // private
- focus : function(){
- this.focusEl.focus(10);
- },
-
- // private
- onBeforeDragStart : function(e){
- return !this.disabled;
- },
-
- // private
- onDragStart: function(e){
- this.thumb.addClass('x-slider-thumb-drag');
- this.dragging = true;
- this.dragStartValue = this.value;
- this.fireEvent('dragstart', this, e);
- },
-
- // private
- onDrag: function(e){
- var pos = this.innerEl.translatePoints(this.tracker.getXY());
- this.setValue(Ext.util.Format.round(this.reverseValue(pos.left), this.decimalPrecision), false);
- this.fireEvent('drag', this, e);
- },
-
- // private
- onDragEnd: function(e){
- this.thumb.removeClass('x-slider-thumb-drag');
- this.dragging = false;
- this.fireEvent('dragend', this, e);
- if(this.dragStartValue != this.value){
- this.fireEvent('changecomplete', this, this.value);
- }
- },
-
- // private
- onResize : function(w, h){
- this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r')));
- this.syncThumb();
- },
-
- //private
- onDisable: function(){
- Ext.Slider.superclass.onDisable.call(this);
- this.thumb.addClass(this.disabledClass);
- if(Ext.isIE){
- //IE breaks when using overflow visible and opacity other than 1.
- //Create a place holder for the thumb and display it.
- var xy = this.thumb.getXY();
- this.thumb.hide();
- this.innerEl.addClass(this.disabledClass).dom.disabled = true;
- if (!this.thumbHolder){
- this.thumbHolder = this.endEl.createChild({cls: 'x-slider-thumb ' + this.disabledClass});
- }
- this.thumbHolder.show().setXY(xy);
- }
- },
-
- //private
- onEnable: function(){
- Ext.Slider.superclass.onEnable.call(this);
- this.thumb.removeClass(this.disabledClass);
- if(Ext.isIE){
- this.innerEl.removeClass(this.disabledClass).dom.disabled = false;
- if (this.thumbHolder){
- this.thumbHolder.hide();
- }
- this.thumb.show();
- this.syncThumb();
- }
- },
-
- /**
- * Synchronizes the thumb position to the proper proportion of the total component width based
- * on the current slider {@link #value}. This will be called automatically when the Slider
- * is resized by a layout, but if it is rendered auto width, this method can be called from
- * another resize handler to sync the Slider if necessary.
- */
- syncThumb : function(){
- if(this.rendered){
- this.moveThumb(this.translateValue(this.value));
- }
- },
-
- /**
- * Returns the current value of the slider
- * @return {Number} The current value of the slider
- */
- getValue : function(){
- return this.value;
- }
-});
-Ext.reg('slider', Ext.Slider);
-
-// private class to support vertical sliders
-Ext.Slider.Vertical = {
- onResize : function(w, h){
- this.innerEl.setHeight(h - (this.el.getPadding('t') + this.endEl.getPadding('b')));
- this.syncThumb();
- },
-
- getRatio : function(){
- var h = this.innerEl.getHeight();
- var v = this.maxValue - this.minValue;
- return h/v;
- },
-
- moveThumb: function(v, animate){
- if(!animate || this.animate === false){
- this.thumb.setBottom(v);
- }else{
- this.thumb.shift({bottom: v, stopFx: true, duration:.35});
- }
- },
-
- onDrag: function(e){
- var pos = this.innerEl.translatePoints(this.tracker.getXY());
- var bottom = this.innerEl.getHeight()-pos.top;
- this.setValue(this.minValue + Ext.util.Format.round(bottom/this.getRatio(), this.decimalPrecision), false);
- this.fireEvent('drag', this, e);
- },
-
- onClickChange : function(local){
- if(local.left > this.clickRange[0] && local.left < this.clickRange[1]){
- var bottom = this.innerEl.getHeight()-local.top;
- this.setValue(this.minValue + Ext.util.Format.round(bottom/this.getRatio(), this.decimalPrecision), undefined, true);
- }
- }
-};/**
- * @class Ext.ProgressBar
- * @extends Ext.BoxComponent
- * An updateable progress bar component. The progress bar supports two different modes: manual and automatic.
- * In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the
- * progress bar as needed from your own code. This method is most appropriate when you want to show progress
- * throughout an operation that has predictable points of interest at which you can update the control.
- * In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it
- * once the operation is complete. You can optionally have the progress bar wait for a specific amount of time
- * and then clear itself. Automatic mode is most appropriate for timed operations or asynchronous operations in
- * which you have no need for indicating intermediate progress.
- * @cfg {Float} value A floating point value between 0 and 1 (e.g., .5, defaults to 0)
- * @cfg {String} text The progress bar text (defaults to '')
- * @cfg {Mixed} textEl The element to render the progress text to (defaults to the progress
- * bar's internal text element)
- * @cfg {String} id The progress bar element's id (defaults to an auto-generated id)
- * @xtype progress
- */
-Ext.ProgressBar = Ext.extend(Ext.BoxComponent, {
- /**
- * @cfg {String} baseCls
- * The base CSS class to apply to the progress bar's wrapper element (defaults to 'x-progress')
- */
- baseCls : 'x-progress',
-
- /**
- * @cfg {Boolean} animate
- * True to animate the progress bar during transitions (defaults to false)
- */
- animate : false,
-
- // private
- waitTimer : null,
-
- // private
- initComponent : function(){
- Ext.ProgressBar.superclass.initComponent.call(this);
- this.addEvents(
- /**
- * @event update
- * Fires after each update interval
- * @param {Ext.ProgressBar} this
- * @param {Number} The current progress value
- * @param {String} The current progress text
- */
- "update"
- );
- },
-
- // private
- onRender : function(ct, position){
- var tpl = new Ext.Template(
- '',
- '',
- '',
- ' ',
- '',
- '',
- ' ',
- ''
- );
-
- this.el = position ? tpl.insertBefore(position, {cls: this.baseCls}, true)
- : tpl.append(ct, {cls: this.baseCls}, true);
-
- if(this.id){
- this.el.dom.id = this.id;
- }
- var inner = this.el.dom.firstChild;
- this.progressBar = Ext.get(inner.firstChild);
-
- if(this.textEl){
- //use an external text el
- this.textEl = Ext.get(this.textEl);
- delete this.textTopEl;
- }else{
- //setup our internal layered text els
- this.textTopEl = Ext.get(this.progressBar.dom.firstChild);
- var textBackEl = Ext.get(inner.childNodes[1]);
- this.textTopEl.setStyle("z-index", 99).addClass('x-hidden');
- this.textEl = new Ext.CompositeElement([this.textTopEl.dom.firstChild, textBackEl.dom.firstChild]);
- this.textEl.setWidth(inner.offsetWidth);
- }
- this.progressBar.setHeight(inner.offsetHeight);
- },
-
- // private
- afterRender : function(){
- Ext.ProgressBar.superclass.afterRender.call(this);
- if(this.value){
- this.updateProgress(this.value, this.text);
- }else{
- this.updateText(this.text);
- }
- },
-
- /**
- * Updates the progress bar value, and optionally its text. If the text argument is not specified,
- * any existing text value will be unchanged. To blank out existing text, pass ''. Note that even
- * if the progress bar value exceeds 1, it will never automatically reset -- you are responsible for
- * determining when the progress is complete and calling {@link #reset} to clear and/or hide the control.
- * @param {Float} value (optional) A floating point value between 0 and 1 (e.g., .5, defaults to 0)
- * @param {String} text (optional) The string to display in the progress text element (defaults to '')
- * @param {Boolean} animate (optional) Whether to animate the transition of the progress bar. If this value is
- * not specified, the default for the class is used (default to false)
- * @return {Ext.ProgressBar} this
- */
- updateProgress : function(value, text, animate){
- this.value = value || 0;
- if(text){
- this.updateText(text);
- }
- if(this.rendered){
- var w = Math.floor(value*this.el.dom.firstChild.offsetWidth);
- this.progressBar.setWidth(w, animate === true || (animate !== false && this.animate));
- if(this.textTopEl){
- //textTopEl should be the same width as the bar so overflow will clip as the bar moves
- this.textTopEl.removeClass('x-hidden').setWidth(w);
- }
- }
- this.fireEvent('update', this, value, text);
- return this;
- },
-
- /**
- * Initiates an auto-updating progress bar. A duration can be specified, in which case the progress
- * bar will automatically reset after a fixed amount of time and optionally call a callback function
- * if specified. If no duration is passed in, then the progress bar will run indefinitely and must
- * be manually cleared by calling {@link #reset}. The wait method accepts a config object with
- * the following properties:
- *
-Property Type Description
----------- ------------ ----------------------------------------------------------------------
-duration Number The length of time in milliseconds that the progress bar should
- run before resetting itself (defaults to undefined, in which case it
- will run indefinitely until reset is called)
-interval Number The length of time in milliseconds between each progress update
- (defaults to 1000 ms)
-animate Boolean Whether to animate the transition of the progress bar. If this value is
- not specified, the default for the class is used.
-increment Number The number of progress update segments to display within the progress
- bar (defaults to 10). If the bar reaches the end and is still
- updating, it will automatically wrap back to the beginning.
-text String Optional text to display in the progress bar element (defaults to '').
-fn Function A callback function to execute after the progress bar finishes auto-
- updating. The function will be called with no arguments. This function
- will be ignored if duration is not specified since in that case the
- progress bar can only be stopped programmatically, so any required function
- should be called by the same code after it resets the progress bar.
-scope Object The scope that is passed to the callback function (only applies when
- duration and fn are both passed).
-
- *
- * Example usage:
- *
-var p = new Ext.ProgressBar({
- renderTo: 'my-el'
-});
-
-//Wait for 5 seconds, then update the status el (progress bar will auto-reset)
-p.wait({
- interval: 100, //bar will move fast!
- duration: 5000,
- increment: 15,
- text: 'Updating...',
- scope: this,
- fn: function(){
- Ext.fly('status').update('Done!');
- }
-});
-
-//Or update indefinitely until some async action completes, then reset manually
-p.wait();
-myAction.on('complete', function(){
- p.reset();
- Ext.fly('status').update('Done!');
-});
-
- * @param {Object} config (optional) Configuration options
- * @return {Ext.ProgressBar} this
- */
- wait : function(o){
- if(!this.waitTimer){
- var scope = this;
- o = o || {};
- this.updateText(o.text);
- this.waitTimer = Ext.TaskMgr.start({
- run: function(i){
- var inc = o.increment || 10;
- i -= 1;
- this.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
- },
- interval: o.interval || 1000,
- duration: o.duration,
- onStop: function(){
- if(o.fn){
- o.fn.apply(o.scope || this);
- }
- this.reset();
- },
- scope: scope
- });
- }
- return this;
- },
-
- /**
- * Returns true if the progress bar is currently in a {@link #wait} operation
- * @return {Boolean} True if waiting, else false
- */
- isWaiting : function(){
- return this.waitTimer !== null;
- },
-
- /**
- * Updates the progress bar text. If specified, textEl will be updated, otherwise the progress
- * bar itself will display the updated text.
- * @param {String} text (optional) The string to display in the progress text element (defaults to '')
- * @return {Ext.ProgressBar} this
- */
- updateText : function(text){
- this.text = text || ' ';
- if(this.rendered){
- this.textEl.update(this.text);
- }
- return this;
- },
-
- /**
- * Synchronizes the inner bar width to the proper proportion of the total componet width based
- * on the current progress {@link #value}. This will be called automatically when the ProgressBar
- * is resized by a layout, but if it is rendered auto width, this method can be called from
- * another resize handler to sync the ProgressBar if necessary.
- */
- syncProgressBar : function(){
- if(this.value){
- this.updateProgress(this.value, this.text);
- }
- return this;
- },
-
- /**
- * Sets the size of the progress bar.
- * @param {Number} width The new width in pixels
- * @param {Number} height The new height in pixels
- * @return {Ext.ProgressBar} this
- */
- setSize : function(w, h){
- Ext.ProgressBar.superclass.setSize.call(this, w, h);
- if(this.textTopEl){
- var inner = this.el.dom.firstChild;
- this.textEl.setSize(inner.offsetWidth, inner.offsetHeight);
- }
- this.syncProgressBar();
- return this;
- },
-
- /**
- * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress
- * bar will also be hidden (using the {@link #hideMode} property internally).
- * @param {Boolean} hide (optional) True to hide the progress bar (defaults to false)
- * @return {Ext.ProgressBar} this
- */
- reset : function(hide){
- this.updateProgress(0);
- if(this.textTopEl){
- this.textTopEl.addClass('x-hidden');
- }
- if(this.waitTimer){
- this.waitTimer.onStop = null; //prevent recursion
- Ext.TaskMgr.stop(this.waitTimer);
- this.waitTimer = null;
- }
- if(hide === true){
- this.hide();
- }
- return this;
- }
-});
+});
Ext.reg('progress', Ext.ProgressBar);
\ No newline at end of file