+
+ return {
+ // private ==> used by Fx
+ adjustWidth : function(width) {
+ var me = this;
+ var isNum = Ext.isNumber(width);
+ if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
+ width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
+ }
+ return (isNum && width < 0) ? 0 : width;
+ },
+
+ // private ==> used by Fx
+ adjustHeight : function(height) {
+ var me = this;
+ var isNum = Ext.isNumber(height);
+ if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
+ height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
+ }
+ return (isNum && height < 0) ? 0 : height;
+ },
+
+
+ /**
+ * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
+ * @param {String/Array} className The CSS class to add, or an array of classes
+ * @return {Ext.Element} this
+ */
+ addClass : function(className){
+ var me = this, i, len, v;
+ className = Ext.isArray(className) ? className : [className];
+ for (i=0, len = className.length; i < len; i++) {
+ v = className[i];
+ if (v) {
+ me.dom.className += (!me.hasClass(v) && v ? " " + v : "");
+ };
+ };
+ return me;
+ },
+
+ /**
+ * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
+ * @param {String/Array} className The CSS class to add, or an array of classes
+ * @return {Ext.Element} this
+ */
+ radioClass : function(className){
+ var cn = this.dom.parentNode.childNodes, v;
+ className = Ext.isArray(className) ? className : [className];
+ for (var i=0, len = cn.length; i < len; i++) {
+ v = cn[i];
+ if(v && v.nodeType == 1) {
+ Ext.fly(v, '_internal').removeClass(className);
+ }
+ };
+ return this.addClass(className);
+ },
+
+ /**
+ * Removes one or more CSS classes from the element.
+ * @param {String/Array} className The CSS class to remove, or an array of classes
+ * @return {Ext.Element} this
+ */
+ removeClass : function(className){
+ var me = this, v;
+ className = Ext.isArray(className) ? className : [className];
+ if (me.dom && me.dom.className) {
+ for (var i=0, len=className.length; i < len; i++) {
+ v = className[i];
+ if(v) {
+ me.dom.className = me.dom.className.replace(
+ classReCache[v] = classReCache[v] || new RegExp('(?:^|\\s+)' + v + '(?:\\s+|$)', "g"), " "
+ );
+ }
+ };
+ }
+ return me;
+ },
+
+ /**
+ * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
+ * @param {String} className The CSS class to toggle
+ * @return {Ext.Element} this
+ */
+ toggleClass : function(className){
+ return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
+ },
+
+ /**
+ * Checks if the specified CSS class exists on this element's DOM node.
+ * @param {String} className The CSS class to check for
+ * @return {Boolean} True if the class exists, else false
+ */
+ hasClass : function(className){
+ return className && (' '+this.dom.className+' ').indexOf(' '+className+' ') != -1;
+ },
+
+ /**
+ * Replaces a CSS class on the element with another. If the old name does not exist, the new name will simply be added.
+ * @param {String} oldClassName The CSS class to replace
+ * @param {String} newClassName The replacement CSS class
+ * @return {Ext.Element} this
+ */
+ replaceClass : function(oldClassName, newClassName){
+ return this.removeClass(oldClassName).addClass(newClassName);
+ },
+
+ isStyle : function(style, val) {
+ return this.getStyle(style) == val;
+ },
+
+ /**
+ * Normalizes currentStyle and computedStyle.
+ * @param {String} property The style property whose value is returned.
+ * @return {String} The current value of the style property for this element.
+ */
+ getStyle : function(){
+ return view && view.getComputedStyle ?
+ function(prop){
+ var el = this.dom,
+ v,
+ cs,
+ out,
+ display,
+ wk = Ext.isWebKit,
+ display;
+
+ if(el == document){
+ return null;
+ }
+ prop = chkCache(prop);
+ // Fix bug caused by this: https://bugs.webkit.org/show_bug.cgi?id=13343
+ if(wk && /marginRight/.test(prop)){
+ display = this.getStyle('display');
+ el.style.display = 'inline-block';
+ }
+ out = (v = el.style[prop]) ? v :
+ (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
+
+ // Webkit returns rgb values for transparent.
+ if(wk){
+ if(out == 'rgba(0, 0, 0, 0)'){
+ out = 'transparent';
+ }else if(display){
+ el.style.display = display;
+ }
+ }
+ return out;
+ } :
+ function(prop){
+ var el = this.dom,
+ m,
+ cs;
+
+ if(el == document) return null;
+ if (prop == 'opacity') {
+ if (el.style.filter.match) {
+ if(m = el.style.filter.match(opacityRe)){
+ var fv = parseFloat(m[1]);
+ if(!isNaN(fv)){
+ return fv ? fv / 100 : 0;
+ }
+ }
+ }
+ return 1;
+ }
+ prop = chkCache(prop);
+ return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
+ };
+ }(),
+
+ /**
+ * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
+ * are convert to standard 6 digit hex color.
+ * @param {String} attr The css attribute
+ * @param {String} defaultValue The default value to use when a valid color isn't found
+ * @param {String} prefix (optional) defaults to #. Use an empty string when working with
+ * color anims.
+ */
+ getColor : function(attr, defaultValue, prefix){
+ var v = this.getStyle(attr),
+ color = Ext.isDefined(prefix) ? prefix : '#',
+ h;
+
+ if(!v || /transparent|inherit/.test(v)){
+ return defaultValue;
+ }
+ if(/^r/.test(v)){
+ Ext.each(v.slice(4, v.length -1).split(','), function(s){
+ h = parseInt(s, 10);
+ color += (h < 16 ? '0' : '') + h.toString(16);
+ });
+ }else{
+ v = v.replace('#', '');
+ color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
+ }
+ return(color.length > 5 ? color.toLowerCase() : defaultValue);
+ },
+
+ /**
+ * Wrapper for setting style properties, also takes single object parameter of multiple styles.
+ * @param {String/Object} property The style property to be set, or an object of multiple styles.
+ * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
+ * @return {Ext.Element} this
+ */
+ setStyle : function(prop, value){
+ var tmp,
+ style,
+ camel;
+ if (!Ext.isObject(prop)) {
+ tmp = {};
+ tmp[prop] = value;
+ prop = tmp;
+ }
+ for (style in prop) {
+ value = prop[style];
+ style == 'opacity' ?
+ this.setOpacity(value) :
+ this.dom.style[chkCache(style)] = value;
+ }
+ return this;
+ },
+
+ /**
+ * Set the opacity of the element
+ * @param {Float} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
+ * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
+ * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
+ * @return {Ext.Element} this
+ */
+ setOpacity : function(opacity, animate){
+ var me = this,
+ s = me.dom.style;
+
+ if(!animate || !me.anim){
+ if(Ext.isIE){
+ var opac = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')' : '',
+ val = s.filter.replace(opacityRe, '').replace(trimRe, '');
+
+ s.zoom = 1;
+ s.filter = val + (val.length > 0 ? ' ' : '') + opac;
+ }else{
+ s.opacity = opacity;
+ }
+ }else{
+ me.anim({opacity: {to: opacity}}, me.preanim(arguments, 1), null, .35, 'easeIn');
+ }
+ return me;
+ },
+
+ /**
+ * Clears any opacity settings from this element. Required in some cases for IE.
+ * @return {Ext.Element} this
+ */
+ clearOpacity : function(){
+ var style = this.dom.style;
+ if(Ext.isIE){
+ if(!Ext.isEmpty(style.filter)){
+ style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
+ }
+ }else{
+ style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
+ }
+ return this;
+ },
+
+ /**
+ * Returns the offset height of the element
+ * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
+ * @return {Number} The element's height
+ */
+ getHeight : function(contentHeight){
+ var me = this,
+ dom = me.dom,
+ hidden = Ext.isIE && me.isStyle('display', 'none'),
+ h = MATH.max(dom.offsetHeight, hidden ? 0 : dom.clientHeight) || 0;
+
+ h = !contentHeight ? h : h - me.getBorderWidth("tb") - me.getPadding("tb");
+ return h < 0 ? 0 : h;
+ },
+
+ /**
+ * Returns the offset width of the element
+ * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
+ * @return {Number} The element's width
+ */
+ getWidth : function(contentWidth){
+ var me = this,
+ dom = me.dom,
+ hidden = Ext.isIE && me.isStyle('display', 'none'),
+ w = MATH.max(dom.offsetWidth, hidden ? 0 : dom.clientWidth) || 0;
+ w = !contentWidth ? w : w - me.getBorderWidth("lr") - me.getPadding("lr");
+ return w < 0 ? 0 : w;
+ },
+
+ /**
+ * Set the width of this Element.
+ * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
+ * </ul></div>
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ setWidth : function(width, animate){
+ var me = this;
+ width = me.adjustWidth(width);
+ !animate || !me.anim ?
+ me.dom.style.width = me.addUnits(width) :
+ me.anim({width : {to : width}}, me.preanim(arguments, 1));
+ return me;
+ },
+
+ /**
+ * Set the height of this Element.
+ * <pre><code>
+// change the height to 200px and animate with default configuration
+Ext.fly('elementId').setHeight(200, true);
+
+// change the height to 150px and animate with a custom configuration
+Ext.fly('elId').setHeight(150, {
+ duration : .5, // animation will have a duration of .5 seconds
+ // will change the content to "finished"
+ callback: function(){ this.{@link #update}("finished"); }
+});
+ * </code></pre>
+ * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels.)</li>
+ * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
+ * </ul></div>
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ setHeight : function(height, animate){
+ var me = this;
+ height = me.adjustHeight(height);
+ !animate || !me.anim ?
+ me.dom.style.height = me.addUnits(height) :
+ me.anim({height : {to : height}}, me.preanim(arguments, 1));
+ return me;
+ },
+
+ /**
+ * Gets the width of the border(s) for the specified side(s)
+ * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
+ * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
+ * @return {Number} The width of the sides passed added together
+ */
+ getBorderWidth : function(side){
+ return this.addStyles(side, borders);
+ },
+
+ /**
+ * Gets the width of the padding(s) for the specified side(s)
+ * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
+ * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
+ * @return {Number} The padding of the sides passed added together
+ */
+ getPadding : function(side){
+ return this.addStyles(side, paddings);
+ },
+
+ /**
+ * Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
+ * @return {Ext.Element} this
+ */
+ clip : function(){
+ var me = this,
+ dom = me.dom;
+
+ if(!data(dom, ISCLIPPED)){
+ data(dom, ISCLIPPED, true);
+ data(dom, ORIGINALCLIP, {
+ o: me.getStyle(OVERFLOW),
+ x: me.getStyle(OVERFLOWX),
+ y: me.getStyle(OVERFLOWY)
+ });
+ me.setStyle(OVERFLOW, HIDDEN);
+ me.setStyle(OVERFLOWX, HIDDEN);
+ me.setStyle(OVERFLOWY, HIDDEN);
+ }
+ return me;
+ },
+
+ /**
+ * Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
+ * @return {Ext.Element} this
+ */
+ unclip : function(){
+ var me = this,
+ dom = me.dom;
+
+ if(data(dom, ISCLIPPED)){
+ data(dom, ISCLIPPED, false);
+ var o = data(dom, ORIGINALCLIP);
+ if(o.o){
+ me.setStyle(OVERFLOW, o.o);
+ }
+ if(o.x){
+ me.setStyle(OVERFLOWX, o.x);
+ }
+ if(o.y){
+ me.setStyle(OVERFLOWY, o.y);
+ }
+ }
+ return me;
+ },
+
+ // private
+ addStyles : function(sides, styles){
+ var val = 0,
+ m = sides.match(/\w/g),
+ s;
+ for (var i=0, len=m.length; i<len; i++) {
+ s = m[i] && parseInt(this.getStyle(styles[m[i]]), 10);
+ if (s) {
+ val += MATH.abs(s);
+ }
+ }
+ return val;
+ },
+
+ margins : margins
+ }
+}()
+);
+/**
+ * @class Ext.Element
+ */
+
+// special markup used throughout Ext when box wrapping elements
+Ext.Element.boxMarkup = '<div class="{0}-tl"><div class="{0}-tr"><div class="{0}-tc"></div></div></div><div class="{0}-ml"><div class="{0}-mr"><div class="{0}-mc"></div></div></div><div class="{0}-bl"><div class="{0}-br"><div class="{0}-bc"></div></div></div>';
+
+Ext.Element.addMethods(function(){
+ var INTERNAL = "_internal",
+ pxMatch = /(\d+\.?\d+)px/;
+ return {
+ /**
+ * More flexible version of {@link #setStyle} for setting style properties.
+ * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
+ * a function which returns such a specification.
+ * @return {Ext.Element} this
+ */
+ applyStyles : function(style){
+ Ext.DomHelper.applyStyles(this.dom, style);
+ return this;
+ },
+
+ /**
+ * Returns an object with properties matching the styles requested.
+ * For example, el.getStyles('color', 'font-size', 'width') might return
+ * {'color': '#FFFFFF', 'font-size': '13px', 'width': '100px'}.
+ * @param {String} style1 A style name
+ * @param {String} style2 A style name
+ * @param {String} etc.
+ * @return {Object} The style object
+ */
+ getStyles : function(){
+ var ret = {};
+ Ext.each(arguments, function(v) {
+ ret[v] = this.getStyle(v);
+ },
+ this);
+ return ret;
+ },
+
+ // private ==> used by ext full
+ setOverflow : function(v){
+ var dom = this.dom;
+ if(v=='auto' && Ext.isMac && Ext.isGecko2){ // work around stupid FF 2.0/Mac scroll bar bug
+ dom.style.overflow = 'hidden';
+ (function(){dom.style.overflow = 'auto';}).defer(1);
+ }else{
+ dom.style.overflow = v;
+ }
+ },
+
+ /**
+ * <p>Wraps the specified element with a special 9 element markup/CSS block that renders by default as
+ * a gray container with a gradient background, rounded corners and a 4-way shadow.</p>
+ * <p>This special markup is used throughout Ext when box wrapping elements ({@link Ext.Button},
+ * {@link Ext.Panel} when <tt>{@link Ext.Panel#frame frame=true}</tt>, {@link Ext.Window}). The markup
+ * is of this form:</p>
+ * <pre><code>
+ Ext.Element.boxMarkup =
+ '<div class="{0}-tl"><div class="{0}-tr"><div class="{0}-tc"></div></div></div>
+ <div class="{0}-ml"><div class="{0}-mr"><div class="{0}-mc"></div></div></div>
+ <div class="{0}-bl"><div class="{0}-br"><div class="{0}-bc"></div></div></div>';
+ * </code></pre>
+ * <p>Example usage:</p>
+ * <pre><code>
+ // Basic box wrap
+ Ext.get("foo").boxWrap();
+
+ // You can also add a custom class and use CSS inheritance rules to customize the box look.
+ // 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
+ // for how to create a custom box wrap style.
+ Ext.get("foo").boxWrap().addClass("x-box-blue");
+ * </code></pre>
+ * @param {String} class (optional) A base CSS class to apply to the containing wrapper element
+ * (defaults to <tt>'x-box'</tt>). Note that there are a number of CSS rules that are dependent on
+ * this name to make the overall effect work, so if you supply an alternate base class, make sure you
+ * also supply all of the necessary rules.
+ * @return {Ext.Element} The outermost wrapping element of the created box structure.
+ */
+ boxWrap : function(cls){
+ cls = cls || 'x-box';
+ var el = Ext.get(this.insertHtml("beforeBegin", "<div class='" + cls + "'>" + String.format(Ext.Element.boxMarkup, cls) + "</div>")); //String.format('<div class="{0}">'+Ext.Element.boxMarkup+'</div>', cls)));
+ Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
+ return el;
+ },
+
+ /**
+ * Set the size of this Element. If animation is true, both width and height will be animated concurrently.
+ * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
+ * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
+ * </ul></div>
+ * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
+ * </ul></div>
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ setSize : function(width, height, animate){
+ var me = this;
+ if(Ext.isObject(width)){ // in case of object from getSize()
+ height = width.height;
+ width = width.width;
+ }
+ width = me.adjustWidth(width);
+ height = me.adjustHeight(height);
+ if(!animate || !me.anim){
+ me.dom.style.width = me.addUnits(width);
+ me.dom.style.height = me.addUnits(height);
+ }else{
+ me.anim({width: {to: width}, height: {to: height}}, me.preanim(arguments, 2));
+ }
+ return me;
+ },
+
+ /**
+ * Returns either the offsetHeight or the height of this element based on CSS height adjusted by padding or borders
+ * when needed to simulate offsetHeight when offsets aren't available. This may not work on display:none elements
+ * if a height has not been set using CSS.
+ * @return {Number}
+ */
+ getComputedHeight : function(){
+ var me = this,
+ h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
+ if(!h){
+ h = parseFloat(me.getStyle('height')) || 0;
+ if(!me.isBorderBox()){
+ h += me.getFrameWidth('tb');
+ }
+ }
+ return h;
+ },
+
+ /**
+ * Returns either the offsetWidth or the width of this element based on CSS width adjusted by padding or borders
+ * when needed to simulate offsetWidth when offsets aren't available. This may not work on display:none elements
+ * if a width has not been set using CSS.
+ * @return {Number}
+ */
+ getComputedWidth : function(){
+ var w = Math.max(this.dom.offsetWidth, this.dom.clientWidth);
+ if(!w){
+ w = parseFloat(this.getStyle('width')) || 0;
+ if(!this.isBorderBox()){
+ w += this.getFrameWidth('lr');
+ }
+ }
+ return w;
+ },
+
+ /**
+ * Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
+ for more information about the sides.
+ * @param {String} sides
+ * @return {Number}
+ */
+ getFrameWidth : function(sides, onlyContentBox){
+ return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
+ },
+
+ /**
+ * Sets up event handlers to add and remove a css class when the mouse is over this element
+ * @param {String} className
+ * @return {Ext.Element} this
+ */
+ addClassOnOver : function(className){
+ this.hover(
+ function(){
+ Ext.fly(this, INTERNAL).addClass(className);
+ },
+ function(){
+ Ext.fly(this, INTERNAL).removeClass(className);
+ }
+ );
+ return this;
+ },
+
+ /**
+ * Sets up event handlers to add and remove a css class when this element has the focus
+ * @param {String} className
+ * @return {Ext.Element} this
+ */
+ addClassOnFocus : function(className){
+ this.on("focus", function(){
+ Ext.fly(this, INTERNAL).addClass(className);
+ }, this.dom);
+ this.on("blur", function(){
+ Ext.fly(this, INTERNAL).removeClass(className);
+ }, this.dom);
+ return this;
+ },
+
+ /**
+ * Sets up event handlers to add and remove a css class when the mouse is down and then up on this element (a click effect)
+ * @param {String} className
+ * @return {Ext.Element} this
+ */
+ addClassOnClick : function(className){
+ var dom = this.dom;
+ this.on("mousedown", function(){
+ Ext.fly(dom, INTERNAL).addClass(className);
+ var d = Ext.getDoc(),
+ fn = function(){
+ Ext.fly(dom, INTERNAL).removeClass(className);
+ d.removeListener("mouseup", fn);
+ };
+ d.on("mouseup", fn);
+ });
+ return this;
+ },
+
+ /**
+ * <p>Returns the dimensions of the element available to lay content out in.<p>
+ * <p>If the element (or any ancestor element) has CSS style <code>display : none</code>, the dimensions will be zero.</p>
+ * example:<pre><code>
+ var vpSize = Ext.getBody().getViewSize();
+
+ // all Windows created afterwards will have a default value of 90% height and 95% width
+ Ext.Window.override({
+ width: vpSize.width * 0.9,
+ height: vpSize.height * 0.95
+ });
+ // To handle window resizing you would have to hook onto onWindowResize.
+ * </code></pre>
+ *
+ * getViewSize utilizes clientHeight/clientWidth which excludes sizing of scrollbars.
+ * To obtain the size including scrollbars, use getStyleSize
+ *
+ * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
+ */
+
+ getViewSize : function(){
+ var doc = document,
+ d = this.dom,
+ isDoc = (d == doc || d == doc.body);
+
+ // If the body, use Ext.lib.Dom
+ if (isDoc) {
+ var extdom = Ext.lib.Dom;
+ return {
+ width : extdom.getViewWidth(),
+ height : extdom.getViewHeight()
+ };
+
+ // Else use clientHeight/clientWidth
+ } else {
+ return {
+ width : d.clientWidth,
+ height : d.clientHeight
+ }
+ }
+ },
+
+ /**
+ * <p>Returns the dimensions of the element available to lay content out in.<p>
+ *
+ * getStyleSize utilizes prefers style sizing if present, otherwise it chooses the larger of offsetHeight/clientHeight and offsetWidth/clientWidth.
+ * To obtain the size excluding scrollbars, use getViewSize
+ *
+ * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
+ */
+
+ getStyleSize : function(){
+ var me = this,
+ w, h,
+ doc = document,
+ d = this.dom,
+ isDoc = (d == doc || d == doc.body),
+ s = d.style;
+
+ // If the body, use Ext.lib.Dom
+ if (isDoc) {
+ var extdom = Ext.lib.Dom;
+ return {
+ width : extdom.getViewWidth(),
+ height : extdom.getViewHeight()
+ }
+ }
+ // Use Styles if they are set
+ if(s.width && s.width != 'auto'){
+ w = parseFloat(s.width);
+ if(me.isBorderBox()){
+ w -= me.getFrameWidth('lr');
+ }
+ }
+ // Use Styles if they are set
+ if(s.height && s.height != 'auto'){
+ h = parseFloat(s.height);
+ if(me.isBorderBox()){
+ h -= me.getFrameWidth('tb');
+ }
+ }
+ // Use getWidth/getHeight if style not set.
+ return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
+ },
+
+ /**
+ * Returns the size of the element.
+ * @param {Boolean} contentSize (optional) true to get the width/size minus borders and padding
+ * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
+ */
+ getSize : function(contentSize){
+ return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
+ },
+
+ /**
+ * Forces the browser to repaint this element
+ * @return {Ext.Element} this
+ */
+ repaint : function(){
+ var dom = this.dom;
+ this.addClass("x-repaint");
+ setTimeout(function(){
+ Ext.fly(dom).removeClass("x-repaint");
+ }, 1);
+ return this;
+ },
+
+ /**
+ * Disables text selection for this element (normalized across browsers)
+ * @return {Ext.Element} this
+ */
+ unselectable : function(){
+ this.dom.unselectable = "on";
+ return this.swallowEvent("selectstart", true).
+ applyStyles("-moz-user-select:none;-khtml-user-select:none;").
+ addClass("x-unselectable");
+ },
+
+ /**
+ * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
+ * then it returns the calculated width of the sides (see getPadding)
+ * @param {String} sides (optional) Any combination of l, r, t, b to get the sum of those sides
+ * @return {Object/Number}
+ */
+ getMargins : function(side){
+ var me = this,
+ key,
+ hash = {t:"top", l:"left", r:"right", b: "bottom"},
+ o = {};
+
+ if (!side) {
+ for (key in me.margins){
+ o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
+ }
+ return o;
+ } else {
+ return me.addStyles.call(me, side, me.margins);
+ }
+ }
+ };
+}());
+/**
+ * @class Ext.Element
+ */
+(function(){
+var D = Ext.lib.Dom,
+ LEFT = "left",
+ RIGHT = "right",
+ TOP = "top",
+ BOTTOM = "bottom",
+ POSITION = "position",
+ STATIC = "static",
+ RELATIVE = "relative",
+ AUTO = "auto",
+ ZINDEX = "z-index";
+
+Ext.Element.addMethods({
+ /**
+ * Gets the current X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @return {Number} The X position of the element
+ */
+ getX : function(){
+ return D.getX(this.dom);
+ },
+
+ /**
+ * Gets the current Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @return {Number} The Y position of the element
+ */
+ getY : function(){
+ return D.getY(this.dom);
+ },
+
+ /**
+ * Gets the current position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @return {Array} The XY position of the element
+ */
+ getXY : function(){
+ return D.getXY(this.dom);
+ },
+
+ /**
+ * Returns the offsets of this element from the passed element. Both element must be part of the DOM tree and not have display:none to have page coordinates.
+ * @param {Mixed} element The element to get the offsets from.
+ * @return {Array} The XY page offsets (e.g. [100, -200])
+ */
+ getOffsetsTo : function(el){
+ var o = this.getXY(),
+ e = Ext.fly(el, '_internal').getXY();
+ return [o[0]-e[0],o[1]-e[1]];
+ },
+
+ /**
+ * Sets the X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Number} The X position of the element
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ setX : function(x, animate){
+ return this.setXY([x, this.getY()], this.animTest(arguments, animate, 1));
+ },
+
+ /**
+ * Sets the Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Number} The Y position of the element
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ setY : function(y, animate){
+ return this.setXY([this.getX(), y], this.animTest(arguments, animate, 1));
+ },
+
+ /**
+ * Sets the element's left position directly using CSS style (instead of {@link #setX}).
+ * @param {String} left The left CSS property value
+ * @return {Ext.Element} this
+ */
+ setLeft : function(left){
+ this.setStyle(LEFT, this.addUnits(left));
+ return this;
+ },
+
+ /**
+ * Sets the element's top position directly using CSS style (instead of {@link #setY}).
+ * @param {String} top The top CSS property value
+ * @return {Ext.Element} this
+ */
+ setTop : function(top){
+ this.setStyle(TOP, this.addUnits(top));
+ return this;
+ },
+
+ /**
+ * Sets the element's CSS right style.
+ * @param {String} right The right CSS property value
+ * @return {Ext.Element} this
+ */
+ setRight : function(right){
+ this.setStyle(RIGHT, this.addUnits(right));
+ return this;
+ },
+
+ /**
+ * Sets the element's CSS bottom style.
+ * @param {String} bottom The bottom CSS property value
+ * @return {Ext.Element} this
+ */
+ setBottom : function(bottom){
+ this.setStyle(BOTTOM, this.addUnits(bottom));
+ return this;
+ },
+
+ /**
+ * Sets the position of the element in page coordinates, regardless of how the element is positioned.
+ * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Array} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ setXY : function(pos, animate){
+ var me = this;
+ if(!animate || !me.anim){
+ D.setXY(me.dom, pos);
+ }else{
+ me.anim({points: {to: pos}}, me.preanim(arguments, 1), 'motion');
+ }
+ return me;
+ },
+
+ /**
+ * Sets the position of the element in page coordinates, regardless of how the element is positioned.
+ * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Number} x X value for new position (coordinates are page-based)
+ * @param {Number} y Y value for new position (coordinates are page-based)
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ setLocation : function(x, y, animate){
+ return this.setXY([x, y], this.animTest(arguments, animate, 2));
+ },
+
+ /**
+ * Sets the position of the element in page coordinates, regardless of how the element is positioned.
+ * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Number} x X value for new position (coordinates are page-based)
+ * @param {Number} y Y value for new position (coordinates are page-based)
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ moveTo : function(x, y, animate){
+ return this.setXY([x, y], this.animTest(arguments, animate, 2));
+ },
+
+ /**
+ * Gets the left X coordinate
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
+ */
+ getLeft : function(local){
+ return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
+ },
+
+ /**
+ * Gets the right X coordinate of the element (element X position + element width)
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
+ */
+ getRight : function(local){
+ var me = this;
+ return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
+ },
+
+ /**
+ * Gets the top Y coordinate
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
+ */
+ getTop : function(local) {
+ return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
+ },
+
+ /**
+ * Gets the bottom Y coordinate of the element (element Y position + element height)
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
+ */
+ getBottom : function(local){
+ var me = this;
+ return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
+ },
+
+ /**
+ * Initializes positioning on this element. If a desired position is not passed, it will make the
+ * the element positioned relative IF it is not already positioned.
+ * @param {String} pos (optional) Positioning to use "relative", "absolute" or "fixed"
+ * @param {Number} zIndex (optional) The zIndex to apply
+ * @param {Number} x (optional) Set the page X position
+ * @param {Number} y (optional) Set the page Y position
+ */
+ position : function(pos, zIndex, x, y){
+ var me = this;
+
+ if(!pos && me.isStyle(POSITION, STATIC)){
+ me.setStyle(POSITION, RELATIVE);
+ } else if(pos) {
+ me.setStyle(POSITION, pos);
+ }
+ if(zIndex){
+ me.setStyle(ZINDEX, zIndex);
+ }
+ if(x || y) me.setXY([x || false, y || false]);
+ },
+
+ /**
+ * Clear positioning back to the default when the document was loaded
+ * @param {String} value (optional) The value to use for the left,right,top,bottom, defaults to '' (empty string). You could use 'auto'.
+ * @return {Ext.Element} this
+ */
+ clearPositioning : function(value){
+ value = value || '';
+ this.setStyle({
+ left : value,
+ right : value,
+ top : value,
+ bottom : value,
+ "z-index" : "",
+ position : STATIC
+ });
+ return this;
+ },
+
+ /**
+ * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
+ * snapshot before performing an update and then restoring the element.
+ * @return {Object}
+ */
+ getPositioning : function(){
+ var l = this.getStyle(LEFT);
+ var t = this.getStyle(TOP);
+ return {
+ "position" : this.getStyle(POSITION),
+ "left" : l,
+ "right" : l ? "" : this.getStyle(RIGHT),
+ "top" : t,
+ "bottom" : t ? "" : this.getStyle(BOTTOM),
+ "z-index" : this.getStyle(ZINDEX)
+ };
+ },
+
+ /**
+ * Set positioning with an object returned by getPositioning().
+ * @param {Object} posCfg
+ * @return {Ext.Element} this
+ */
+ setPositioning : function(pc){
+ var me = this,
+ style = me.dom.style;
+
+ me.setStyle(pc);
+
+ if(pc.right == AUTO){
+ style.right = "";
+ }
+ if(pc.bottom == AUTO){
+ style.bottom = "";
+ }
+
+ return me;
+ },
+
+ /**
+ * Translates the passed page coordinates into left/top css values for this element
+ * @param {Number/Array} x The page x or an array containing [x, y]
+ * @param {Number} y (optional) The page y, required if x is not an array
+ * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
+ */
+ translatePoints : function(x, y){
+ y = isNaN(x[1]) ? y : x[1];
+ x = isNaN(x[0]) ? x : x[0];
+ var me = this,
+ relative = me.isStyle(POSITION, RELATIVE),
+ o = me.getXY(),
+ l = parseInt(me.getStyle(LEFT), 10),
+ t = parseInt(me.getStyle(TOP), 10);
+
+ l = !isNaN(l) ? l : (relative ? 0 : me.dom.offsetLeft);
+ t = !isNaN(t) ? t : (relative ? 0 : me.dom.offsetTop);
+
+ return {left: (x - o[0] + l), top: (y - o[1] + t)};
+ },
+
+ animTest : function(args, animate, i) {
+ return !!animate && this.preanim ? this.preanim(args, i) : false;
+ }
+});
+})();/**
+ * @class Ext.Element
+ */
+Ext.Element.addMethods({
+ /**
+ * Sets the element's box. Use getBox() on another element to get a box obj. If animate is true then width, height, x and y will be animated concurrently.
+ * @param {Object} box The box to fill {x, y, width, height}
+ * @param {Boolean} adjust (optional) Whether to adjust for box-model issues automatically
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ setBox : function(box, adjust, animate){
+ var me = this,
+ w = box.width,
+ h = box.height;
+ if((adjust && !me.autoBoxAdjust) && !me.isBorderBox()){
+ w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
+ h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
+ }
+ me.setBounds(box.x, box.y, w, h, me.animTest.call(me, arguments, animate, 2));
+ return me;
+ },
+
+ /**
+ * Return an object defining the area of this Element which can be passed to {@link #setBox} to
+ * set another Element's size/location to match this element.
+ * @param {Boolean} contentBox (optional) If true a box for the content of the element is returned.
+ * @param {Boolean} local (optional) If true the element's left and top are returned instead of page x/y.
+ * @return {Object} box An object in the format<pre><code>
+{
+ x: <Element's X position>,
+ y: <Element's Y position>,
+ width: <Element's width>,
+ height: <Element's height>,
+ bottom: <Element's lower bound>,
+ right: <Element's rightmost bound>
+}
+</code></pre>
+ * The returned object may also be addressed as an Array where index 0 contains the X position
+ * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
+ */
+ getBox : function(contentBox, local) {
+ var me = this,
+ xy,
+ left,
+ top,
+ getBorderWidth = me.getBorderWidth,
+ getPadding = me.getPadding,
+ l,
+ r,
+ t,
+ b;
+ if(!local){
+ xy = me.getXY();
+ }else{
+ left = parseInt(me.getStyle("left"), 10) || 0;
+ top = parseInt(me.getStyle("top"), 10) || 0;
+ xy = [left, top];
+ }
+ var el = me.dom, w = el.offsetWidth, h = el.offsetHeight, bx;
+ if(!contentBox){
+ bx = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: w, height: h};
+ }else{
+ l = getBorderWidth.call(me, "l") + getPadding.call(me, "l");
+ r = getBorderWidth.call(me, "r") + getPadding.call(me, "r");
+ t = getBorderWidth.call(me, "t") + getPadding.call(me, "t");
+ b = getBorderWidth.call(me, "b") + getPadding.call(me, "b");
+ bx = {x: xy[0]+l, y: xy[1]+t, 0: xy[0]+l, 1: xy[1]+t, width: w-(l+r), height: h-(t+b)};
+ }
+ bx.right = bx.x + bx.width;
+ bx.bottom = bx.y + bx.height;
+ return bx;
+ },
+
+ /**
+ * Move this element relative to its current position.
+ * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
+ * @param {Number} distance How far to move the element in pixels
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ move : function(direction, distance, animate){
+ var me = this,
+ xy = me.getXY(),
+ x = xy[0],
+ y = xy[1],
+ left = [x - distance, y],
+ right = [x + distance, y],
+ top = [x, y - distance],
+ bottom = [x, y + distance],
+ hash = {
+ l : left,
+ left : left,
+ r : right,
+ right : right,
+ t : top,
+ top : top,
+ up : top,
+ b : bottom,
+ bottom : bottom,
+ down : bottom
+ };
+
+ direction = direction.toLowerCase();
+ me.moveTo(hash[direction][0], hash[direction][1], me.animTest.call(me, arguments, animate, 2));
+ },
+
+ /**
+ * Quick set left and top adding default units
+ * @param {String} left The left CSS property value
+ * @param {String} top The top CSS property value
+ * @return {Ext.Element} this
+ */
+ setLeftTop : function(left, top){
+ var me = this,
+ style = me.dom.style;
+ style.left = me.addUnits(left);
+ style.top = me.addUnits(top);
+ return me;
+ },
+
+ /**
+ * Returns the region of the given element.
+ * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
+ * @return {Region} A Ext.lib.Region containing "top, left, bottom, right" member data.
+ */
+ getRegion : function(){
+ return Ext.lib.Dom.getRegion(this.dom);
+ },
+
+ /**
+ * Sets the element's position and size in one shot. If animation is true then width, height, x and y will be animated concurrently.
+ * @param {Number} x X value for new position (coordinates are page-based)
+ * @param {Number} y Y value for new position (coordinates are page-based)
+ * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)</li>
+ * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
+ * </ul></div>
+ * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)</li>
+ * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
+ * </ul></div>
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ setBounds : function(x, y, width, height, animate){
+ var me = this;
+ if (!animate || !me.anim) {
+ me.setSize(width, height);
+ me.setLocation(x, y);
+ } else {
+ me.anim({points: {to: [x, y]},
+ width: {to: me.adjustWidth(width)},
+ height: {to: me.adjustHeight(height)}},
+ me.preanim(arguments, 4),
+ 'motion');
+ }
+ return me;
+ },
+
+ /**
+ * Sets the element's position and size the specified region. If animation is true then width, height, x and y will be animated concurrently.
+ * @param {Ext.lib.Region} region The region to fill
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ setRegion : function(region, animate) {
+ return this.setBounds(region.left, region.top, region.right-region.left, region.bottom-region.top, this.animTest.call(this, arguments, animate, 1));
+ }
+});/**
+ * @class Ext.Element
+ */
+Ext.Element.addMethods({
+ /**
+ * Returns true if this element is scrollable.
+ * @return {Boolean}
+ */
+ isScrollable : function(){
+ var dom = this.dom;
+ return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
+ },
+
+ /**
+ * Scrolls this element the specified scroll point. It does NOT do bounds checking so if you scroll to a weird value it will try to do it. For auto bounds checking, use scroll().
+ * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
+ * @param {Number} value The new scroll value.
+ * @return {Element} this
+ */
+ scrollTo : function(side, value){
+ this.dom["scroll" + (/top/i.test(side) ? "Top" : "Left")] = value;
+ return this;
+ },
+
+ /**
+ * Returns the current scroll position of the element.
+ * @return {Object} An object containing the scroll position in the format {left: (scrollLeft), top: (scrollTop)}
+ */
+ getScroll : function(){
+ var d = this.dom,
+ doc = document,
+ body = doc.body,
+ docElement = doc.documentElement,
+ l,
+ t,
+ ret;
+
+ if(d == doc || d == body){
+ if(Ext.isIE && Ext.isStrict){
+ l = docElement.scrollLeft;
+ t = docElement.scrollTop;
+ }else{
+ l = window.pageXOffset;
+ t = window.pageYOffset;
+ }
+ ret = {left: l || (body ? body.scrollLeft : 0), top: t || (body ? body.scrollTop : 0)};
+ }else{
+ ret = {left: d.scrollLeft, top: d.scrollTop};
+ }
+ return ret;
+ }
+});/**
+ * @class Ext.Element
+ */
+Ext.Element.addMethods({
+ /**
+ * Scrolls this element the specified scroll point. It does NOT do bounds checking so if you scroll to a weird value it will try to do it. For auto bounds checking, use scroll().
+ * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
+ * @param {Number} value The new scroll value
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Element} this
+ */
+ scrollTo : function(side, value, animate){
+ var top = /top/i.test(side), //check if we're scrolling top or left
+ me = this,
+ dom = me.dom,
+ prop;
+ if (!animate || !me.anim) {
+ prop = 'scroll' + (top ? 'Top' : 'Left'), // just setting the value, so grab the direction
+ dom[prop] = value;
+ }else{
+ prop = 'scroll' + (top ? 'Left' : 'Top'), // if scrolling top, we need to grab scrollLeft, if left, scrollTop
+ me.anim({scroll: {to: top ? [dom[prop], value] : [value, dom[prop]]}},
+ me.preanim(arguments, 2), 'scroll');
+ }
+ return me;
+ },
+
+ /**
+ * Scrolls this element into view within the passed container.
+ * @param {Mixed} container (optional) The container element to scroll (defaults to document.body). Should be a
+ * string (id), dom node, or Ext.Element.
+ * @param {Boolean} hscroll (optional) False to disable horizontal scroll (defaults to true)
+ * @return {Ext.Element} this
+ */
+ scrollIntoView : function(container, hscroll){
+ var c = Ext.getDom(container) || Ext.getBody().dom,
+ el = this.dom,
+ o = this.getOffsetsTo(c),
+ l = o[0] + c.scrollLeft,
+ t = o[1] + c.scrollTop,
+ b = t + el.offsetHeight,
+ r = l + el.offsetWidth,
+ ch = c.clientHeight,
+ ct = parseInt(c.scrollTop, 10),
+ cl = parseInt(c.scrollLeft, 10),
+ cb = ct + ch,
+ cr = cl + c.clientWidth;
+
+ if (el.offsetHeight > ch || t < ct) {
+ c.scrollTop = t;
+ } else if (b > cb){
+ c.scrollTop = b-ch;
+ }
+ c.scrollTop = c.scrollTop; // corrects IE, other browsers will ignore
+
+ if(hscroll !== false){
+ if(el.offsetWidth > c.clientWidth || l < cl){
+ c.scrollLeft = l;
+ }else if(r > cr){
+ c.scrollLeft = r - c.clientWidth;
+ }
+ c.scrollLeft = c.scrollLeft;
+ }
+ return this;
+ },
+
+ // private
+ scrollChildIntoView : function(child, hscroll){
+ Ext.fly(child, '_scrollChildIntoView').scrollIntoView(this, hscroll);
+ },
+
+ /**
+ * Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
+ * within this element's scrollable range.
+ * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
+ * @param {Number} distance How far to scroll the element in pixels
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Boolean} Returns true if a scroll was triggered or false if the element
+ * was scrolled as far as it could go.
+ */
+ scroll : function(direction, distance, animate){
+ if(!this.isScrollable()){
+ return;
+ }
+ var el = this.dom,
+ l = el.scrollLeft, t = el.scrollTop,
+ w = el.scrollWidth, h = el.scrollHeight,
+ cw = el.clientWidth, ch = el.clientHeight,
+ scrolled = false, v,
+ hash = {
+ l: Math.min(l + distance, w-cw),
+ r: v = Math.max(l - distance, 0),
+ t: Math.max(t - distance, 0),
+ b: Math.min(t + distance, h-ch)
+ };
+ hash.d = hash.b;
+ hash.u = hash.t;
+
+ direction = direction.substr(0, 1);
+ if((v = hash[direction]) > -1){
+ scrolled = true;
+ this.scrollTo(direction == 'l' || direction == 'r' ? 'left' : 'top', v, this.preanim(arguments, 2));
+ }
+ return scrolled;
+ }
+});/**
+ * @class Ext.Element
+ */
+/**
+ * Visibility mode constant for use with {@link #setVisibilityMode}. Use visibility to hide element
+ * @static
+ * @type Number
+ */
+Ext.Element.VISIBILITY = 1;
+/**
+ * Visibility mode constant for use with {@link #setVisibilityMode}. Use display to hide element
+ * @static
+ * @type Number
+ */
+Ext.Element.DISPLAY = 2;
+
+Ext.Element.addMethods(function(){
+ var VISIBILITY = "visibility",
+ DISPLAY = "display",
+ HIDDEN = "hidden",
+ OFFSETS = "offsets",
+ NONE = "none",
+ ORIGINALDISPLAY = 'originalDisplay',
+ VISMODE = 'visibilityMode',
+ ELDISPLAY = Ext.Element.DISPLAY,
+ data = Ext.Element.data,
+ getDisplay = function(dom){
+ var d = data(dom, ORIGINALDISPLAY);
+ if(d === undefined){
+ data(dom, ORIGINALDISPLAY, d = '');
+ }
+ return d;
+ },
+ getVisMode = function(dom){
+ var m = data(dom, VISMODE);
+ if(m === undefined){
+ data(dom, VISMODE, m = 1);
+ }
+ return m;
+ };
+
+ return {
+ /**
+ * The element's default display mode (defaults to "")
+ * @type String
+ */
+ originalDisplay : "",
+ visibilityMode : 1,
+
+ /**
+ * Sets the element's visibility mode. When setVisible() is called it
+ * will use this to determine whether to set the visibility or the display property.
+ * @param {Number} visMode Ext.Element.VISIBILITY or Ext.Element.DISPLAY
+ * @return {Ext.Element} this
+ */
+ setVisibilityMode : function(visMode){
+ data(this.dom, VISMODE, visMode);
+ return this;
+ },
+
+ /**
+ * Perform custom animation on this element.
+ * <div><ul class="mdetail-params">
+ * <li><u>Animation Properties</u></li>
+ *
+ * <p>The Animation Control Object enables gradual transitions for any member of an
+ * element's style object that takes a numeric value including but not limited to
+ * these properties:</p><div><ul class="mdetail-params">
+ * <li><tt>bottom, top, left, right</tt></li>
+ * <li><tt>height, width</tt></li>
+ * <li><tt>margin, padding</tt></li>
+ * <li><tt>borderWidth</tt></li>
+ * <li><tt>opacity</tt></li>
+ * <li><tt>fontSize</tt></li>
+ * <li><tt>lineHeight</tt></li>
+ * </ul></div>
+ *
+ *
+ * <li><u>Animation Property Attributes</u></li>
+ *
+ * <p>Each Animation Property is a config object with optional properties:</p>
+ * <div><ul class="mdetail-params">
+ * <li><tt>by</tt>* : relative change - start at current value, change by this value</li>
+ * <li><tt>from</tt> : ignore current value, start from this value</li>
+ * <li><tt>to</tt>* : start at current value, go to this value</li>
+ * <li><tt>unit</tt> : any allowable unit specification</li>
+ * <p>* do not specify both <tt>to</tt> and <tt>by</tt> for an animation property</p>
+ * </ul></div>
+ *
+ * <li><u>Animation Types</u></li>
+ *
+ * <p>The supported animation types:</p><div><ul class="mdetail-params">
+ * <li><tt>'run'</tt> : Default
+ * <pre><code>
+var el = Ext.get('complexEl');
+el.animate(
+ // animation control object
+ {
+ borderWidth: {to: 3, from: 0},
+ opacity: {to: .3, from: 1},
+ height: {to: 50, from: el.getHeight()},
+ width: {to: 300, from: el.getWidth()},
+ top : {by: - 100, unit: 'px'},
+ },
+ 0.35, // animation duration
+ null, // callback
+ 'easeOut', // easing method
+ 'run' // animation type ('run','color','motion','scroll')
+);
+ * </code></pre>
+ * </li>
+ * <li><tt>'color'</tt>
+ * <p>Animates transition of background, text, or border colors.</p>
+ * <pre><code>
+el.animate(
+ // animation control object
+ {
+ color: { to: '#06e' },
+ backgroundColor: { to: '#e06' }
+ },
+ 0.35, // animation duration
+ null, // callback
+ 'easeOut', // easing method
+ 'color' // animation type ('run','color','motion','scroll')
+);
+ * </code></pre>
+ * </li>
+ *
+ * <li><tt>'motion'</tt>
+ * <p>Animates the motion of an element to/from specific points using optional bezier
+ * way points during transit.</p>
+ * <pre><code>
+el.animate(
+ // animation control object
+ {
+ borderWidth: {to: 3, from: 0},
+ opacity: {to: .3, from: 1},
+ height: {to: 50, from: el.getHeight()},
+ width: {to: 300, from: el.getWidth()},
+ top : {by: - 100, unit: 'px'},
+ points: {
+ to: [50, 100], // go to this point
+ control: [ // optional bezier way points
+ [ 600, 800],
+ [-100, 200]
+ ]
+ }
+ },
+ 3000, // animation duration (milliseconds!)
+ null, // callback
+ 'easeOut', // easing method
+ 'motion' // animation type ('run','color','motion','scroll')
+);
+ * </code></pre>
+ * </li>
+ * <li><tt>'scroll'</tt>
+ * <p>Animate horizontal or vertical scrolling of an overflowing page element.</p>
+ * <pre><code>
+el.animate(
+ // animation control object
+ {
+ scroll: {to: [400, 300]}
+ },
+ 0.35, // animation duration
+ null, // callback
+ 'easeOut', // easing method
+ 'scroll' // animation type ('run','color','motion','scroll')
+);
+ * </code></pre>
+ * </li>
+ * </ul></div>
+ *
+ * </ul></div>
+ *
+ * @param {Object} args The animation control args
+ * @param {Float} duration (optional) How long the animation lasts in seconds (defaults to <tt>.35</tt>)
+ * @param {Function} onComplete (optional) Function to call when animation completes
+ * @param {String} easing (optional) {@link Ext.Fx#easing} method to use (defaults to <tt>'easeOut'</tt>)
+ * @param {String} animType (optional) <tt>'run'</tt> is the default. Can also be <tt>'color'</tt>,
+ * <tt>'motion'</tt>, or <tt>'scroll'</tt>
+ * @return {Ext.Element} this
+ */
+ animate : function(args, duration, onComplete, easing, animType){
+ this.anim(args, {duration: duration, callback: onComplete, easing: easing}, animType);
+ return this;
+ },
+
+ /*
+ * @private Internal animation call
+ */
+ anim : function(args, opt, animType, defaultDur, defaultEase, cb){
+ animType = animType || 'run';
+ opt = opt || {};
+ var me = this,
+ anim = Ext.lib.Anim[animType](
+ me.dom,
+ args,
+ (opt.duration || defaultDur) || .35,
+ (opt.easing || defaultEase) || 'easeOut',
+ function(){
+ if(cb) cb.call(me);
+ if(opt.callback) opt.callback.call(opt.scope || me, me, opt);
+ },
+ me
+ );
+ opt.anim = anim;
+ return anim;
+ },
+
+ // private legacy anim prep
+ preanim : function(a, i){
+ return !a[i] ? false : (Ext.isObject(a[i]) ? a[i]: {duration: a[i+1], callback: a[i+2], easing: a[i+3]});
+ },
+
+ /**
+ * Checks whether the element is currently visible using both visibility and display properties.
+ * @return {Boolean} True if the element is currently visible, else false
+ */
+ isVisible : function() {
+ return !this.isStyle(VISIBILITY, HIDDEN) && !this.isStyle(DISPLAY, NONE);
+ },
+
+ /**
+ * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
+ * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
+ * @param {Boolean} visible Whether the element is visible
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ setVisible : function(visible, animate){
+ var me = this, isDisplay, isVisible, isOffsets,
+ dom = me.dom;
+
+ // hideMode string override
+ if (Ext.isString(animate)){
+ isDisplay = animate == DISPLAY;
+ isVisible = animate == VISIBILITY;
+ isOffsets = animate == OFFSETS;
+ animate = false;
+ } else {
+ isDisplay = getVisMode(this.dom) == ELDISPLAY;
+ isVisible = !isDisplay;
+ }
+
+ if (!animate || !me.anim) {
+ if (isDisplay){
+ me.setDisplayed(visible);
+ } else if (isOffsets){
+ if (!visible){
+ me.hideModeStyles = {
+ position: me.getStyle('position'),
+ top: me.getStyle('top'),
+ left: me.getStyle('left')
+ };
+
+ me.applyStyles({position: 'absolute', top: '-10000px', left: '-10000px'});
+ } else {
+ me.applyStyles(me.hideModeStyles || {position: '', top: '', left: ''});
+ }
+ }else{
+ me.fixDisplay();
+ dom.style.visibility = visible ? "visible" : HIDDEN;
+ }
+ }else{
+ // closure for composites
+ if (visible){
+ me.setOpacity(.01);
+ me.setVisible(true);
+ }
+ me.anim({opacity: { to: (visible?1:0) }},
+ me.preanim(arguments, 1),
+ null,
+ .35,
+ 'easeIn',
+ function(){
+ if(!visible){
+ dom.style[isDisplay ? DISPLAY : VISIBILITY] = (isDisplay) ? NONE : HIDDEN;
+ Ext.fly(dom).setOpacity(1);
+ }
+ });
+ }
+ return me;
+ },
+
+ /**
+ * Toggles the element's visibility or display, depending on visibility mode.
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ toggle : function(animate){
+ var me = this;
+ me.setVisible(!me.isVisible(), me.preanim(arguments, 0));
+ return me;
+ },
+
+ /**
+ * Sets the CSS display property. Uses originalDisplay if the specified value is a boolean true.
+ * @param {Mixed} value Boolean value to display the element using its default display, or a string to set the display directly.
+ * @return {Ext.Element} this
+ */
+ setDisplayed : function(value) {
+ if(typeof value == "boolean"){
+ value = value ? getDisplay(this.dom) : NONE;
+ }
+ this.setStyle(DISPLAY, value);
+ return this;
+ },
+
+ // private
+ fixDisplay : function(){
+ var me = this;
+ if(me.isStyle(DISPLAY, NONE)){
+ me.setStyle(VISIBILITY, HIDDEN);
+ me.setStyle(DISPLAY, getDisplay(this.dom)); // first try reverting to default
+ if(me.isStyle(DISPLAY, NONE)){ // if that fails, default to block
+ me.setStyle(DISPLAY, "block");
+ }
+ }
+ },
+
+ /**
+ * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ hide : function(animate){
+ // hideMode override
+ if (Ext.isString(animate)){
+ this.setVisible(false, animate);
+ return this;
+ }
+ this.setVisible(false, this.preanim(arguments, 0));
+ return this;
+ },
+
+ /**
+ * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.Element} this
+ */
+ show : function(animate){
+ // hideMode override
+ if (Ext.isString(animate)){
+ this.setVisible(true, animate);
+ return this;
+ }
+ this.setVisible(true, this.preanim(arguments, 0));
+ return this;
+ }
+ };
+}());/**
+ * @class Ext.Element
+ */
+Ext.Element.addMethods(
+function(){
+ var VISIBILITY = "visibility",
+ DISPLAY = "display",
+ HIDDEN = "hidden",
+ NONE = "none",
+ XMASKED = "x-masked",
+ XMASKEDRELATIVE = "x-masked-relative",
+ data = Ext.Element.data;
+
+ return {
+ /**
+ * Checks whether the element is currently visible using both visibility and display properties.
+ * @param {Boolean} deep (optional) True to walk the dom and see if parent elements are hidden (defaults to false)
+ * @return {Boolean} True if the element is currently visible, else false
+ */
+ isVisible : function(deep) {
+ var vis = !this.isStyle(VISIBILITY,HIDDEN) && !this.isStyle(DISPLAY,NONE),
+ p = this.dom.parentNode;
+ if(deep !== true || !vis){
+ return vis;
+ }
+ while(p && !/^body/i.test(p.tagName)){
+ if(!Ext.fly(p, '_isVisible').isVisible()){
+ return false;
+ }
+ p = p.parentNode;
+ }
+ return true;
+ },
+
+ /**
+ * Returns true if display is not "none"
+ * @return {Boolean}
+ */
+ isDisplayed : function() {
+ return !this.isStyle(DISPLAY, NONE);
+ },
+
+ /**
+ * Convenience method for setVisibilityMode(Element.DISPLAY)
+ * @param {String} display (optional) What to set display to when visible
+ * @return {Ext.Element} this
+ */
+ enableDisplayMode : function(display){
+ this.setVisibilityMode(Ext.Element.DISPLAY);
+ if(!Ext.isEmpty(display)){
+ data(this.dom, 'originalDisplay', display);
+ }
+ return this;
+ },
+
+ /**
+ * Puts a mask over this element to disable user interaction. Requires core.css.
+ * This method can only be applied to elements which accept child nodes.
+ * @param {String} msg (optional) A message to display in the mask
+ * @param {String} msgCls (optional) A css class to apply to the msg element
+ * @return {Element} The mask element
+ */
+ mask : function(msg, msgCls){
+ var me = this,
+ dom = me.dom,
+ dh = Ext.DomHelper,
+ EXTELMASKMSG = "ext-el-mask-msg",
+ el,
+ mask;
+
+ if(!/^body/i.test(dom.tagName) && me.getStyle('position') == 'static'){
+ me.addClass(XMASKEDRELATIVE);
+ }
+ if((el = data(dom, 'maskMsg'))){
+ el.remove();
+ }
+ if((el = data(dom, 'mask'))){
+ el.remove();
+ }
+
+ mask = dh.append(dom, {cls : "ext-el-mask"}, true);
+ data(dom, 'mask', mask);
+
+ me.addClass(XMASKED);
+ mask.setDisplayed(true);
+ if(typeof msg == 'string'){
+ var mm = dh.append(dom, {cls : EXTELMASKMSG, cn:{tag:'div'}}, true);
+ data(dom, 'maskMsg', mm);
+ mm.dom.className = msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG;
+ mm.dom.firstChild.innerHTML = msg;
+ mm.setDisplayed(true);
+ mm.center(me);
+ }
+ if(Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto'){ // ie will not expand full height automatically
+ mask.setSize(undefined, me.getHeight());
+ }
+ return mask;
+ },
+
+ /**
+ * Removes a previously applied mask.
+ */
+ unmask : function(){
+ var me = this,
+ dom = me.dom,
+ mask = data(dom, 'mask'),
+ maskMsg = data(dom, 'maskMsg');
+ if(mask){
+ if(maskMsg){
+ maskMsg.remove();
+ data(dom, 'maskMsg', undefined);
+ }
+ mask.remove();
+ data(dom, 'mask', undefined);
+ }
+ me.removeClass([XMASKED, XMASKEDRELATIVE]);
+ },
+
+ /**
+ * Returns true if this element is masked
+ * @return {Boolean}
+ */
+ isMasked : function(){
+ var m = data(this.dom, 'mask');
+ return m && m.isVisible();
+ },
+
+ /**
+ * Creates an iframe shim for this element to keep selects and other windowed objects from
+ * showing through.
+ * @return {Ext.Element} The new shim element
+ */
+ createShim : function(){
+ var el = document.createElement('iframe'),
+ shim;
+ el.frameBorder = '0';
+ el.className = 'ext-shim';
+ el.src = Ext.SSL_SECURE_URL;
+ shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom));
+ shim.autoBoxAdjust = false;
+ return shim;
+ }
+ };
+}());/**
+ * @class Ext.Element
+ */
+Ext.Element.addMethods({
+ /**
+ * Convenience method for constructing a KeyMap
+ * @param {Number/Array/Object/String} key Either a string with the keys to listen for, the numeric key code, array of key codes or an object with the following options:
+ * <code>{key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}</code>
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the specified function is executed. Defaults to this Element.
+ * @return {Ext.KeyMap} The KeyMap created
+ */
+ addKeyListener : function(key, fn, scope){
+ var config;
+ if(!Ext.isObject(key) || Ext.isArray(key)){
+ config = {
+ key: key,
+ fn: fn,
+ scope: scope
+ };
+ }else{
+ config = {
+ key : key.key,
+ shift : key.shift,
+ ctrl : key.ctrl,
+ alt : key.alt,
+ fn: fn,
+ scope: scope
+ };
+ }
+ return new Ext.KeyMap(this, config);
+ },
+
+ /**
+ * Creates a KeyMap for this element
+ * @param {Object} config The KeyMap config. See {@link Ext.KeyMap} for more details
+ * @return {Ext.KeyMap} The KeyMap created
+ */
+ addKeyMap : function(config){
+ return new Ext.KeyMap(this, config);
+ }
+});(function(){
+ // contants
+ var NULL = null,
+ UNDEFINED = undefined,
+ TRUE = true,
+ FALSE = false,
+ SETX = "setX",
+ SETY = "setY",
+ SETXY = "setXY",
+ LEFT = "left",
+ BOTTOM = "bottom",
+ TOP = "top",
+ RIGHT = "right",
+ HEIGHT = "height",
+ WIDTH = "width",
+ POINTS = "points",
+ HIDDEN = "hidden",
+ ABSOLUTE = "absolute",
+ VISIBLE = "visible",
+ MOTION = "motion",
+ POSITION = "position",
+ EASEOUT = "easeOut",
+ /*
+ * Use a light flyweight here since we are using so many callbacks and are always assured a DOM element
+ */
+ flyEl = new Ext.Element.Flyweight(),
+ queues = {},
+ getObject = function(o){
+ return o || {};
+ },
+ fly = function(dom){
+ flyEl.dom = dom;
+ flyEl.id = Ext.id(dom);
+ return flyEl;
+ },
+ /*
+ * Queueing now stored outside of the element due to closure issues
+ */
+ getQueue = function(id){
+ if(!queues[id]){
+ queues[id] = [];
+ }
+ return queues[id];
+ },
+ setQueue = function(id, value){
+ queues[id] = value;
+ };
+
+//Notifies Element that fx methods are available
+Ext.enableFx = TRUE;
+
+/**
+ * @class Ext.Fx
+ * <p>A class to provide basic animation and visual effects support. <b>Note:</b> This class is automatically applied
+ * to the {@link Ext.Element} interface when included, so all effects calls should be performed via {@link Ext.Element}.
+ * Conversely, since the effects are not actually defined in {@link Ext.Element}, Ext.Fx <b>must</b> be
+ * {@link Ext#enableFx included} in order for the Element effects to work.</p><br/>
+ *
+ * <p><b><u>Method Chaining</u></b></p>
+ * <p>It is important to note that although the Fx methods and many non-Fx Element methods support "method chaining" in that
+ * they return the Element object itself as the method return value, it is not always possible to mix the two in a single
+ * method chain. The Fx methods use an internal effects queue so that each effect can be properly timed and sequenced.
+ * Non-Fx methods, on the other hand, have no such internal queueing and will always execute immediately. For this reason,
+ * while it may be possible to mix certain Fx and non-Fx method calls in a single chain, it may not always provide the
+ * expected results and should be done with care. Also see <tt>{@link #callback}</tt>.</p><br/>
+ *
+ * <p><b><u>Anchor Options for Motion Effects</u></b></p>
+ * <p>Motion effects support 8-way anchoring, meaning that you can choose one of 8 different anchor points on the Element
+ * that will serve as either the start or end point of the animation. Following are all of the supported anchor positions:</p>
+<pre>
+Value Description
+----- -----------------------------
+tl The top left corner
+t The center of the top edge
+tr The top right corner
+l The center of the left edge
+r The center of the right edge
+bl The bottom left corner
+b The center of the bottom edge
+br The bottom right corner
+</pre>
+ * <b>Note</b>: some Fx methods accept specific custom config parameters. The options shown in the Config Options
+ * section below are common options that can be passed to any Fx method unless otherwise noted.</b>
+ *
+ * @cfg {Function} callback A function called when the effect is finished. Note that effects are queued internally by the
+ * Fx class, so a callback is not required to specify another effect -- effects can simply be chained together
+ * and called in sequence (see note for <b><u>Method Chaining</u></b> above), for example:<pre><code>
+ * el.slideIn().highlight();
+ * </code></pre>
+ * The callback is intended for any additional code that should run once a particular effect has completed. The Element
+ * being operated upon is passed as the first parameter.
+ *
+ * @cfg {Object} scope The scope (<code>this</code> reference) in which the <tt>{@link #callback}</tt> function is executed. Defaults to the browser window.
+ *
+ * @cfg {String} easing A valid Ext.lib.Easing value for the effect:</p><div class="mdetail-params"><ul>
+ * <li><b><tt>backBoth</tt></b></li>
+ * <li><b><tt>backIn</tt></b></li>
+ * <li><b><tt>backOut</tt></b></li>
+ * <li><b><tt>bounceBoth</tt></b></li>
+ * <li><b><tt>bounceIn</tt></b></li>
+ * <li><b><tt>bounceOut</tt></b></li>
+ * <li><b><tt>easeBoth</tt></b></li>
+ * <li><b><tt>easeBothStrong</tt></b></li>
+ * <li><b><tt>easeIn</tt></b></li>
+ * <li><b><tt>easeInStrong</tt></b></li>
+ * <li><b><tt>easeNone</tt></b></li>
+ * <li><b><tt>easeOut</tt></b></li>
+ * <li><b><tt>easeOutStrong</tt></b></li>
+ * <li><b><tt>elasticBoth</tt></b></li>
+ * <li><b><tt>elasticIn</tt></b></li>
+ * <li><b><tt>elasticOut</tt></b></li>
+ * </ul></div>
+ *
+ * @cfg {String} afterCls A css class to apply after the effect
+ * @cfg {Number} duration The length of time (in seconds) that the effect should last
+ *
+ * @cfg {Number} endOpacity Only applicable for {@link #fadeIn} or {@link #fadeOut}, a number between
+ * <tt>0</tt> and <tt>1</tt> inclusive to configure the ending opacity value.
+ *
+ * @cfg {Boolean} remove Whether the Element should be removed from the DOM and destroyed after the effect finishes
+ * @cfg {Boolean} useDisplay Whether to use the <i>display</i> CSS property instead of <i>visibility</i> when hiding Elements (only applies to
+ * effects that end with the element being visually hidden, ignored otherwise)
+ * @cfg {String/Object/Function} afterStyle A style specification string, e.g. <tt>"width:100px"</tt>, or an object
+ * in the form <tt>{width:"100px"}</tt>, or a function which returns such a specification that will be applied to the
+ * Element after the effect finishes.
+ * @cfg {Boolean} block Whether the effect should block other effects from queueing while it runs
+ * @cfg {Boolean} concurrent Whether to allow subsequently-queued effects to run at the same time as the current effect, or to ensure that they run in sequence
+ * @cfg {Boolean} stopFx Whether preceding effects should be stopped and removed before running current effect (only applies to non blocking effects)
+ */
+Ext.Fx = {
+
+ // private - calls the function taking arguments from the argHash based on the key. Returns the return value of the function.
+ // this is useful for replacing switch statements (for example).
+ switchStatements : function(key, fn, argHash){
+ return fn.apply(this, argHash[key]);
+ },
+
+ /**
+ * Slides the element into view. An anchor point can be optionally passed to set the point of
+ * origin for the slide effect. This function automatically handles wrapping the element with
+ * a fixed-size container if needed. See the Fx class overview for valid anchor point options.
+ * Usage:
+ *<pre><code>
+// default: slide the element in from the top
+el.slideIn();
+
+// custom: slide the element in from the right with a 2-second duration
+el.slideIn('r', { duration: 2 });
+
+// common config options shown with default values
+el.slideIn('t', {
+ easing: 'easeOut',
+ duration: .5
+});
+</code></pre>
+ * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ slideIn : function(anchor, o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ st = dom.style,
+ xy,
+ r,
+ b,
+ wrap,
+ after,
+ st,
+ args,
+ pt,
+ bw,
+ bh;
+
+ anchor = anchor || "t";
+
+ me.queueFx(o, function(){
+ xy = fly(dom).getXY();
+ // fix display to visibility
+ fly(dom).fixDisplay();
+
+ // restore values after effect
+ r = fly(dom).getFxRestore();
+ b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight};
+ b.right = b.x + b.width;
+ b.bottom = b.y + b.height;
+
+ // fixed size for slide
+ fly(dom).setWidth(b.width).setHeight(b.height);
+
+ // wrap if needed
+ wrap = fly(dom).fxWrap(r.pos, o, HIDDEN);
+
+ st.visibility = VISIBLE;
+ st.position = ABSOLUTE;
+
+ // clear out temp styles after slide and unwrap
+ function after(){
+ fly(dom).fxUnwrap(wrap, r.pos, o);
+ st.width = r.width;
+ st.height = r.height;
+ fly(dom).afterFx(o);
+ }
+
+ // time to calculate the positions
+ pt = {to: [b.x, b.y]};
+ bw = {to: b.width};
+ bh = {to: b.height};
+
+ function argCalc(wrap, style, ww, wh, sXY, sXYval, s1, s2, w, h, p){
+ var ret = {};
+ fly(wrap).setWidth(ww).setHeight(wh);
+ if(fly(wrap)[sXY]){
+ fly(wrap)[sXY](sXYval);
+ }
+ style[s1] = style[s2] = "0";
+ if(w){
+ ret.width = w
+ };
+ if(h){
+ ret.height = h;
+ }
+ if(p){
+ ret.points = p;
+ }
+ return ret;
+ };
+
+ args = fly(dom).switchStatements(anchor.toLowerCase(), argCalc, {
+ t : [wrap, st, b.width, 0, NULL, NULL, LEFT, BOTTOM, NULL, bh, NULL],
+ l : [wrap, st, 0, b.height, NULL, NULL, RIGHT, TOP, bw, NULL, NULL],
+ r : [wrap, st, b.width, b.height, SETX, b.right, LEFT, TOP, NULL, NULL, pt],
+ b : [wrap, st, b.width, b.height, SETY, b.bottom, LEFT, TOP, NULL, bh, pt],
+ tl : [wrap, st, 0, 0, NULL, NULL, RIGHT, BOTTOM, bw, bh, pt],
+ bl : [wrap, st, 0, 0, SETY, b.y + b.height, RIGHT, TOP, bw, bh, pt],
+ br : [wrap, st, 0, 0, SETXY, [b.right, b.bottom], LEFT, TOP, bw, bh, pt],
+ tr : [wrap, st, 0, 0, SETX, b.x + b.width, LEFT, BOTTOM, bw, bh, pt]
+ });
+
+ st.visibility = VISIBLE;
+ fly(wrap).show();
+
+ arguments.callee.anim = fly(wrap).fxanim(args,
+ o,
+ MOTION,
+ .5,
+ EASEOUT,
+ after);
+ });
+ return me;
+ },
+
+ /**
+ * Slides the element out of view. An anchor point can be optionally passed to set the end point
+ * for the slide effect. When the effect is completed, the element will be hidden (visibility =
+ * 'hidden') but block elements will still take up space in the document. The element must be removed
+ * from the DOM using the 'remove' config option if desired. This function automatically handles
+ * wrapping the element with a fixed-size container if needed. See the Fx class overview for valid anchor point options.
+ * Usage:
+ *<pre><code>
+// default: slide the element out to the top
+el.slideOut();
+
+// custom: slide the element out to the right with a 2-second duration
+el.slideOut('r', { duration: 2 });
+
+// common config options shown with default values
+el.slideOut('t', {
+ easing: 'easeOut',
+ duration: .5,
+ remove: false,
+ useDisplay: false
+});
+</code></pre>
+ * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ slideOut : function(anchor, o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ st = dom.style,
+ xy = me.getXY(),
+ wrap,
+ r,
+ b,
+ a,
+ zero = {to: 0};
+
+ anchor = anchor || "t";
+
+ me.queueFx(o, function(){
+
+ // restore values after effect
+ r = fly(dom).getFxRestore();
+ b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight};
+ b.right = b.x + b.width;
+ b.bottom = b.y + b.height;
+
+ // fixed size for slide
+ fly(dom).setWidth(b.width).setHeight(b.height);
+
+ // wrap if needed
+ wrap = fly(dom).fxWrap(r.pos, o, VISIBLE);
+
+ st.visibility = VISIBLE;
+ st.position = ABSOLUTE;
+ fly(wrap).setWidth(b.width).setHeight(b.height);
+
+ function after(){
+ o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
+ fly(dom).fxUnwrap(wrap, r.pos, o);
+ st.width = r.width;
+ st.height = r.height;
+ fly(dom).afterFx(o);
+ }
+
+ function argCalc(style, s1, s2, p1, v1, p2, v2, p3, v3){
+ var ret = {};
+
+ style[s1] = style[s2] = "0";
+ ret[p1] = v1;
+ if(p2){
+ ret[p2] = v2;
+ }
+ if(p3){
+ ret[p3] = v3;
+ }
+
+ return ret;
+ };
+
+ a = fly(dom).switchStatements(anchor.toLowerCase(), argCalc, {
+ t : [st, LEFT, BOTTOM, HEIGHT, zero],
+ l : [st, RIGHT, TOP, WIDTH, zero],
+ r : [st, LEFT, TOP, WIDTH, zero, POINTS, {to : [b.right, b.y]}],
+ b : [st, LEFT, TOP, HEIGHT, zero, POINTS, {to : [b.x, b.bottom]}],
+ tl : [st, RIGHT, BOTTOM, WIDTH, zero, HEIGHT, zero],
+ bl : [st, RIGHT, TOP, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.x, b.bottom]}],
+ br : [st, LEFT, TOP, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.x + b.width, b.bottom]}],
+ tr : [st, LEFT, BOTTOM, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.right, b.y]}]
+ });
+
+ arguments.callee.anim = fly(wrap).fxanim(a,
+ o,
+ MOTION,
+ .5,
+ EASEOUT,
+ after);
+ });
+ return me;
+ },
+
+ /**
+ * Fades the element out while slowly expanding it in all directions. When the effect is completed, the
+ * element will be hidden (visibility = 'hidden') but block elements will still take up space in the document.
+ * The element must be removed from the DOM using the 'remove' config option if desired.
+ * Usage:
+ *<pre><code>
+// default
+el.puff();
+
+// common config options shown with default values
+el.puff({
+ easing: 'easeOut',
+ duration: .5,
+ remove: false,
+ useDisplay: false
+});
+</code></pre>
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ puff : function(o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ st = dom.style,
+ width,
+ height,
+ r;
+
+ me.queueFx(o, function(){
+ width = fly(dom).getWidth();
+ height = fly(dom).getHeight();
+ fly(dom).clearOpacity();
+ fly(dom).show();
+
+ // restore values after effect
+ r = fly(dom).getFxRestore();
+
+ function after(){
+ o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
+ fly(dom).clearOpacity();
+ fly(dom).setPositioning(r.pos);
+ st.width = r.width;
+ st.height = r.height;
+ st.fontSize = '';
+ fly(dom).afterFx(o);
+ }
+
+ arguments.callee.anim = fly(dom).fxanim({
+ width : {to : fly(dom).adjustWidth(width * 2)},
+ height : {to : fly(dom).adjustHeight(height * 2)},
+ points : {by : [-width * .5, -height * .5]},
+ opacity : {to : 0},
+ fontSize: {to : 200, unit: "%"}
+ },
+ o,
+ MOTION,
+ .5,
+ EASEOUT,
+ after);
+ });
+ return me;
+ },
+
+ /**
+ * Blinks the element as if it was clicked and then collapses on its center (similar to switching off a television).
+ * When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will still
+ * take up space in the document. The element must be removed from the DOM using the 'remove' config option if desired.
+ * Usage:
+ *<pre><code>
+// default
+el.switchOff();
+
+// all config options shown with default values
+el.switchOff({
+ easing: 'easeIn',
+ duration: .3,
+ remove: false,
+ useDisplay: false
+});
+</code></pre>
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ switchOff : function(o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ st = dom.style,
+ r;
+
+ me.queueFx(o, function(){
+ fly(dom).clearOpacity();
+ fly(dom).clip();
+
+ // restore values after effect
+ r = fly(dom).getFxRestore();
+
+ function after(){
+ o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
+ fly(dom).clearOpacity();
+ fly(dom).setPositioning(r.pos);
+ st.width = r.width;
+ st.height = r.height;
+ fly(dom).afterFx(o);
+ };
+
+ fly(dom).fxanim({opacity : {to : 0.3}},
+ NULL,
+ NULL,
+ .1,
+ NULL,
+ function(){
+ fly(dom).clearOpacity();
+ (function(){
+ fly(dom).fxanim({
+ height : {to : 1},
+ points : {by : [0, fly(dom).getHeight() * .5]}
+ },
+ o,
+ MOTION,
+ 0.3,
+ 'easeIn',
+ after);
+ }).defer(100);
+ });
+ });
+ return me;
+ },
+
+ /**
+ * Highlights the Element by setting a color (applies to the background-color by default, but can be
+ * changed using the "attr" config option) and then fading back to the original color. If no original
+ * color is available, you should provide the "endColor" config option which will be cleared after the animation.
+ * Usage:
+<pre><code>
+// default: highlight background to yellow
+el.highlight();
+
+// custom: highlight foreground text to blue for 2 seconds
+el.highlight("0000ff", { attr: 'color', duration: 2 });
+
+// common config options shown with default values
+el.highlight("ffff9c", {
+ attr: "background-color", //can be any valid CSS property (attribute) that supports a color value
+ endColor: (current color) or "ffffff",
+ easing: 'easeIn',
+ duration: 1
+});
+</code></pre>
+ * @param {String} color (optional) The highlight color. Should be a 6 char hex color without the leading # (defaults to yellow: 'ffff9c')
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ highlight : function(color, o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ attr = o.attr || "backgroundColor",
+ a = {},
+ restore;
+
+ me.queueFx(o, function(){
+ fly(dom).clearOpacity();
+ fly(dom).show();
+
+ function after(){
+ dom.style[attr] = restore;
+ fly(dom).afterFx(o);
+ }
+ restore = dom.style[attr];
+ a[attr] = {from: color || "ffff9c", to: o.endColor || fly(dom).getColor(attr) || "ffffff"};
+ arguments.callee.anim = fly(dom).fxanim(a,
+ o,
+ 'color',
+ 1,
+ 'easeIn',
+ after);
+ });
+ return me;
+ },
+
+ /**
+ * Shows a ripple of exploding, attenuating borders to draw attention to an Element.
+ * Usage:
+<pre><code>
+// default: a single light blue ripple
+el.frame();
+
+// custom: 3 red ripples lasting 3 seconds total
+el.frame("ff0000", 3, { duration: 3 });
+
+// common config options shown with default values
+el.frame("C3DAF9", 1, {
+ duration: 1 //duration of each individual ripple.
+ // Note: Easing is not configurable and will be ignored if included
+});
+</code></pre>
+ * @param {String} color (optional) The color of the border. Should be a 6 char hex color without the leading # (defaults to light blue: 'C3DAF9').
+ * @param {Number} count (optional) The number of ripples to display (defaults to 1)
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ frame : function(color, count, o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ proxy,
+ active;
+
+ me.queueFx(o, function(){
+ color = color || '#C3DAF9';
+ if(color.length == 6){
+ color = '#' + color;
+ }
+ count = count || 1;
+ fly(dom).show();
+
+ var xy = fly(dom).getXY(),
+ b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight},
+ queue = function(){
+ proxy = fly(document.body || document.documentElement).createChild({
+ style:{
+ position : ABSOLUTE,
+ 'z-index': 35000, // yee haw
+ border : '0px solid ' + color
+ }
+ });
+ return proxy.queueFx({}, animFn);
+ };
+
+
+ arguments.callee.anim = {
+ isAnimated: true,
+ stop: function() {
+ count = 0;
+ proxy.stopFx();
+ }
+ };
+
+ function animFn(){
+ var scale = Ext.isBorderBox ? 2 : 1;
+ active = proxy.anim({
+ top : {from : b.y, to : b.y - 20},
+ left : {from : b.x, to : b.x - 20},
+ borderWidth : {from : 0, to : 10},
+ opacity : {from : 1, to : 0},
+ height : {from : b.height, to : b.height + 20 * scale},
+ width : {from : b.width, to : b.width + 20 * scale}
+ },{
+ duration: o.duration || 1,
+ callback: function() {
+ proxy.remove();
+ --count > 0 ? queue() : fly(dom).afterFx(o);
+ }
+ });
+ arguments.callee.anim = {
+ isAnimated: true,
+ stop: function(){
+ active.stop();
+ }
+ };
+ };
+ queue();
+ });
+ return me;
+ },
+
+ /**
+ * Creates a pause before any subsequent queued effects begin. If there are
+ * no effects queued after the pause it will have no effect.
+ * Usage:
+<pre><code>
+el.pause(1);
+</code></pre>
+ * @param {Number} seconds The length of time to pause (in seconds)
+ * @return {Ext.Element} The Element
+ */
+ pause : function(seconds){
+ var dom = this.dom,
+ t;
+
+ this.queueFx({}, function(){
+ t = setTimeout(function(){
+ fly(dom).afterFx({});
+ }, seconds * 1000);
+ arguments.callee.anim = {
+ isAnimated: true,
+ stop: function(){
+ clearTimeout(t);
+ fly(dom).afterFx({});
+ }
+ };
+ });
+ return this;
+ },
+
+ /**
+ * Fade an element in (from transparent to opaque). The ending opacity can be specified
+ * using the <tt>{@link #endOpacity}</tt> config option.
+ * Usage:
+<pre><code>
+// default: fade in from opacity 0 to 100%
+el.fadeIn();
+
+// custom: fade in from opacity 0 to 75% over 2 seconds
+el.fadeIn({ endOpacity: .75, duration: 2});
+
+// common config options shown with default values
+el.fadeIn({
+ endOpacity: 1, //can be any value between 0 and 1 (e.g. .5)
+ easing: 'easeOut',
+ duration: .5
+});
+</code></pre>
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ fadeIn : function(o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ to = o.endOpacity || 1;
+
+ me.queueFx(o, function(){
+ fly(dom).setOpacity(0);
+ fly(dom).fixDisplay();
+ dom.style.visibility = VISIBLE;
+ arguments.callee.anim = fly(dom).fxanim({opacity:{to:to}},
+ o, NULL, .5, EASEOUT, function(){
+ if(to == 1){
+ fly(dom).clearOpacity();
+ }
+ fly(dom).afterFx(o);
+ });
+ });
+ return me;
+ },
+
+ /**
+ * Fade an element out (from opaque to transparent). The ending opacity can be specified
+ * using the <tt>{@link #endOpacity}</tt> config option. Note that IE may require
+ * <tt>{@link #useDisplay}:true</tt> in order to redisplay correctly.
+ * Usage:
+<pre><code>
+// default: fade out from the element's current opacity to 0
+el.fadeOut();
+
+// custom: fade out from the element's current opacity to 25% over 2 seconds
+el.fadeOut({ endOpacity: .25, duration: 2});
+
+// common config options shown with default values
+el.fadeOut({
+ endOpacity: 0, //can be any value between 0 and 1 (e.g. .5)
+ easing: 'easeOut',
+ duration: .5,
+ remove: false,
+ useDisplay: false
+});
+</code></pre>
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ fadeOut : function(o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ style = dom.style,
+ to = o.endOpacity || 0;
+
+ me.queueFx(o, function(){
+ arguments.callee.anim = fly(dom).fxanim({
+ opacity : {to : to}},
+ o,
+ NULL,
+ .5,
+ EASEOUT,
+ function(){
+ if(to == 0){
+ Ext.Element.data(dom, 'visibilityMode') == Ext.Element.DISPLAY || o.useDisplay ?
+ style.display = "none" :
+ style.visibility = HIDDEN;
+
+ fly(dom).clearOpacity();
+ }
+ fly(dom).afterFx(o);
+ });
+ });
+ return me;
+ },
+
+ /**
+ * Animates the transition of an element's dimensions from a starting height/width
+ * to an ending height/width. This method is a convenience implementation of {@link shift}.
+ * Usage:
+<pre><code>
+// change height and width to 100x100 pixels
+el.scale(100, 100);
+
+// common config options shown with default values. The height and width will default to
+// the element's existing values if passed as null.
+el.scale(
+ [element's width],
+ [element's height], {
+ easing: 'easeOut',
+ duration: .35
+ }
+);
+</code></pre>
+ * @param {Number} width The new width (pass undefined to keep the original width)
+ * @param {Number} height The new height (pass undefined to keep the original height)
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ scale : function(w, h, o){
+ this.shift(Ext.apply({}, o, {
+ width: w,
+ height: h
+ }));
+ return this;
+ },
+
+ /**
+ * Animates the transition of any combination of an element's dimensions, xy position and/or opacity.
+ * Any of these properties not specified in the config object will not be changed. This effect
+ * requires that at least one new dimension, position or opacity setting must be passed in on
+ * the config object in order for the function to have any effect.
+ * Usage:
+<pre><code>
+// slide the element horizontally to x position 200 while changing the height and opacity
+el.shift({ x: 200, height: 50, opacity: .8 });
+
+// common config options shown with default values.
+el.shift({
+ width: [element's width],
+ height: [element's height],
+ x: [element's x position],
+ y: [element's y position],
+ opacity: [element's opacity],
+ easing: 'easeOut',
+ duration: .35
+});
+</code></pre>
+ * @param {Object} options Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ shift : function(o){
+ o = getObject(o);
+ var dom = this.dom,
+ a = {};
+
+ this.queueFx(o, function(){
+ for (var prop in o) {
+ if (o[prop] != UNDEFINED) {
+ a[prop] = {to : o[prop]};
+ }
+ }
+
+ a.width ? a.width.to = fly(dom).adjustWidth(o.width) : a;
+ a.height ? a.height.to = fly(dom).adjustWidth(o.height) : a;
+
+ if (a.x || a.y || a.xy) {
+ a.points = a.xy ||
+ {to : [ a.x ? a.x.to : fly(dom).getX(),
+ a.y ? a.y.to : fly(dom).getY()]};
+ }
+
+ arguments.callee.anim = fly(dom).fxanim(a,
+ o,
+ MOTION,
+ .35,
+ EASEOUT,
+ function(){
+ fly(dom).afterFx(o);
+ });
+ });
+ return this;
+ },
+
+ /**
+ * Slides the element while fading it out of view. An anchor point can be optionally passed to set the
+ * ending point of the effect.
+ * Usage:
+ *<pre><code>
+// default: slide the element downward while fading out
+el.ghost();
+
+// custom: slide the element out to the right with a 2-second duration
+el.ghost('r', { duration: 2 });
+
+// common config options shown with default values
+el.ghost('b', {
+ easing: 'easeOut',
+ duration: .5,
+ remove: false,
+ useDisplay: false
+});
+</code></pre>
+ * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to bottom: 'b')
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ ghost : function(anchor, o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ st = dom.style,
+ a = {opacity: {to: 0}, points: {}},
+ pt = a.points,
+ r,
+ w,
+ h;
+
+ anchor = anchor || "b";
+
+ me.queueFx(o, function(){
+ // restore values after effect
+ r = fly(dom).getFxRestore();
+ w = fly(dom).getWidth();
+ h = fly(dom).getHeight();
+
+ function after(){
+ o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
+ fly(dom).clearOpacity();
+ fly(dom).setPositioning(r.pos);
+ st.width = r.width;
+ st.height = r.height;
+ fly(dom).afterFx(o);
+ }
+
+ pt.by = fly(dom).switchStatements(anchor.toLowerCase(), function(v1,v2){ return [v1, v2];}, {
+ t : [0, -h],
+ l : [-w, 0],
+ r : [w, 0],
+ b : [0, h],
+ tl : [-w, -h],
+ bl : [-w, h],
+ br : [w, h],
+ tr : [w, -h]
+ });
+
+ arguments.callee.anim = fly(dom).fxanim(a,
+ o,
+ MOTION,
+ .5,
+ EASEOUT, after);
+ });
+ return me;
+ },
+
+ /**
+ * Ensures that all effects queued after syncFx is called on the element are
+ * run concurrently. This is the opposite of {@link #sequenceFx}.
+ * @return {Ext.Element} The Element
+ */
+ syncFx : function(){
+ var me = this;
+ me.fxDefaults = Ext.apply(me.fxDefaults || {}, {
+ block : FALSE,
+ concurrent : TRUE,
+ stopFx : FALSE
+ });
+ return me;
+ },
+
+ /**
+ * Ensures that all effects queued after sequenceFx is called on the element are
+ * run in sequence. This is the opposite of {@link #syncFx}.
+ * @return {Ext.Element} The Element
+ */
+ sequenceFx : function(){
+ var me = this;
+ me.fxDefaults = Ext.apply(me.fxDefaults || {}, {
+ block : FALSE,
+ concurrent : FALSE,
+ stopFx : FALSE
+ });
+ return me;
+ },
+
+ /* @private */
+ nextFx : function(){
+ var ef = getQueue(this.dom.id)[0];
+ if(ef){
+ ef.call(this);
+ }
+ },
+
+ /**
+ * Returns true if the element has any effects actively running or queued, else returns false.
+ * @return {Boolean} True if element has active effects, else false
+ */
+ hasActiveFx : function(){
+ return getQueue(this.dom.id)[0];
+ },
+
+ /**
+ * Stops any running effects and clears the element's internal effects queue if it contains
+ * any additional effects that haven't started yet.
+ * @return {Ext.Element} The Element
+ */
+ stopFx : function(finish){
+ var me = this,
+ id = me.dom.id;
+ if(me.hasActiveFx()){
+ var cur = getQueue(id)[0];
+ if(cur && cur.anim){
+ if(cur.anim.isAnimated){
+ setQueue(id, [cur]); //clear
+ cur.anim.stop(finish !== undefined ? finish : TRUE);
+ }else{
+ setQueue(id, []);
+ }
+ }
+ }
+ return me;
+ },
+
+ /* @private */
+ beforeFx : function(o){
+ if(this.hasActiveFx() && !o.concurrent){
+ if(o.stopFx){
+ this.stopFx();
+ return TRUE;
+ }
+ return FALSE;
+ }
+ return TRUE;
+ },
+
+ /**
+ * Returns true if the element is currently blocking so that no other effect can be queued
+ * until this effect is finished, else returns false if blocking is not set. This is commonly
+ * used to ensure that an effect initiated by a user action runs to completion prior to the
+ * same effect being restarted (e.g., firing only one effect even if the user clicks several times).
+ * @return {Boolean} True if blocking, else false
+ */
+ hasFxBlock : function(){
+ var q = getQueue(this.dom.id);
+ return q && q[0] && q[0].block;
+ },
+
+ /* @private */
+ queueFx : function(o, fn){
+ var me = fly(this.dom);
+ if(!me.hasFxBlock()){
+ Ext.applyIf(o, me.fxDefaults);
+ if(!o.concurrent){
+ var run = me.beforeFx(o);
+ fn.block = o.block;
+ getQueue(me.dom.id).push(fn);
+ if(run){
+ me.nextFx();
+ }
+ }else{
+ fn.call(me);
+ }
+ }
+ return me;
+ },
+
+ /* @private */
+ fxWrap : function(pos, o, vis){
+ var dom = this.dom,
+ wrap,
+ wrapXY;
+ if(!o.wrap || !(wrap = Ext.getDom(o.wrap))){
+ if(o.fixPosition){
+ wrapXY = fly(dom).getXY();
+ }
+ var div = document.createElement("div");
+ div.style.visibility = vis;
+ wrap = dom.parentNode.insertBefore(div, dom);
+ fly(wrap).setPositioning(pos);
+ if(fly(wrap).isStyle(POSITION, "static")){
+ fly(wrap).position("relative");
+ }
+ fly(dom).clearPositioning('auto');
+ fly(wrap).clip();
+ wrap.appendChild(dom);
+ if(wrapXY){
+ fly(wrap).setXY(wrapXY);
+ }
+ }
+ return wrap;
+ },
+
+ /* @private */
+ fxUnwrap : function(wrap, pos, o){
+ var dom = this.dom;
+ fly(dom).clearPositioning();
+ fly(dom).setPositioning(pos);
+ if(!o.wrap){
+ var pn = fly(wrap).dom.parentNode;
+ pn.insertBefore(dom, wrap);
+ fly(wrap).remove();
+ }
+ },
+
+ /* @private */
+ getFxRestore : function(){
+ var st = this.dom.style;
+ return {pos: this.getPositioning(), width: st.width, height : st.height};
+ },
+
+ /* @private */
+ afterFx : function(o){
+ var dom = this.dom,
+ id = dom.id;
+ if(o.afterStyle){
+ fly(dom).setStyle(o.afterStyle);
+ }
+ if(o.afterCls){
+ fly(dom).addClass(o.afterCls);
+ }
+ if(o.remove == TRUE){
+ fly(dom).remove();
+ }
+ if(o.callback){
+ o.callback.call(o.scope, fly(dom));
+ }
+ if(!o.concurrent){
+ getQueue(id).shift();
+ fly(dom).nextFx();
+ }
+ },
+
+ /* @private */
+ fxanim : function(args, opt, animType, defaultDur, defaultEase, cb){
+ animType = animType || 'run';
+ opt = opt || {};
+ var anim = Ext.lib.Anim[animType](
+ this.dom,
+ args,
+ (opt.duration || defaultDur) || .35,
+ (opt.easing || defaultEase) || EASEOUT,
+ cb,
+ this
+ );
+ opt.anim = anim;
+ return anim;
+ }
+};
+
+// backwards compat
+Ext.Fx.resize = Ext.Fx.scale;
+
+//When included, Ext.Fx is automatically applied to Element so that all basic
+//effects are available directly via the Element API
+Ext.Element.addMethods(Ext.Fx);
+})();
+/**
+ * @class Ext.CompositeElementLite
+ * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
+ * members, or to perform collective actions upon the whole set.</p>
+ * <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
+ * {@link Ext.Fx}. The methods from these classes will be performed on all the elements in this collection.</p>
+ * Example:<pre><code>
+var els = Ext.select("#some-el div.some-class");
+// or select directly from an existing element
+var el = Ext.get('some-el');
+el.select('div.some-class');
+
+els.setWidth(100); // all elements become 100 width
+els.hide(true); // all elements fade out and hide
+// or
+els.setWidth(100).hide(true);
+</code>
+ */
+Ext.CompositeElementLite = function(els, root){
+ /**
+ * <p>The Array of DOM elements which this CompositeElement encapsulates. Read-only.</p>
+ * <p>This will not <i>usually</i> be accessed in developers' code, but developers wishing
+ * to augment the capabilities of the CompositeElementLite class may use it when adding
+ * methods to the class.</p>
+ * <p>For example to add the <code>nextAll</code> method to the class to <b>add</b> all
+ * following siblings of selected elements, the code would be</p><code><pre>
+Ext.override(Ext.CompositeElementLite, {
+ nextAll: function() {
+ var els = this.elements, i, l = els.length, n, r = [], ri = -1;
+
+// Loop through all elements in this Composite, accumulating
+// an Array of all siblings.
+ for (i = 0; i < l; i++) {
+ for (n = els[i].nextSibling; n; n = n.nextSibling) {
+ r[++ri] = n;
+ }
+ }
+
+// Add all found siblings to this Composite
+ return this.add(r);
+ }
+});</pre></code>
+ * @type Array
+ * @property elements
+ */
+ this.elements = [];
+ this.add(els, root);
+ this.el = new Ext.Element.Flyweight();
+};
+
+Ext.CompositeElementLite.prototype = {
+ isComposite: true,
+
+ // private
+ getElement : function(el){
+ // Set the shared flyweight dom property to the current element
+ var e = this.el;
+ e.dom = el;
+ e.id = el.id;
+ return e;
+ },
+
+ // private
+ transformElement : function(el){
+ return Ext.getDom(el);
+ },
+
+ /**
+ * Returns the number of elements in this Composite.
+ * @return Number
+ */
+ getCount : function(){
+ return this.elements.length;
+ },
+ /**
+ * Adds elements to this Composite object.
+ * @param {Mixed} els Either an Array of DOM elements to add, or another Composite object who's elements should be added.
+ * @return {CompositeElement} This Composite object.
+ */
+ add : function(els, root){
+ var me = this,
+ elements = me.elements;
+ if(!els){
+ return this;
+ }
+ if(Ext.isString(els)){
+ els = Ext.Element.selectorFunction(els, root);
+ }else if(els.isComposite){
+ els = els.elements;
+ }else if(!Ext.isIterable(els)){
+ els = [els];
+ }
+
+ for(var i = 0, len = els.length; i < len; ++i){
+ elements.push(me.transformElement(els[i]));
+ }
+ return me;
+ },
+
+ invoke : function(fn, args){
+ var me = this,
+ els = me.elements,
+ len = els.length,
+ e,
+ i;
+
+ for(i = 0; i < len; i++) {
+ e = els[i];
+ if(e){
+ Ext.Element.prototype[fn].apply(me.getElement(e), args);
+ }
+ }
+ return me;
+ },
+ /**
+ * Returns a flyweight Element of the dom element object at the specified index
+ * @param {Number} index
+ * @return {Ext.Element}
+ */
+ item : function(index){
+ var me = this,
+ el = me.elements[index],
+ out = null;
+
+ if(el){
+ out = me.getElement(el);
+ }
+ return out;
+ },
+
+ // fixes scope with flyweight
+ addListener : function(eventName, handler, scope, opt){
+ var els = this.elements,
+ len = els.length,
+ i, e;
+
+ for(i = 0; i<len; i++) {
+ e = els[i];
+ if(e) {
+ Ext.EventManager.on(e, eventName, handler, scope || e, opt);
+ }
+ }
+ return this;
+ },
+ /**
+ * <p>Calls the passed function for each element in this composite.</p>
+ * @param {Function} fn The function to call. The function is passed the following parameters:<ul>
+ * <li><b>el</b> : Element<div class="sub-desc">The current Element in the iteration.
+ * <b>This is the flyweight (shared) Ext.Element instance, so if you require a
+ * a reference to the dom node, use el.dom.</b></div></li>
+ * <li><b>c</b> : Composite<div class="sub-desc">This Composite object.</div></li>
+ * <li><b>idx</b> : Number<div class="sub-desc">The zero-based index in the iteration.</div></li>
+ * </ul>
+ * @param {Object} scope (optional) The scope (<i>this</i> reference) in which the function is executed. (defaults to the Element)
+ * @return {CompositeElement} this
+ */
+ each : function(fn, scope){
+ var me = this,
+ els = me.elements,
+ len = els.length,
+ i, e;
+
+ for(i = 0; i<len; i++) {
+ e = els[i];
+ if(e){
+ e = this.getElement(e);
+ if(fn.call(scope || e, e, me, i) === false){
+ break;
+ }
+ }
+ }
+ return me;
+ },
+
+ /**
+ * Clears this Composite and adds the elements passed.
+ * @param {Mixed} els Either an array of DOM elements, or another Composite from which to fill this Composite.
+ * @return {CompositeElement} this
+ */
+ fill : function(els){
+ var me = this;
+ me.elements = [];
+ me.add(els);
+ return me;
+ },
+
+ /**
+ * Filters this composite to only elements that match the passed selector.
+ * @param {String/Function} selector A string CSS selector or a comparison function.
+ * The comparison function will be called with the following arguments:<ul>
+ * <li><code>el</code> : Ext.Element<div class="sub-desc">The current DOM element.</div></li>
+ * <li><code>index</code> : Number<div class="sub-desc">The current index within the collection.</div></li>
+ * </ul>
+ * @return {CompositeElement} this
+ */
+ filter : function(selector){
+ var els = [],
+ me = this,
+ elements = me.elements,
+ fn = Ext.isFunction(selector) ? selector
+ : function(el){
+ return el.is(selector);
+ };
+
+
+ me.each(function(el, self, i){
+ if(fn(el, i) !== false){
+ els[els.length] = me.transformElement(el);
+ }
+ });
+ me.elements = els;
+ return me;
+ },
+
+ /**
+ * Find the index of the passed element within the composite collection.
+ * @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
+ * @return Number The index of the passed Ext.Element in the composite collection, or -1 if not found.
+ */
+ indexOf : function(el){
+ return this.elements.indexOf(this.transformElement(el));
+ },
+
+ /**
+ * Replaces the specified element with the passed element.
+ * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
+ * to replace.
+ * @param {Mixed} replacement The id of an element or the Element itself.
+ * @param {Boolean} domReplace (Optional) True to remove and replace the element in the document too.
+ * @return {CompositeElement} this
+ */
+ replaceElement : function(el, replacement, domReplace){
+ var index = !isNaN(el) ? el : this.indexOf(el),
+ d;
+ if(index > -1){
+ replacement = Ext.getDom(replacement);
+ if(domReplace){
+ d = this.elements[index];
+ d.parentNode.insertBefore(replacement, d);
+ Ext.removeNode(d);
+ }
+ this.elements.splice(index, 1, replacement);
+ }
+ return this;
+ },
+
+ /**
+ * Removes all elements.
+ */
+ clear : function(){
+ this.elements = [];
+ }
+};
+
+Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
+
+(function(){
+var fnName,
+ ElProto = Ext.Element.prototype,
+ CelProto = Ext.CompositeElementLite.prototype;
+
+for(fnName in ElProto){
+ if(Ext.isFunction(ElProto[fnName])){
+ (function(fnName){
+ CelProto[fnName] = CelProto[fnName] || function(){
+ return this.invoke(fnName, arguments);
+ };
+ }).call(CelProto, fnName);
+
+ }
+}
+})();
+
+if(Ext.DomQuery){
+ Ext.Element.selectorFunction = Ext.DomQuery.select;
+}
+
+/**
+ * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
+ * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
+ * {@link Ext.CompositeElementLite CompositeElementLite} object.
+ * @param {String/Array} selector The CSS selector or an array of elements
+ * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
+ * @return {CompositeElementLite/CompositeElement}
+ * @member Ext.Element
+ * @method select
+ */
+Ext.Element.select = function(selector, root){
+ var els;
+ if(typeof selector == "string"){
+ els = Ext.Element.selectorFunction(selector, root);
+ }else if(selector.length !== undefined){
+ els = selector;
+ }else{
+ throw "Invalid selector";
+ }
+ return new Ext.CompositeElementLite(els);
+};
+/**
+ * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
+ * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
+ * {@link Ext.CompositeElementLite CompositeElementLite} object.
+ * @param {String/Array} selector The CSS selector or an array of elements
+ * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
+ * @return {CompositeElementLite/CompositeElement}
+ * @member Ext
+ * @method select
+ */
+Ext.select = Ext.Element.select;/**
+ * @class Ext.CompositeElementLite
+ */
+Ext.apply(Ext.CompositeElementLite.prototype, {
+ addElements : function(els, root){
+ if(!els){
+ return this;
+ }
+ if(typeof els == "string"){
+ els = Ext.Element.selectorFunction(els, root);
+ }
+ var yels = this.elements;
+ Ext.each(els, function(e) {
+ yels.push(Ext.get(e));
+ });
+ return this;
+ },
+
+ /**
+ * Returns the first Element
+ * @return {Ext.Element}
+ */
+ first : function(){
+ return this.item(0);
+ },
+
+ /**
+ * Returns the last Element
+ * @return {Ext.Element}
+ */
+ last : function(){
+ return this.item(this.getCount()-1);
+ },
+
+ /**
+ * Returns true if this composite contains the passed element
+ * @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
+ * @return Boolean
+ */
+ contains : function(el){
+ return this.indexOf(el) != -1;
+ },
+
+ /**
+ * Removes the specified element(s).
+ * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
+ * or an array of any of those.
+ * @param {Boolean} removeDom (optional) True to also remove the element from the document
+ * @return {CompositeElement} this
+ */
+ removeElement : function(keys, removeDom){
+ var me = this,
+ els = this.elements,
+ el;
+ Ext.each(keys, function(val){
+ if ((el = (els[val] || els[val = me.indexOf(val)]))) {
+ if(removeDom){
+ if(el.dom){
+ el.remove();
+ }else{
+ Ext.removeNode(el);
+ }
+ }
+ els.splice(val, 1);
+ }
+ });
+ return this;
+ }
+});
+/**
+ * @class Ext.CompositeElement
+ * @extends Ext.CompositeElementLite
+ * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
+ * members, or to perform collective actions upon the whole set.</p>
+ * <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
+ * {@link Ext.Fx}. The methods from these classes will be performed on all the elements in this collection.</p>
+ * <p>All methods return <i>this</i> and can be chained.</p>
+ * Usage:
+<pre><code>
+var els = Ext.select("#some-el div.some-class", true);
+// or select directly from an existing element
+var el = Ext.get('some-el');
+el.select('div.some-class', true);
+
+els.setWidth(100); // all elements become 100 width
+els.hide(true); // all elements fade out and hide
+// or
+els.setWidth(100).hide(true);
+</code></pre>
+ */
+Ext.CompositeElement = function(els, root){
+ this.elements = [];
+ this.add(els, root);
+};
+
+Ext.extend(Ext.CompositeElement, Ext.CompositeElementLite, {
+
+ // private
+ getElement : function(el){
+ // In this case just return it, since we already have a reference to it
+ return el;
+ },
+
+ // private
+ transformElement : function(el){
+ return Ext.get(el);
+ }
+
+ /**
+ * Adds elements to this composite.
+ * @param {String/Array} els A string CSS selector, an array of elements or an element
+ * @return {CompositeElement} this
+ */
+
+ /**
+ * Returns the Element object at the specified index
+ * @param {Number} index
+ * @return {Ext.Element}
+ */
+
+ /**
+ * Iterates each <code>element</code> in this <code>composite</code>
+ * calling the supplied function using {@link Ext#each}.
+ * @param {Function} fn The function to be called with each
+ * <code>element</code>. If the supplied function returns <tt>false</tt>,
+ * iteration stops. This function is called with the following arguments:
+ * <div class="mdetail-params"><ul>
+ * <li><code>element</code> : <i>Ext.Element</i><div class="sub-desc">The element at the current <code>index</code>
+ * in the <code>composite</code></div></li>
+ * <li><code>composite</code> : <i>Object</i> <div class="sub-desc">This composite.</div></li>
+ * <li><code>index</code> : <i>Number</i> <div class="sub-desc">The current index within the <code>composite</code> </div></li>
+ * </ul></div>
+ * @param {Object} scope (optional) The scope (<code><this</code> reference) in which the specified function is executed.
+ * Defaults to the <code>element</code> at the current <code>index</code>
+ * within the composite.
+ * @return {CompositeElement} this
+ */
+});
+
+/**
+ * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
+ * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
+ * {@link Ext.CompositeElementLite CompositeElementLite} object.
+ * @param {String/Array} selector The CSS selector or an array of elements
+ * @param {Boolean} unique (optional) true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
+ * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
+ * @return {CompositeElementLite/CompositeElement}
+ * @member Ext.Element
+ * @method select
+ */
+Ext.Element.select = function(selector, unique, root){
+ var els;
+ if(typeof selector == "string"){
+ els = Ext.Element.selectorFunction(selector, root);
+ }else if(selector.length !== undefined){
+ els = selector;
+ }else{
+ throw "Invalid selector";
+ }
+
+ return (unique === true) ? new Ext.CompositeElement(els) : new Ext.CompositeElementLite(els);
+};
+
+/**
+ * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
+ * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
+ * {@link Ext.CompositeElementLite CompositeElementLite} object.
+ * @param {String/Array} selector The CSS selector or an array of elements
+ * @param {Boolean} unique (optional) true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
+ * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
+ * @return {CompositeElementLite/CompositeElement}
+ * @member Ext.Element
+ * @method select
+ */
+Ext.select = Ext.Element.select;(function(){
+ var BEFOREREQUEST = "beforerequest",
+ REQUESTCOMPLETE = "requestcomplete",
+ REQUESTEXCEPTION = "requestexception",
+ UNDEFINED = undefined,
+ LOAD = 'load',
+ POST = 'POST',
+ GET = 'GET',
+ WINDOW = window;
+
+ /**
+ * @class Ext.data.Connection
+ * @extends Ext.util.Observable
+ * <p>The class encapsulates a connection to the page's originating domain, allowing requests to be made
+ * either to a configured URL, or to a URL specified at request time.</p>
+ * <p>Requests made by this class are asynchronous, and will return immediately. No data from
+ * the server will be available to the statement immediately following the {@link #request} call.
+ * To process returned data, use a
+ * <a href="#request-option-success" ext:member="request-option-success" ext:cls="Ext.data.Connection">success callback</a>
+ * in the request options object,
+ * or an {@link #requestcomplete event listener}.</p>
+ * <p><h3>File Uploads</h3><a href="#request-option-isUpload" ext:member="request-option-isUpload" ext:cls="Ext.data.Connection">File uploads</a> are not performed using normal "Ajax" techniques, that
+ * is they are <b>not</b> performed using XMLHttpRequests. Instead the form is submitted in the standard
+ * manner with the DOM <tt><form></tt> element temporarily modified to have its
+ * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
+ * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
+ * but removed after the return data has been gathered.</p>
+ * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
+ * server is using JSON to send the return object, then the
+ * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
+ * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
+ * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
+ * "<" as "&lt;", "&" as "&amp;" etc.</p>
+ * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
+ * is created containing a <tt>responseText</tt> property in order to conform to the
+ * requirements of event handlers and callbacks.</p>
+ * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
+ * and some server technologies (notably JEE) may require some custom processing in order to
+ * retrieve parameter names and parameter values from the packet content.</p>
+ * @constructor
+ * @param {Object} config a configuration object.
+ */
+ Ext.data.Connection = function(config){
+ Ext.apply(this, config);
+ this.addEvents(
+ /**
+ * @event beforerequest
+ * Fires before a network request is made to retrieve a data object.
+ * @param {Connection} conn This Connection object.
+ * @param {Object} options The options config object passed to the {@link #request} method.
+ */
+ BEFOREREQUEST,
+ /**
+ * @event requestcomplete
+ * Fires if the request was successfully completed.
+ * @param {Connection} conn This Connection object.
+ * @param {Object} response The XHR object containing the response data.
+ * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
+ * for details.
+ * @param {Object} options The options config object passed to the {@link #request} method.
+ */
+ REQUESTCOMPLETE,
+ /**
+ * @event requestexception
+ * Fires if an error HTTP status was returned from the server.
+ * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">HTTP Status Code Definitions</a>
+ * for details of HTTP status codes.
+ * @param {Connection} conn This Connection object.
+ * @param {Object} response The XHR object containing the response data.
+ * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
+ * for details.
+ * @param {Object} options The options config object passed to the {@link #request} method.
+ */
+ REQUESTEXCEPTION
+ );
+ Ext.data.Connection.superclass.constructor.call(this);
+ };
+
+ Ext.extend(Ext.data.Connection, Ext.util.Observable, {
+ /**
+ * @cfg {String} url (Optional) <p>The default URL to be used for requests to the server. Defaults to undefined.</p>
+ * <p>The <code>url</code> config may be a function which <i>returns</i> the URL to use for the Ajax request. The scope
+ * (<code><b>this</b></code> reference) of the function is the <code>scope</code> option passed to the {@link #request} method.</p>
+ */
+ /**
+ * @cfg {Object} extraParams (Optional) An object containing properties which are used as
+ * extra parameters to each request made by this object. (defaults to undefined)
+ */
+ /**
+ * @cfg {Object} defaultHeaders (Optional) An object containing request headers which are added
+ * to each request made by this object. (defaults to undefined)
+ */
+ /**
+ * @cfg {String} method (Optional) The default HTTP method to be used for requests.
+ * (defaults to undefined; if not set, but {@link #request} params are present, POST will be used;
+ * otherwise, GET will be used.)
+ */
+ /**
+ * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
+ */
+ timeout : 30000,
+ /**
+ * @cfg {Boolean} autoAbort (Optional) Whether this request should abort any pending requests. (defaults to false)
+ * @type Boolean
+ */
+ autoAbort:false,
+
+ /**
+ * @cfg {Boolean} disableCaching (Optional) True to add a unique cache-buster param to GET requests. (defaults to true)
+ * @type Boolean
+ */
+ disableCaching: true,
+
+ /**
+ * @cfg {String} disableCachingParam (Optional) Change the parameter which is sent went disabling caching
+ * through a cache buster. Defaults to '_dc'
+ * @type String
+ */
+ disableCachingParam: '_dc',
+
+ /**
+ * <p>Sends an HTTP request to a remote server.</p>
+ * <p><b>Important:</b> Ajax server requests are asynchronous, and this call will
+ * return before the response has been received. Process any returned data
+ * in a callback function.</p>
+ * <pre><code>
+Ext.Ajax.request({
+ url: 'ajax_demo/sample.json',
+ success: function(response, opts) {
+ var obj = Ext.decode(response.responseText);
+ console.dir(obj);
+ },
+ failure: function(response, opts) {
+ console.log('server-side failure with status code ' + response.status);
+ }
+});
+ * </code></pre>
+ * <p>To execute a callback function in the correct scope, use the <tt>scope</tt> option.</p>
+ * @param {Object} options An object which may contain the following properties:<ul>
+ * <li><b>url</b> : String/Function (Optional)<div class="sub-desc">The URL to
+ * which to send the request, or a function to call which returns a URL string. The scope of the
+ * function is specified by the <tt>scope</tt> option. Defaults to the configured
+ * <tt>{@link #url}</tt>.</div></li>
+ * <li><b>params</b> : Object/String/Function (Optional)<div class="sub-desc">
+ * An object containing properties which are used as parameters to the
+ * request, a url encoded string or a function to call to get either. The scope of the function
+ * is specified by the <tt>scope</tt> option.</div></li>
+ * <li><b>method</b> : String (Optional)<div class="sub-desc">The HTTP method to use
+ * for the request. Defaults to the configured method, or if no method was configured,
+ * "GET" if no parameters are being sent, and "POST" if parameters are being sent. Note that
+ * the method name is case-sensitive and should be all caps.</div></li>
+ * <li><b>callback</b> : Function (Optional)<div class="sub-desc">The
+ * function to be called upon receipt of the HTTP response. The callback is
+ * called regardless of success or failure and is passed the following
+ * parameters:<ul>
+ * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
+ * <li><b>success</b> : Boolean<div class="sub-desc">True if the request succeeded.</div></li>
+ * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.
+ * See <a href="http://www.w3.org/TR/XMLHttpRequest/">http://www.w3.org/TR/XMLHttpRequest/</a> for details about
+ * accessing elements of the response.</div></li>
+ * </ul></div></li>
+ * <li><a id="request-option-success"></a><b>success</b> : Function (Optional)<div class="sub-desc">The function
+ * to be called upon success of the request. The callback is passed the following
+ * parameters:<ul>
+ * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
+ * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
+ * </ul></div></li>
+ * <li><b>failure</b> : Function (Optional)<div class="sub-desc">The function
+ * to be called upon failure of the request. The callback is passed the
+ * following parameters:<ul>
+ * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
+ * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
+ * </ul></div></li>
+ * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
+ * which to execute the callbacks: The "this" object for the callback function. If the <tt>url</tt>, or <tt>params</tt> options were
+ * specified as functions from which to draw values, then this also serves as the scope for those function calls.
+ * Defaults to the browser window.</div></li>
+ * <li><b>timeout</b> : Number (Optional)<div class="sub-desc">The timeout in milliseconds to be used for this request. Defaults to 30 seconds.</div></li>
+ * <li><b>form</b> : Element/HTMLElement/String (Optional)<div class="sub-desc">The <tt><form></tt>
+ * Element or the id of the <tt><form></tt> to pull parameters from.</div></li>
+ * <li><a id="request-option-isUpload"></a><b>isUpload</b> : Boolean (Optional)<div class="sub-desc"><b>Only meaningful when used
+ * with the <tt>form</tt> option</b>.
+ * <p>True if the form object is a file upload (will be set automatically if the form was
+ * configured with <b><tt>enctype</tt></b> "multipart/form-data").</p>
+ * <p>File uploads are not performed using normal "Ajax" techniques, that is they are <b>not</b>
+ * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
+ * DOM <tt><form></tt> element temporarily modified to have its
+ * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
+ * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
+ * but removed after the return data has been gathered.</p>
+ * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
+ * server is using JSON to send the return object, then the
+ * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
+ * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
+ * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
+ * is created containing a <tt>responseText</tt> property in order to conform to the
+ * requirements of event handlers and callbacks.</p>
+ * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
+ * and some server technologies (notably JEE) may require some custom processing in order to
+ * retrieve parameter names and parameter values from the packet content.</p>
+ * </div></li>
+ * <li><b>headers</b> : Object (Optional)<div class="sub-desc">Request
+ * headers to set for the request.</div></li>
+ * <li><b>xmlData</b> : Object (Optional)<div class="sub-desc">XML document
+ * to use for the post. Note: This will be used instead of params for the post
+ * data. Any params will be appended to the URL.</div></li>
+ * <li><b>jsonData</b> : Object/String (Optional)<div class="sub-desc">JSON
+ * data to use as the post. Note: This will be used instead of params for the post
+ * data. Any params will be appended to the URL.</div></li>
+ * <li><b>disableCaching</b> : Boolean (Optional)<div class="sub-desc">True
+ * to add a unique cache-buster param to GET requests.</div></li>
+ * </ul></p>
+ * <p>The options object may also contain any other property which might be needed to perform
+ * postprocessing in a callback because it is passed to callback functions.</p>
+ * @return {Number} transactionId The id of the server transaction. This may be used
+ * to cancel the request.
+ */
+ request : function(o){
+ var me = this;
+ if(me.fireEvent(BEFOREREQUEST, me, o)){
+ if (o.el) {
+ if(!Ext.isEmpty(o.indicatorText)){
+ me.indicatorText = '<div class="loading-indicator">'+o.indicatorText+"</div>";
+ }
+ if(me.indicatorText) {
+ Ext.getDom(o.el).innerHTML = me.indicatorText;
+ }
+ o.success = (Ext.isFunction(o.success) ? o.success : function(){}).createInterceptor(function(response) {
+ Ext.getDom(o.el).innerHTML = response.responseText;
+ });
+ }
+
+ var p = o.params,
+ url = o.url || me.url,
+ method,
+ cb = {success: me.handleResponse,
+ failure: me.handleFailure,
+ scope: me,
+ argument: {options: o},
+ timeout : o.timeout || me.timeout
+ },
+ form,
+ serForm;
+
+
+ if (Ext.isFunction(p)) {
+ p = p.call(o.scope||WINDOW, o);
+ }
+
+ p = Ext.urlEncode(me.extraParams, Ext.isObject(p) ? Ext.urlEncode(p) : p);
+
+ if (Ext.isFunction(url)) {
+ url = url.call(o.scope || WINDOW, o);
+ }
+
+ if((form = Ext.getDom(o.form))){
+ url = url || form.action;
+ if(o.isUpload || /multipart\/form-data/i.test(form.getAttribute("enctype"))) {
+ return me.doFormUpload.call(me, o, p, url);
+ }
+ serForm = Ext.lib.Ajax.serializeForm(form);
+ p = p ? (p + '&' + serForm) : serForm;
+ }
+
+ method = o.method || me.method || ((p || o.xmlData || o.jsonData) ? POST : GET);
+
+ if(method === GET && (me.disableCaching && o.disableCaching !== false) || o.disableCaching === true){
+ var dcp = o.disableCachingParam || me.disableCachingParam;
+ url = Ext.urlAppend(url, dcp + '=' + (new Date().getTime()));
+ }
+
+ o.headers = Ext.apply(o.headers || {}, me.defaultHeaders || {});
+
+ if(o.autoAbort === true || me.autoAbort) {
+ me.abort();
+ }
+
+ if((method == GET || o.xmlData || o.jsonData) && p){
+ url = Ext.urlAppend(url, p);
+ p = '';
+ }
+ return (me.transId = Ext.lib.Ajax.request(method, url, cb, p, o));
+ }else{
+ return o.callback ? o.callback.apply(o.scope, [o,UNDEFINED,UNDEFINED]) : null;
+ }
+ },
+
+ /**
+ * Determine whether this object has a request outstanding.
+ * @param {Number} transactionId (Optional) defaults to the last transaction
+ * @return {Boolean} True if there is an outstanding request.
+ */
+ isLoading : function(transId){
+ return transId ? Ext.lib.Ajax.isCallInProgress(transId) : !! this.transId;
+ },
+
+ /**
+ * Aborts any outstanding request.
+ * @param {Number} transactionId (Optional) defaults to the last transaction
+ */
+ abort : function(transId){
+ if(transId || this.isLoading()){
+ Ext.lib.Ajax.abort(transId || this.transId);
+ }
+ },
+
+ // private
+ handleResponse : function(response){
+ this.transId = false;
+ var options = response.argument.options;
+ response.argument = options ? options.argument : null;
+ this.fireEvent(REQUESTCOMPLETE, this, response, options);
+ if(options.success){
+ options.success.call(options.scope, response, options);
+ }
+ if(options.callback){
+ options.callback.call(options.scope, options, true, response);
+ }
+ },
+
+ // private
+ handleFailure : function(response, e){
+ this.transId = false;
+ var options = response.argument.options;
+ response.argument = options ? options.argument : null;
+ this.fireEvent(REQUESTEXCEPTION, this, response, options, e);
+ if(options.failure){
+ options.failure.call(options.scope, response, options);
+ }
+ if(options.callback){
+ options.callback.call(options.scope, options, false, response);
+ }
+ },
+
+ // private
+ doFormUpload : function(o, ps, url){
+ var id = Ext.id(),
+ doc = document,
+ frame = doc.createElement('iframe'),
+ form = Ext.getDom(o.form),
+ hiddens = [],
+ hd,
+ encoding = 'multipart/form-data',
+ buf = {
+ target: form.target,
+ method: form.method,
+ encoding: form.encoding,
+ enctype: form.enctype,
+ action: form.action
+ };
+
+ Ext.fly(frame).set({
+ id: id,
+ name: id,
+ cls: 'x-hidden'
+
+ });
+
+ doc.body.appendChild(frame);
+
+ //Reset the Frame to neutral domain
+ Ext.fly(frame).set({
+ src : Ext.SSL_SECURE_URL
+ });
+
+ // This is required so that IE doesn't pop the response up in a new window.
+ if(Ext.isIE){
+ document.frames[id].name = id;
+ }
+
+
+ Ext.fly(form).set({
+ target: id,
+ method: POST,
+ enctype: encoding,
+ encoding: encoding,
+ action: url || buf.action
+ });
+
+ // add dynamic params
+ Ext.iterate(Ext.urlDecode(ps, false), function(k, v){
+ hd = doc.createElement('input');
+ Ext.fly(hd).set({
+ type: 'hidden',
+ value: v,
+ name: k
+ });
+ form.appendChild(hd);
+ hiddens.push(hd);
+ });
+
+ function cb(){
+ var me = this,
+ // bogus response object
+ r = {responseText : '',
+ responseXML : null,
+ argument : o.argument},
+ doc,
+ firstChild;
+
+ try{
+ doc = frame.contentWindow.document || frame.contentDocument || WINDOW.frames[id].document;
+ if(doc){
+ if(doc.body){
+ if(/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)){ // json response wrapped in textarea
+ r.responseText = firstChild.value;
+ }else{
+ r.responseText = doc.body.innerHTML;
+ }
+ }
+ //in IE the document may still have a body even if returns XML.
+ r.responseXML = doc.XMLDocument || doc;
+ }
+ }
+ catch(e) {}
+
+ Ext.EventManager.removeListener(frame, LOAD, cb, me);
+
+ me.fireEvent(REQUESTCOMPLETE, me, r, o);
+
+ function runCallback(fn, scope, args){
+ if(Ext.isFunction(fn)){
+ fn.apply(scope, args);
+ }
+ }
+
+ runCallback(o.success, o.scope, [r, o]);
+ runCallback(o.callback, o.scope, [o, true, r]);
+
+ if(!me.debugUploads){
+ setTimeout(function(){Ext.removeNode(frame);}, 100);
+ }
+ }
+
+ Ext.EventManager.on(frame, LOAD, cb, this);
+ form.submit();
+
+ Ext.fly(form).set(buf);
+ Ext.each(hiddens, function(h) {
+ Ext.removeNode(h);
+ });
+ }
+ });
+})();
+
+/**
+ * @class Ext.Ajax
+ * @extends Ext.data.Connection
+ * <p>The global Ajax request class that provides a simple way to make Ajax requests
+ * with maximum flexibility.</p>
+ * <p>Since Ext.Ajax is a singleton, you can set common properties/events for it once
+ * and override them at the request function level only if necessary.</p>
+ * <p>Common <b>Properties</b> you may want to set are:<div class="mdetail-params"><ul>
+ * <li><b><tt>{@link #method}</tt></b><p class="sub-desc"></p></li>
+ * <li><b><tt>{@link #extraParams}</tt></b><p class="sub-desc"></p></li>
+ * <li><b><tt>{@link #url}</tt></b><p class="sub-desc"></p></li>
+ * </ul></div>
+ * <pre><code>
+// Default headers to pass in every request
+Ext.Ajax.defaultHeaders = {
+ 'Powered-By': 'Ext'
+};
+ * </code></pre>
+ * </p>
+ * <p>Common <b>Events</b> you may want to set are:<div class="mdetail-params"><ul>
+ * <li><b><tt>{@link Ext.data.Connection#beforerequest beforerequest}</tt></b><p class="sub-desc"></p></li>
+ * <li><b><tt>{@link Ext.data.Connection#requestcomplete requestcomplete}</tt></b><p class="sub-desc"></p></li>
+ * <li><b><tt>{@link Ext.data.Connection#requestexception requestexception}</tt></b><p class="sub-desc"></p></li>
+ * </ul></div>
+ * <pre><code>
+// Example: show a spinner during all Ajax requests
+Ext.Ajax.on('beforerequest', this.showSpinner, this);
+Ext.Ajax.on('requestcomplete', this.hideSpinner, this);
+Ext.Ajax.on('requestexception', this.hideSpinner, this);
+ * </code></pre>
+ * </p>
+ * <p>An example request:</p>
+ * <pre><code>
+// Basic request
+Ext.Ajax.{@link Ext.data.Connection#request request}({
+ url: 'foo.php',
+ success: someFn,
+ failure: otherFn,
+ headers: {
+ 'my-header': 'foo'
+ },
+ params: { foo: 'bar' }
+});
+
+// Simple ajax form submission
+Ext.Ajax.{@link Ext.data.Connection#request request}({
+ form: 'some-form',
+ params: 'foo=bar'
+});
+ * </code></pre>
+ * </p>
+ * @singleton
+ */
+Ext.Ajax = new Ext.data.Connection({
+ /**
+ * @cfg {String} url @hide
+ */
+ /**
+ * @cfg {Object} extraParams @hide
+ */
+ /**
+ * @cfg {Object} defaultHeaders @hide
+ */
+ /**
+ * @cfg {String} method (Optional) @hide
+ */
+ /**
+ * @cfg {Number} timeout (Optional) @hide
+ */
+ /**
+ * @cfg {Boolean} autoAbort (Optional) @hide
+ */
+
+ /**
+ * @cfg {Boolean} disableCaching (Optional) @hide
+ */
+
+ /**
+ * @property disableCaching
+ * True to add a unique cache-buster param to GET requests. (defaults to true)
+ * @type Boolean
+ */
+ /**
+ * @property url
+ * The default URL to be used for requests to the server. (defaults to undefined)
+ * If the server receives all requests through one URL, setting this once is easier than
+ * entering it on every request.
+ * @type String
+ */
+ /**
+ * @property extraParams
+ * An object containing properties which are used as extra parameters to each request made
+ * by this object (defaults to undefined). Session information and other data that you need
+ * to pass with each request are commonly put here.
+ * @type Object
+ */
+ /**
+ * @property defaultHeaders
+ * An object containing request headers which are added to each request made by this object
+ * (defaults to undefined).
+ * @type Object
+ */
+ /**
+ * @property method
+ * The default HTTP method to be used for requests. Note that this is case-sensitive and
+ * should be all caps (defaults to undefined; if not set but params are present will use
+ * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
+ * @type String
+ */
+ /**
+ * @property timeout
+ * The timeout in milliseconds to be used for requests. (defaults to 30000)
+ * @type Number
+ */
+
+ /**
+ * @property autoAbort
+ * Whether a new request should abort any pending requests. (defaults to false)
+ * @type Boolean
+ */
+ autoAbort : false,
+
+ /**
+ * Serialize the passed form into a url encoded string
+ * @param {String/HTMLElement} form
+ * @return {String}
+ */
+ serializeForm : function(form){
+ return Ext.lib.Ajax.serializeForm(form);
+ }
+});
+/**
+ * @class Ext.Updater
+ * @extends Ext.util.Observable
+ * Provides AJAX-style update capabilities for Element objects. Updater can be used to {@link #update}
+ * an {@link Ext.Element} once, or you can use {@link #startAutoRefresh} to set up an auto-updating
+ * {@link Ext.Element Element} on a specific interval.<br><br>
+ * Usage:<br>
+ * <pre><code>
+ * var el = Ext.get("foo"); // Get Ext.Element object
+ * var mgr = el.getUpdater();
+ * mgr.update({
+ url: "http://myserver.com/index.php",
+ params: {
+ param1: "foo",
+ param2: "bar"
+ }
+ * });
+ * ...
+ * mgr.formUpdate("myFormId", "http://myserver.com/index.php");
+ * <br>
+ * // or directly (returns the same Updater instance)
+ * var mgr = new Ext.Updater("myElementId");
+ * mgr.startAutoRefresh(60, "http://myserver.com/index.php");
+ * mgr.on("update", myFcnNeedsToKnow);
+ * <br>
+ * // short handed call directly from the element object
+ * Ext.get("foo").load({
+ url: "bar.php",
+ scripts: true,
+ params: "param1=foo&param2=bar",
+ text: "Loading Foo..."
+ * });
+ * </code></pre>
+ * @constructor
+ * Create new Updater directly.
+ * @param {Mixed} el The element to update
+ * @param {Boolean} forceNew (optional) By default the constructor checks to see if the passed element already
+ * has an Updater and if it does it returns the same instance. This will skip that check (useful for extending this class).
+ */
+Ext.UpdateManager = Ext.Updater = Ext.extend(Ext.util.Observable,
+function() {
+ var BEFOREUPDATE = "beforeupdate",
+ UPDATE = "update",
+ FAILURE = "failure";
+
+ // private
+ function processSuccess(response){
+ var me = this;
+ me.transaction = null;
+ if (response.argument.form && response.argument.reset) {
+ try { // put in try/catch since some older FF releases had problems with this
+ response.argument.form.reset();
+ } catch(e){}
+ }
+ if (me.loadScripts) {
+ me.renderer.render(me.el, response, me,
+ updateComplete.createDelegate(me, [response]));
+ } else {
+ me.renderer.render(me.el, response, me);
+ updateComplete.call(me, response);
+ }
+ }
+
+ // private
+ function updateComplete(response, type, success){
+ this.fireEvent(type || UPDATE, this.el, response);
+ if(Ext.isFunction(response.argument.callback)){
+ response.argument.callback.call(response.argument.scope, this.el, Ext.isEmpty(success) ? true : false, response, response.argument.options);
+ }
+ }
+
+ // private
+ function processFailure(response){
+ updateComplete.call(this, response, FAILURE, !!(this.transaction = null));
+ }
+
+ return {
+ constructor: function(el, forceNew){
+ var me = this;
+ el = Ext.get(el);
+ if(!forceNew && el.updateManager){
+ return el.updateManager;
+ }
+ /**
+ * The Element object
+ * @type Ext.Element
+ */
+ me.el = el;
+ /**
+ * Cached url to use for refreshes. Overwritten every time update() is called unless "discardUrl" param is set to true.
+ * @type String
+ */
+ me.defaultUrl = null;
+
+ me.addEvents(
+ /**
+ * @event beforeupdate
+ * Fired before an update is made, return false from your handler and the update is cancelled.
+ * @param {Ext.Element} el
+ * @param {String/Object/Function} url
+ * @param {String/Object} params
+ */
+ BEFOREUPDATE,
+ /**
+ * @event update
+ * Fired after successful update is made.
+ * @param {Ext.Element} el
+ * @param {Object} oResponseObject The response Object
+ */
+ UPDATE,
+ /**
+ * @event failure
+ * Fired on update failure.
+ * @param {Ext.Element} el
+ * @param {Object} oResponseObject The response Object
+ */
+ FAILURE
+ );
+
+ Ext.apply(me, Ext.Updater.defaults);
+ /**
+ * Blank page URL to use with SSL file uploads (defaults to {@link Ext.Updater.defaults#sslBlankUrl}).
+ * @property sslBlankUrl
+ * @type String
+ */
+ /**
+ * Whether to append unique parameter on get request to disable caching (defaults to {@link Ext.Updater.defaults#disableCaching}).
+ * @property disableCaching
+ * @type Boolean
+ */
+ /**
+ * Text for loading indicator (defaults to {@link Ext.Updater.defaults#indicatorText}).
+ * @property indicatorText
+ * @type String
+ */
+ /**
+ * Whether to show indicatorText when loading (defaults to {@link Ext.Updater.defaults#showLoadIndicator}).
+ * @property showLoadIndicator
+ * @type String
+ */
+ /**
+ * Timeout for requests or form posts in seconds (defaults to {@link Ext.Updater.defaults#timeout}).
+ * @property timeout
+ * @type Number
+ */
+ /**
+ * True to process scripts in the output (defaults to {@link Ext.Updater.defaults#loadScripts}).
+ * @property loadScripts
+ * @type Boolean
+ */
+
+ /**
+ * Transaction object of the current executing transaction, or null if there is no active transaction.
+ */
+ me.transaction = null;
+ /**
+ * Delegate for refresh() prebound to "this", use myUpdater.refreshDelegate.createCallback(arg1, arg2) to bind arguments
+ * @type Function
+ */
+ me.refreshDelegate = me.refresh.createDelegate(me);
+ /**
+ * Delegate for update() prebound to "this", use myUpdater.updateDelegate.createCallback(arg1, arg2) to bind arguments
+ * @type Function
+ */
+ me.updateDelegate = me.update.createDelegate(me);
+ /**
+ * Delegate for formUpdate() prebound to "this", use myUpdater.formUpdateDelegate.createCallback(arg1, arg2) to bind arguments
+ * @type Function
+ */
+ me.formUpdateDelegate = (me.formUpdate || function(){}).createDelegate(me);
+
+ /**
+ * The renderer for this Updater (defaults to {@link Ext.Updater.BasicRenderer}).
+ */
+ me.renderer = me.renderer || me.getDefaultRenderer();
+
+ Ext.Updater.superclass.constructor.call(me);
+ },
+
+ /**
+ * Sets the content renderer for this Updater. See {@link Ext.Updater.BasicRenderer#render} for more details.
+ * @param {Object} renderer The object implementing the render() method
+ */
+ setRenderer : function(renderer){
+ this.renderer = renderer;
+ },
+
+ /**
+ * Returns the current content renderer for this Updater. See {@link Ext.Updater.BasicRenderer#render} for more details.
+ * @return {Object}
+ */
+ getRenderer : function(){
+ return this.renderer;
+ },
+
+ /**
+ * This is an overrideable method which returns a reference to a default
+ * renderer class if none is specified when creating the Ext.Updater.
+ * Defaults to {@link Ext.Updater.BasicRenderer}
+ */
+ getDefaultRenderer: function() {
+ return new Ext.Updater.BasicRenderer();
+ },
+
+ /**
+ * Sets the default URL used for updates.
+ * @param {String/Function} defaultUrl The url or a function to call to get the url
+ */
+ setDefaultUrl : function(defaultUrl){
+ this.defaultUrl = defaultUrl;
+ },
+
+ /**
+ * Get the Element this Updater is bound to
+ * @return {Ext.Element} The element
+ */
+ getEl : function(){
+ return this.el;
+ },
+
+ /**
+ * Performs an <b>asynchronous</b> request, updating this element with the response.
+ * If params are specified it uses POST, otherwise it uses GET.<br><br>
+ * <b>Note:</b> Due to the asynchronous nature of remote server requests, the Element
+ * will not have been fully updated when the function returns. To post-process the returned
+ * data, use the callback option, or an <b><code>update</code></b> event handler.
+ * @param {Object} options A config object containing any of the following options:<ul>
+ * <li>url : <b>String/Function</b><p class="sub-desc">The URL to request or a function which
+ * <i>returns</i> the URL (defaults to the value of {@link Ext.Ajax#url} if not specified).</p></li>
+ * <li>method : <b>String</b><p class="sub-desc">The HTTP method to
+ * use. Defaults to POST if the <code>params</code> argument is present, otherwise GET.</p></li>
+ * <li>params : <b>String/Object/Function</b><p class="sub-desc">The
+ * parameters to pass to the server (defaults to none). These may be specified as a url-encoded
+ * string, or as an object containing properties which represent parameters,
+ * or as a function, which returns such an object.</p></li>
+ * <li>scripts : <b>Boolean</b><p class="sub-desc">If <code>true</code>
+ * any <script> tags embedded in the response text will be extracted
+ * and executed (defaults to {@link Ext.Updater.defaults#loadScripts}). If this option is specified,
+ * the callback will be called <i>after</i> the execution of the scripts.</p></li>
+ * <li>callback : <b>Function</b><p class="sub-desc">A function to
+ * be called when the response from the server arrives. The following
+ * parameters are passed:<ul>
+ * <li><b>el</b> : Ext.Element<p class="sub-desc">The Element being updated.</p></li>
+ * <li><b>success</b> : Boolean<p class="sub-desc">True for success, false for failure.</p></li>
+ * <li><b>response</b> : XMLHttpRequest<p class="sub-desc">The XMLHttpRequest which processed the update.</p></li>
+ * <li><b>options</b> : Object<p class="sub-desc">The config object passed to the update call.</p></li></ul>
+ * </p></li>
+ * <li>scope : <b>Object</b><p class="sub-desc">The scope in which
+ * to execute the callback (The callback's <code>this</code> reference.) If the
+ * <code>params</code> argument is a function, this scope is used for that function also.</p></li>
+ * <li>discardUrl : <b>Boolean</b><p class="sub-desc">By default, the URL of this request becomes
+ * the default URL for this Updater object, and will be subsequently used in {@link #refresh}
+ * calls. To bypass this behavior, pass <code>discardUrl:true</code> (defaults to false).</p></li>
+ * <li>timeout : <b>Number</b><p class="sub-desc">The number of seconds to wait for a response before
+ * timing out (defaults to {@link Ext.Updater.defaults#timeout}).</p></li>
+ * <li>text : <b>String</b><p class="sub-desc">The text to use as the innerHTML of the
+ * {@link Ext.Updater.defaults#indicatorText} div (defaults to 'Loading...'). To replace the entire div, not
+ * just the text, override {@link Ext.Updater.defaults#indicatorText} directly.</p></li>
+ * <li>nocache : <b>Boolean</b><p class="sub-desc">Only needed for GET
+ * requests, this option causes an extra, auto-generated parameter to be appended to the request
+ * to defeat caching (defaults to {@link Ext.Updater.defaults#disableCaching}).</p></li></ul>
+ * <p>
+ * For example:
+ <pre><code>
+ um.update({
+ url: "your-url.php",
+ params: {param1: "foo", param2: "bar"}, // or a URL encoded string
+ callback: yourFunction,
+ scope: yourObject, //(optional scope)
+ discardUrl: true,
+ nocache: true,
+ text: "Loading...",
+ timeout: 60,
+ scripts: false // Save time by avoiding RegExp execution.
+ });
+ </code></pre>
+ */
+ update : function(url, params, callback, discardUrl){
+ var me = this,
+ cfg,
+ callerScope;
+
+ if(me.fireEvent(BEFOREUPDATE, me.el, url, params) !== false){
+ if(Ext.isObject(url)){ // must be config object
+ cfg = url;
+ url = cfg.url;
+ params = params || cfg.params;
+ callback = callback || cfg.callback;
+ discardUrl = discardUrl || cfg.discardUrl;
+ callerScope = cfg.scope;
+ if(!Ext.isEmpty(cfg.nocache)){me.disableCaching = cfg.nocache;};
+ if(!Ext.isEmpty(cfg.text)){me.indicatorText = '<div class="loading-indicator">'+cfg.text+"</div>";};
+ if(!Ext.isEmpty(cfg.scripts)){me.loadScripts = cfg.scripts;};
+ if(!Ext.isEmpty(cfg.timeout)){me.timeout = cfg.timeout;};
+ }
+ me.showLoading();
+
+ if(!discardUrl){
+ me.defaultUrl = url;
+ }
+ if(Ext.isFunction(url)){
+ url = url.call(me);
+ }
+
+ var o = Ext.apply({}, {
+ url : url,
+ params: (Ext.isFunction(params) && callerScope) ? params.createDelegate(callerScope) : params,
+ success: processSuccess,
+ failure: processFailure,
+ scope: me,
+ callback: undefined,
+ timeout: (me.timeout*1000),
+ disableCaching: me.disableCaching,
+ argument: {
+ "options": cfg,
+ "url": url,
+ "form": null,
+ "callback": callback,
+ "scope": callerScope || window,
+ "params": params
+ }
+ }, cfg);
+
+ me.transaction = Ext.Ajax.request(o);
+ }
+ },
+
+ /**
+ * <p>Performs an asynchronous form post, updating this element with the response. If the form has the attribute
+ * enctype="<a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form-data</a>", it assumes it's a file upload.
+ * Uses this.sslBlankUrl for SSL file uploads to prevent IE security warning.</p>
+ * <p>File uploads are not performed using normal "Ajax" techniques, that is they are <b>not</b>
+ * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
+ * DOM <code><form></code> element temporarily modified to have its
+ * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
+ * to a dynamically generated, hidden <code><iframe></code> which is inserted into the document
+ * but removed after the return data has been gathered.</p>
+ * <p>Be aware that file upload packets, sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form-data</a>
+ * and some server technologies (notably JEE) may require some custom processing in order to
+ * retrieve parameter names and parameter values from the packet content.</p>
+ * @param {String/HTMLElement} form The form Id or form element
+ * @param {String} url (optional) The url to pass the form to. If omitted the action attribute on the form will be used.
+ * @param {Boolean} reset (optional) Whether to try to reset the form after the update
+ * @param {Function} callback (optional) Callback when transaction is complete. The following
+ * parameters are passed:<ul>
+ * <li><b>el</b> : Ext.Element<p class="sub-desc">The Element being updated.</p></li>
+ * <li><b>success</b> : Boolean<p class="sub-desc">True for success, false for failure.</p></li>
+ * <li><b>response</b> : XMLHttpRequest<p class="sub-desc">The XMLHttpRequest which processed the update.</p></li></ul>
+ */
+ formUpdate : function(form, url, reset, callback){
+ var me = this;
+ if(me.fireEvent(BEFOREUPDATE, me.el, form, url) !== false){
+ if(Ext.isFunction(url)){
+ url = url.call(me);
+ }
+ form = Ext.getDom(form);
+ me.transaction = Ext.Ajax.request({
+ form: form,
+ url:url,
+ success: processSuccess,
+ failure: processFailure,
+ scope: me,
+ timeout: (me.timeout*1000),
+ argument: {
+ "url": url,
+ "form": form,
+ "callback": callback,
+ "reset": reset
+ }
+ });
+ me.showLoading.defer(1, me);
+ }
+ },
+
+ /**
+ * Set this element to auto refresh. Can be canceled by calling {@link #stopAutoRefresh}.
+ * @param {Number} interval How often to update (in seconds).
+ * @param {String/Object/Function} url (optional) The url for this request, a config object in the same format
+ * supported by {@link #load}, or a function to call to get the url (defaults to the last used url). Note that while
+ * the url used in a load call can be reused by this method, other load config options will not be reused and must be
+ * sepcified as part of a config object passed as this paramter if needed.
+ * @param {String/Object} params (optional) The parameters to pass as either a url encoded string
+ * "¶m1=1¶m2=2" or as an object {param1: 1, param2: 2}
+ * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
+ * @param {Boolean} refreshNow (optional) Whether to execute the refresh now, or wait the interval
+ */
+ startAutoRefresh : function(interval, url, params, callback, refreshNow){
+ var me = this;
+ if(refreshNow){
+ me.update(url || me.defaultUrl, params, callback, true);
+ }
+ if(me.autoRefreshProcId){
+ clearInterval(me.autoRefreshProcId);
+ }
+ me.autoRefreshProcId = setInterval(me.update.createDelegate(me, [url || me.defaultUrl, params, callback, true]), interval * 1000);
+ },
+
+ /**
+ * Stop auto refresh on this element.
+ */
+ stopAutoRefresh : function(){
+ if(this.autoRefreshProcId){
+ clearInterval(this.autoRefreshProcId);
+ delete this.autoRefreshProcId;
+ }
+ },
+
+ /**
+ * Returns true if the Updater is currently set to auto refresh its content (see {@link #startAutoRefresh}), otherwise false.
+ */
+ isAutoRefreshing : function(){
+ return !!this.autoRefreshProcId;
+ },
+
+ /**
+ * Display the element's "loading" state. By default, the element is updated with {@link #indicatorText}. This
+ * method may be overridden to perform a custom action while this Updater is actively updating its contents.
+ */
+ showLoading : function(){
+ if(this.showLoadIndicator){
+ this.el.dom.innerHTML = this.indicatorText;
+ }
+ },
+
+ /**
+ * Aborts the currently executing transaction, if any.
+ */
+ abort : function(){
+ if(this.transaction){
+ Ext.Ajax.abort(this.transaction);
+ }
+ },
+
+ /**
+ * Returns true if an update is in progress, otherwise false.
+ * @return {Boolean}
+ */
+ isUpdating : function(){
+ return this.transaction ? Ext.Ajax.isLoading(this.transaction) : false;
+ },
+
+ /**
+ * Refresh the element with the last used url or defaultUrl. If there is no url, it returns immediately
+ * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
+ */
+ refresh : function(callback){
+ if(this.defaultUrl){
+ this.update(this.defaultUrl, null, callback, true);
+ }
+ }
+ }
+}());
+
+/**
+ * @class Ext.Updater.defaults
+ * The defaults collection enables customizing the default properties of Updater
+ */
+Ext.Updater.defaults = {
+ /**
+ * Timeout for requests or form posts in seconds (defaults to 30 seconds).
+ * @type Number
+ */
+ timeout : 30,
+ /**
+ * True to append a unique parameter to GET requests to disable caching (defaults to false).
+ * @type Boolean
+ */
+ disableCaching : false,
+ /**
+ * Whether or not to show {@link #indicatorText} during loading (defaults to true).
+ * @type Boolean
+ */
+ showLoadIndicator : true,
+ /**
+ * Text for loading indicator (defaults to '<div class="loading-indicator">Loading...</div>').
+ * @type String
+ */
+ indicatorText : '<div class="loading-indicator">Loading...</div>',
+ /**
+ * True to process scripts by default (defaults to false).
+ * @type Boolean
+ */
+ loadScripts : false,
+ /**
+ * Blank page URL to use with SSL file uploads (defaults to {@link Ext#SSL_SECURE_URL} if set, or "javascript:false").
+ * @type String
+ */
+ sslBlankUrl : Ext.SSL_SECURE_URL
+};
+
+
+/**
+ * Static convenience method. <b>This method is deprecated in favor of el.load({url:'foo.php', ...})</b>.
+ * Usage:
+ * <pre><code>Ext.Updater.updateElement("my-div", "stuff.php");</code></pre>
+ * @param {Mixed} el The element to update
+ * @param {String} url The url
+ * @param {String/Object} params (optional) Url encoded param string or an object of name/value pairs
+ * @param {Object} options (optional) A config object with any of the Updater properties you want to set - for
+ * example: {disableCaching:true, indicatorText: "Loading data..."}
+ * @static
+ * @deprecated
+ * @member Ext.Updater
+ */
+Ext.Updater.updateElement = function(el, url, params, options){
+ var um = Ext.get(el).getUpdater();
+ Ext.apply(um, options);
+ um.update(url, params, options ? options.callback : null);
+};
+
+/**
+ * @class Ext.Updater.BasicRenderer
+ * <p>This class is a base class implementing a simple render method which updates an element using results from an Ajax request.</p>
+ * <p>The BasicRenderer updates the element's innerHTML with the responseText. To perform a custom render (i.e. XML or JSON processing),
+ * create an object with a conforming {@link #render} method and pass it to setRenderer on the Updater.</p>
+ */
+Ext.Updater.BasicRenderer = function(){};
+
+Ext.Updater.BasicRenderer.prototype = {
+ /**
+ * This method is called when an Ajax response is received, and an Element needs updating.
+ * @param {Ext.Element} el The element being rendered
+ * @param {Object} xhr The XMLHttpRequest object
+ * @param {Updater} updateManager The calling update manager
+ * @param {Function} callback A callback that will need to be called if loadScripts is true on the Updater
+ */
+ render : function(el, response, updateManager, callback){
+ el.update(response.responseText, updateManager.loadScripts, callback);
+ }
+};/**
+ * @class Date
+ *
+ * The date parsing and formatting syntax contains a subset of
+ * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
+ * supported will provide results equivalent to their PHP versions.
+ *
+ * The following is a list of all currently supported formats:
+ * <pre>
+Format Description Example returned values
+------ ----------------------------------------------------------------------- -----------------------
+ d Day of the month, 2 digits with leading zeros 01 to 31
+ D A short textual representation of the day of the week Mon to Sun
+ j Day of the month without leading zeros 1 to 31
+ l A full textual representation of the day of the week Sunday to Saturday
+ N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday)
+ S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j
+ w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday)
+ z The day of the year (starting from 0) 0 to 364 (365 in leap years)
+ W ISO-8601 week number of year, weeks starting on Monday 01 to 53
+ F A full textual representation of a month, such as January or March January to December
+ m Numeric representation of a month, with leading zeros 01 to 12
+ M A short textual representation of a month Jan to Dec
+ n Numeric representation of a month, without leading zeros 1 to 12
+ t Number of days in the given month 28 to 31
+ L Whether it's a leap year 1 if it is a leap year, 0 otherwise.
+ o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004
+ belongs to the previous or next year, that year is used instead)
+ Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
+ y A two digit representation of a year Examples: 99 or 03
+ a Lowercase Ante meridiem and Post meridiem am or pm
+ A Uppercase Ante meridiem and Post meridiem AM or PM
+ g 12-hour format of an hour without leading zeros 1 to 12
+ G 24-hour format of an hour without leading zeros 0 to 23
+ h 12-hour format of an hour with leading zeros 01 to 12
+ H 24-hour format of an hour with leading zeros 00 to 23
+ i Minutes, with leading zeros 00 to 59
+ s Seconds, with leading zeros 00 to 59
+ u Decimal fraction of a second Examples:
+ (minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or
+ 100 (i.e. 0.100s) or
+ 999 (i.e. 0.999s) or
+ 999876543210 (i.e. 0.999876543210s)
+ O Difference to Greenwich time (GMT) in hours and minutes Example: +1030
+ P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00
+ T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ...
+ Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400
+ c ISO 8601 date
+ Notes: Examples:
+ 1) If unspecified, the month / day defaults to the current month / day, 1991 or
+ the time defaults to midnight, while the timezone defaults to the 1992-10 or
+ browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
+ and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or
+ are optional. 1995-07-18T17:21:28-02:00 or
+ 2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or
+ least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or
+ of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or
+ Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or
+ date-time granularity which are supported, or see 2000-02-13T21:25:33
+ http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34
+ U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463
+ M$ Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
+ \/Date(1238606590509+0800)\/
+</pre>
+ *
+ * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
+ * <pre><code>
+// Sample date:
+// 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
+
+var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
+document.write(dt.format('Y-m-d')); // 2007-01-10
+document.write(dt.format('F j, Y, g:i a')); // January 10, 2007, 3:05 pm
+document.write(dt.format('l, \\t\\he jS \\of F Y h:i:s A')); // Wednesday, the 10th of January 2007 03:05:01 PM
+</code></pre>
+ *
+ * Here are some standard date/time patterns that you might find helpful. They
+ * are not part of the source of Date.js, but to use them you can simply copy this
+ * block of code into any script that is included after Date.js and they will also become
+ * globally available on the Date object. Feel free to add or remove patterns as needed in your code.
+ * <pre><code>
+Date.patterns = {
+ ISO8601Long:"Y-m-d H:i:s",
+ ISO8601Short:"Y-m-d",
+ ShortDate: "n/j/Y",
+ LongDate: "l, F d, Y",
+ FullDateTime: "l, F d, Y g:i:s A",
+ MonthDay: "F d",
+ ShortTime: "g:i A",
+ LongTime: "g:i:s A",
+ SortableDateTime: "Y-m-d\\TH:i:s",
+ UniversalSortableDateTime: "Y-m-d H:i:sO",
+ YearMonth: "F, Y"
+};
+</code></pre>
+ *
+ * Example usage:
+ * <pre><code>
+var dt = new Date();
+document.write(dt.format(Date.patterns.ShortDate));
+</code></pre>
+ * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
+ * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
+ */
+
+/*
+ * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
+ * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
+ * They generate precompiled functions from format patterns instead of parsing and
+ * processing each pattern every time a date is formatted. These functions are available
+ * on every Date object.
+ */
+
+(function() {
+
+/**
+ * Global flag which determines if strict date parsing should be used.
+ * Strict date parsing will not roll-over invalid dates, which is the
+ * default behaviour of javascript Date objects.
+ * (see {@link #parseDate} for more information)
+ * Defaults to <tt>false</tt>.
+ * @static
+ * @type Boolean
+*/
+Date.useStrict = false;
+
+
+// create private copy of Ext's String.format() method
+// - to remove unnecessary dependency
+// - to resolve namespace conflict with M$-Ajax's implementation
+function xf(format) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return format.replace(/\{(\d+)\}/g, function(m, i) {
+ return args[i];
+ });
+}
+
+
+// private
+Date.formatCodeToRegex = function(character, currentGroup) {
+ // Note: currentGroup - position in regex result array (see notes for Date.parseCodes below)
+ var p = Date.parseCodes[character];
+
+ if (p) {
+ p = typeof p == 'function'? p() : p;
+ Date.parseCodes[character] = p; // reassign function result to prevent repeated execution
+ }
+
+ return p ? Ext.applyIf({
+ c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
+ }, p) : {
+ g:0,
+ c:null,
+ s:Ext.escapeRe(character) // treat unrecognised characters as literals
+ }
+};
+
+// private shorthand for Date.formatCodeToRegex since we'll be using it fairly often
+var $f = Date.formatCodeToRegex;
+
+Ext.apply(Date, {
+ /**
+ * <p>An object hash in which each property is a date parsing function. The property name is the
+ * format string which that function parses.</p>
+ * <p>This object is automatically populated with date parsing functions as
+ * date formats are requested for Ext standard formatting strings.</p>
+ * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
+ * may be used as a format string to {@link #parseDate}.<p>
+ * <p>Example:</p><pre><code>
+Date.parseFunctions['x-date-format'] = myDateParser;
+</code></pre>
+ * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
+ * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
+ * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
+ * (i.e. prevent javascript Date "rollover") (The default must be false).
+ * Invalid date strings should return null when parsed.</div></li>
+ * </ul></div></p>
+ * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
+ * formatting function must be placed into the {@link #formatFunctions} property.
+ * @property parseFunctions
+ * @static
+ * @type Object
+ */
+ parseFunctions: {
+ "M$": function(input, strict) {
+ // note: the timezone offset is ignored since the M$ Ajax server sends
+ // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
+ var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
+ var r = (input || '').match(re);
+ return r? new Date(((r[1] || '') + r[2]) * 1) : null;
+ }
+ },
+ parseRegexes: [],
+
+ /**
+ * <p>An object hash in which each property is a date formatting function. The property name is the
+ * format string which corresponds to the produced formatted date string.</p>
+ * <p>This object is automatically populated with date formatting functions as
+ * date formats are requested for Ext standard formatting strings.</p>
+ * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
+ * may be used as a format string to {@link #format}. Example:</p><pre><code>
+Date.formatFunctions['x-date-format'] = myDateFormatter;
+</code></pre>
+ * <p>A formatting function should return a string representation of the passed Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
+ * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
+ * </ul></div></p>
+ * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
+ * parsing function must be placed into the {@link #parseFunctions} property.
+ * @property formatFunctions
+ * @static
+ * @type Object
+ */
+ formatFunctions: {
+ "M$": function() {
+ // UTC milliseconds since Unix epoch (M$-AJAX serialized date format (MRSF))
+ return '\\/Date(' + this.getTime() + ')\\/';
+ }
+ },
+
+ y2kYear : 50,
+
+ /**
+ * Date interval constant
+ * @static
+ * @type String
+ */
+ MILLI : "ms",
+
+ /**
+ * Date interval constant
+ * @static
+ * @type String
+ */
+ SECOND : "s",
+
+ /**
+ * Date interval constant
+ * @static
+ * @type String
+ */
+ MINUTE : "mi",
+
+ /** Date interval constant
+ * @static
+ * @type String
+ */
+ HOUR : "h",
+
+ /**
+ * Date interval constant
+ * @static
+ * @type String
+ */
+ DAY : "d",
+
+ /**
+ * Date interval constant
+ * @static
+ * @type String
+ */
+ MONTH : "mo",
+
+ /**
+ * Date interval constant
+ * @static
+ * @type String
+ */
+ YEAR : "y",
+
+ /**
+ * <p>An object hash containing default date values used during date parsing.</p>
+ * <p>The following properties are available:<div class="mdetail-params"><ul>
+ * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
+ * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
+ * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
+ * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
+ * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
+ * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
+ * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
+ * </ul></div></p>
+ * <p>Override these properties to customize the default date values used by the {@link #parseDate} method.</p>
+ * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
+ * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
+ * It is the responsiblity of the developer to account for this.</b></p>
+ * Example Usage:
+ * <pre><code>
+// set default day value to the first day of the month
+Date.defaults.d = 1;
+
+// parse a February date string containing only year and month values.
+// setting the default day value to 1 prevents weird date rollover issues
+// when attempting to parse the following date string on, for example, March 31st 2009.
+Date.parseDate('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
+</code></pre>
+ * @property defaults
+ * @static
+ * @type Object
+ */
+ defaults: {},
+
+ /**
+ * An array of textual day names.
+ * Override these values for international dates.
+ * Example:
+ * <pre><code>
+Date.dayNames = [
+ 'SundayInYourLang',
+ 'MondayInYourLang',
+ ...
+];
+</code></pre>
+ * @type Array
+ * @static
+ */
+ dayNames : [
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday"
+ ],
+
+ /**
+ * An array of textual month names.
+ * Override these values for international dates.
+ * Example:
+ * <pre><code>
+Date.monthNames = [
+ 'JanInYourLang',
+ 'FebInYourLang',
+ ...
+];
+</code></pre>
+ * @type Array
+ * @static
+ */
+ monthNames : [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December"
+ ],
+
+ /**
+ * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
+ * Override these values for international dates.
+ * Example:
+ * <pre><code>
+Date.monthNumbers = {
+ 'ShortJanNameInYourLang':0,
+ 'ShortFebNameInYourLang':1,
+ ...
+};
+</code></pre>
+ * @type Object
+ * @static
+ */
+ monthNumbers : {
+ Jan:0,
+ Feb:1,
+ Mar:2,
+ Apr:3,
+ May:4,
+ Jun:5,
+ Jul:6,
+ Aug:7,
+ Sep:8,
+ Oct:9,
+ Nov:10,
+ Dec:11
+ },
+
+ /**
+ * Get the short month name for the given month number.
+ * Override this function for international dates.
+ * @param {Number} month A zero-based javascript month number.
+ * @return {String} The short month name.
+ * @static
+ */
+ getShortMonthName : function(month) {
+ return Date.monthNames[month].substring(0, 3);
+ },
+
+ /**
+ * Get the short day name for the given day number.
+ * Override this function for international dates.
+ * @param {Number} day A zero-based javascript day number.
+ * @return {String} The short day name.
+ * @static
+ */
+ getShortDayName : function(day) {
+ return Date.dayNames[day].substring(0, 3);
+ },
+
+ /**
+ * Get the zero-based javascript month number for the given short/full month name.
+ * Override this function for international dates.
+ * @param {String} name The short/full month name.
+ * @return {Number} The zero-based javascript month number.
+ * @static
+ */
+ getMonthNumber : function(name) {
+ // handle camel casing for english month names (since the keys for the Date.monthNumbers hash are case sensitive)
+ return Date.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
+ },
+
+ /**
+ * The base format-code to formatting-function hashmap used by the {@link #format} method.
+ * Formatting functions are strings (or functions which return strings) which
+ * will return the appropriate value when evaluated in the context of the Date object
+ * from which the {@link #format} method is called.
+ * Add to / override these mappings for custom date formatting.
+ * Note: Date.format() treats characters as literals if an appropriate mapping cannot be found.
+ * Example:
+ * <pre><code>
+Date.formatCodes.x = "String.leftPad(this.getDate(), 2, '0')";
+(new Date()).format("X"); // returns the current day of the month
+</code></pre>
+ * @type Object
+ * @static
+ */
+ formatCodes : {
+ d: "String.leftPad(this.getDate(), 2, '0')",
+ D: "Date.getShortDayName(this.getDay())", // get localised short day name
+ j: "this.getDate()",
+ l: "Date.dayNames[this.getDay()]",
+ N: "(this.getDay() ? this.getDay() : 7)",
+ S: "this.getSuffix()",
+ w: "this.getDay()",
+ z: "this.getDayOfYear()",
+ W: "String.leftPad(this.getWeekOfYear(), 2, '0')",
+ F: "Date.monthNames[this.getMonth()]",
+ m: "String.leftPad(this.getMonth() + 1, 2, '0')",
+ M: "Date.getShortMonthName(this.getMonth())", // get localised short month name
+ n: "(this.getMonth() + 1)",
+ t: "this.getDaysInMonth()",
+ L: "(this.isLeapYear() ? 1 : 0)",
+ o: "(this.getFullYear() + (this.getWeekOfYear() == 1 && this.getMonth() > 0 ? +1 : (this.getWeekOfYear() >= 52 && this.getMonth() < 11 ? -1 : 0)))",
+ Y: "this.getFullYear()",
+ y: "('' + this.getFullYear()).substring(2, 4)",
+ a: "(this.getHours() < 12 ? 'am' : 'pm')",
+ A: "(this.getHours() < 12 ? 'AM' : 'PM')",
+ g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
+ G: "this.getHours()",
+ h: "String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
+ H: "String.leftPad(this.getHours(), 2, '0')",
+ i: "String.leftPad(this.getMinutes(), 2, '0')",
+ s: "String.leftPad(this.getSeconds(), 2, '0')",
+ u: "String.leftPad(this.getMilliseconds(), 3, '0')",
+ O: "this.getGMTOffset()",
+ P: "this.getGMTOffset(true)",
+ T: "this.getTimezone()",
+ Z: "(this.getTimezoneOffset() * -60)",
+
+ c: function() { // ISO-8601 -- GMT format
+ for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
+ var e = c.charAt(i);
+ code.push(e == "T" ? "'T'" : Date.getFormatCode(e)); // treat T as a character literal
+ }
+ return code.join(" + ");
+ },
+ /*
+ c: function() { // ISO-8601 -- UTC format
+ return [
+ "this.getUTCFullYear()", "'-'",
+ "String.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
+ "String.leftPad(this.getUTCDate(), 2, '0')",
+ "'T'",
+ "String.leftPad(this.getUTCHours(), 2, '0')", "':'",
+ "String.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
+ "String.leftPad(this.getUTCSeconds(), 2, '0')",
+ "'Z'"
+ ].join(" + ");
+ },
+ */
+
+ U: "Math.round(this.getTime() / 1000)"
+ },
+
+ /**
+ * Checks if the passed Date parameters will cause a javascript Date "rollover".
+ * @param {Number} year 4-digit year
+ * @param {Number} month 1-based month-of-year
+ * @param {Number} day Day of month
+ * @param {Number} hour (optional) Hour
+ * @param {Number} minute (optional) Minute
+ * @param {Number} second (optional) Second
+ * @param {Number} millisecond (optional) Millisecond
+ * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
+ * @static
+ */
+ isValid : function(y, m, d, h, i, s, ms) {
+ // setup defaults
+ h = h || 0;
+ i = i || 0;
+ s = s || 0;
+ ms = ms || 0;
+
+ var dt = new Date(y, m - 1, d, h, i, s, ms);
+
+ return y == dt.getFullYear() &&
+ m == dt.getMonth() + 1 &&
+ d == dt.getDate() &&
+ h == dt.getHours() &&
+ i == dt.getMinutes() &&
+ s == dt.getSeconds() &&
+ ms == dt.getMilliseconds();
+ },
+
+ /**
+ * Parses the passed string using the specified date format.
+ * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
+ * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
+ * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
+ * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
+ * Keep in mind that the input date string must precisely match the specified format string
+ * in order for the parse operation to be successful (failed parse operations return a null value).
+ * <p>Example:</p><pre><code>
+//dt = Fri May 25 2007 (current date)
+var dt = new Date();
+
+//dt = Thu May 25 2006 (today's month/day in 2006)
+dt = Date.parseDate("2006", "Y");
+
+//dt = Sun Jan 15 2006 (all date parts specified)
+dt = Date.parseDate("2006-01-15", "Y-m-d");
+
+//dt = Sun Jan 15 2006 15:20:01
+dt = Date.parseDate("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
+
+// attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
+dt = Date.parseDate("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
+</code></pre>
+ * @param {String} input The raw date string.
+ * @param {String} format The expected date string format.
+ * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
+ (defaults to false). Invalid date strings will return null when parsed.
+ * @return {Date} The parsed Date.
+ * @static
+ */
+ parseDate : function(input, format, strict) {
+ var p = Date.parseFunctions;
+ if (p[format] == null) {
+ Date.createParser(format);
+ }
+ return p[format](input, Ext.isDefined(strict) ? strict : Date.useStrict);
+ },
+
+ // private
+ getFormatCode : function(character) {
+ var f = Date.formatCodes[character];
+
+ if (f) {
+ f = typeof f == 'function'? f() : f;
+ Date.formatCodes[character] = f; // reassign function result to prevent repeated execution
+ }
+
+ // note: unknown characters are treated as literals
+ return f || ("'" + String.escape(character) + "'");
+ },
+
+ // private
+ createFormat : function(format) {
+ var code = [],
+ special = false,
+ ch = '';
+
+ for (var i = 0; i < format.length; ++i) {
+ ch = format.charAt(i);
+ if (!special && ch == "\\") {
+ special = true;
+ } else if (special) {
+ special = false;
+ code.push("'" + String.escape(ch) + "'");
+ } else {
+ code.push(Date.getFormatCode(ch))
+ }
+ }
+ Date.formatFunctions[format] = new Function("return " + code.join('+'));
+ },
+
+ // private
+ createParser : function() {
+ var code = [
+ "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
+ "def = Date.defaults,",
+ "results = String(input).match(Date.parseRegexes[{0}]);", // either null, or an array of matched strings
+
+ "if(results){",
+ "{1}",
+
+ "if(u != null){", // i.e. unix time is defined
+ "v = new Date(u * 1000);", // give top priority to UNIX time
+ "}else{",
+ // create Date object representing midnight of the current day;
+ // this will provide us with our date defaults
+ // (note: clearTime() handles Daylight Saving Time automatically)
+ "dt = (new Date()).clearTime();",
+
+ // date calculations (note: these calculations create a dependency on Ext.num())
+ "y = Ext.num(y, Ext.num(def.y, dt.getFullYear()));",
+ "m = Ext.num(m, Ext.num(def.m - 1, dt.getMonth()));",
+ "d = Ext.num(d, Ext.num(def.d, dt.getDate()));",
+
+ // time calculations (note: these calculations create a dependency on Ext.num())
+ "h = Ext.num(h, Ext.num(def.h, dt.getHours()));",
+ "i = Ext.num(i, Ext.num(def.i, dt.getMinutes()));",
+ "s = Ext.num(s, Ext.num(def.s, dt.getSeconds()));",
+ "ms = Ext.num(ms, Ext.num(def.ms, dt.getMilliseconds()));",
+
+ "if(z >= 0 && y >= 0){",
+ // both the year and zero-based day of year are defined and >= 0.
+ // these 2 values alone provide sufficient info to create a full date object
+
+ // create Date object representing January 1st for the given year
+ "v = new Date(y, 0, 1, h, i, s, ms);",
+
+ // then add day of year, checking for Date "rollover" if necessary
+ "v = !strict? v : (strict === true && (z <= 364 || (v.isLeapYear() && z <= 365))? v.add(Date.DAY, z) : null);",
+ "}else if(strict === true && !Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
+ "v = null;", // invalid date, so return null
+ "}else{",
+ // plain old Date object
+ "v = new Date(y, m, d, h, i, s, ms);",
+ "}",
+ "}",
+ "}",
+
+ "if(v){",
+ // favour UTC offset over GMT offset
+ "if(zz != null){",
+ // reset to UTC, then add offset
+ "v = v.add(Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
+ "}else if(o){",
+ // reset to GMT, then add offset
+ "v = v.add(Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
+ "}",
+ "}",
+
+ "return v;"
+ ].join('\n');
+
+ return function(format) {
+ var regexNum = Date.parseRegexes.length,
+ currentGroup = 1,
+ calc = [],
+ regex = [],
+ special = false,
+ ch = "";
+
+ for (var i = 0; i < format.length; ++i) {
+ ch = format.charAt(i);
+ if (!special && ch == "\\") {
+ special = true;
+ } else if (special) {
+ special = false;
+ regex.push(String.escape(ch));
+ } else {
+ var obj = $f(ch, currentGroup);
+ currentGroup += obj.g;
+ regex.push(obj.s);
+ if (obj.g && obj.c) {
+ calc.push(obj.c);
+ }
+ }
+ }
+
+ Date.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$");
+ Date.parseFunctions[format] = new Function("input", "strict", xf(code, regexNum, calc.join('')));
+ }
+ }(),
+
+ // private
+ parseCodes : {
+ /*
+ * Notes:
+ * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
+ * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
+ * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
+ */
+ d: {
+ g:1,
+ c:"d = parseInt(results[{0}], 10);\n",
+ s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
+ },
+ j: {
+ g:1,
+ c:"d = parseInt(results[{0}], 10);\n",
+ s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
+ },
+ D: function() {
+ for (var a = [], i = 0; i < 7; a.push(Date.getShortDayName(i)), ++i); // get localised short day names
+ return {
+ g:0,
+ c:null,
+ s:"(?:" + a.join("|") +")"
+ }
+ },
+ l: function() {
+ return {
+ g:0,
+ c:null,
+ s:"(?:" + Date.dayNames.join("|") + ")"
+ }
+ },
+ N: {
+ g:0,
+ c:null,
+ s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
+ },
+ S: {
+ g:0,
+ c:null,
+ s:"(?:st|nd|rd|th)"
+ },
+ w: {
+ g:0,
+ c:null,
+ s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
+ },
+ z: {
+ g:1,
+ c:"z = parseInt(results[{0}], 10);\n",
+ s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
+ },
+ W: {
+ g:0,
+ c:null,
+ s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
+ },
+ F: function() {
+ return {
+ g:1,
+ c:"m = parseInt(Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
+ s:"(" + Date.monthNames.join("|") + ")"
+ }
+ },
+ M: function() {
+ for (var a = [], i = 0; i < 12; a.push(Date.getShortMonthName(i)), ++i); // get localised short month names
+ return Ext.applyIf({
+ s:"(" + a.join("|") + ")"
+ }, $f("F"));
+ },
+ m: {
+ g:1,
+ c:"m = parseInt(results[{0}], 10) - 1;\n",
+ s:"(\\d{2})" // month number with leading zeros (01 - 12)
+ },
+ n: {
+ g:1,
+ c:"m = parseInt(results[{0}], 10) - 1;\n",
+ s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
+ },
+ t: {
+ g:0,
+ c:null,
+ s:"(?:\\d{2})" // no. of days in the month (28 - 31)
+ },
+ L: {
+ g:0,
+ c:null,
+ s:"(?:1|0)"
+ },
+ o: function() {
+ return $f("Y");
+ },
+ Y: {
+ g:1,
+ c:"y = parseInt(results[{0}], 10);\n",
+ s:"(\\d{4})" // 4-digit year
+ },
+ y: {
+ g:1,
+ c:"var ty = parseInt(results[{0}], 10);\n"
+ + "y = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
+ s:"(\\d{1,2})"
+ },
+ a: {
+ g:1,
+ c:"if (results[{0}] == 'am') {\n"
+ + "if (!h || h == 12) { h = 0; }\n"
+ + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
+ s:"(am|pm)"
+ },
+ A: {
+ g:1,
+ c:"if (results[{0}] == 'AM') {\n"
+ + "if (!h || h == 12) { h = 0; }\n"
+ + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
+ s:"(AM|PM)"
+ },
+ g: function() {
+ return $f("G");
+ },
+ G: {
+ g:1,
+ c:"h = parseInt(results[{0}], 10);\n",
+ s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
+ },
+ h: function() {
+ return $f("H");
+ },
+ H: {
+ g:1,
+ c:"h = parseInt(results[{0}], 10);\n",
+ s:"(\\d{2})" // 24-hr format of an hour with leading zeroes (00 - 23)
+ },
+ i: {
+ g:1,
+ c:"i = parseInt(results[{0}], 10);\n",
+ s:"(\\d{2})" // minutes with leading zeros (00 - 59)
+ },
+ s: {
+ g:1,
+ c:"s = parseInt(results[{0}], 10);\n",
+ s:"(\\d{2})" // seconds with leading zeros (00 - 59)
+ },
+ u: {
+ g:1,
+ c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
+ s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
+ },
+ O: {
+ g:1,
+ c:[
+ "o = results[{0}];",
+ "var sn = o.substring(0,1),", // get + / - sign
+ "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
+ "mn = o.substring(3,5) % 60;", // get minutes
+ "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
+ ].join("\n"),
+ s: "([+\-]\\d{4})" // GMT offset in hrs and mins
+ },
+ P: {
+ g:1,
+ c:[
+ "o = results[{0}];",
+ "var sn = o.substring(0,1),", // get + / - sign
+ "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
+ "mn = o.substring(4,6) % 60;", // get minutes
+ "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
+ ].join("\n"),
+ s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
+ },
+ T: {
+ g:0,
+ c:null,
+ s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
+ },
+ Z: {
+ g:1,
+ c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
+ + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
+ s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
+ },
+ c: function() {
+ var calc = [],
+ arr = [
+ $f("Y", 1), // year
+ $f("m", 2), // month
+ $f("d", 3), // day
+ $f("h", 4), // hour
+ $f("i", 5), // minute
+ $f("s", 6), // second
+ {c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"}, // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
+ {c:[ // allow either "Z" (i.e. UTC) or "-0530" or "+08:00" (i.e. UTC offset) timezone delimiters. assumes local timezone if no timezone is specified
+ "if(results[8]) {", // timezone specified
+ "if(results[8] == 'Z'){",
+ "zz = 0;", // UTC
+ "}else if (results[8].indexOf(':') > -1){",
+ $f("P", 8).c, // timezone offset with colon separator
+ "}else{",
+ $f("O", 8).c, // timezone offset without colon separator
+ "}",
+ "}"
+ ].join('\n')}
+ ];
+
+ for (var i = 0, l = arr.length; i < l; ++i) {
+ calc.push(arr[i].c);
+ }
+
+ return {
+ g:1,
+ c:calc.join(""),
+ s:[
+ arr[0].s, // year (required)
+ "(?:", "-", arr[1].s, // month (optional)
+ "(?:", "-", arr[2].s, // day (optional)
+ "(?:",
+ "(?:T| )?", // time delimiter -- either a "T" or a single blank space
+ arr[3].s, ":", arr[4].s, // hour AND minute, delimited by a single colon (optional). MUST be preceded by either a "T" or a single blank space
+ "(?::", arr[5].s, ")?", // seconds (optional)
+ "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
+ "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
+ ")?",
+ ")?",
+ ")?"
+ ].join("")
+ }
+ },
+ U: {
+ g:1,
+ c:"u = parseInt(results[{0}], 10);\n",
+ s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
+ }
+ }
+});
+
+}());
+
+Ext.apply(Date.prototype, {
+ // private
+ dateFormat : function(format) {
+ if (Date.formatFunctions[format] == null) {
+ Date.createFormat(format);
+ }
+ return Date.formatFunctions[format].call(this);
+ },
+
+ /**
+ * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
+ *
+ * Note: The date string returned by the javascript Date object's toString() method varies
+ * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
+ * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
+ * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
+ * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
+ * from the GMT offset portion of the date string.
+ * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
+ */
+ getTimezone : function() {
+ // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
+ //
+ // Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
+ // Safari : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone (same as FF)
+ // FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
+ // IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
+ // IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
+ //
+ // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
+ // step 1: (?:\((.*)\) -- find timezone in parentheses
+ // step 2: ([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?) -- if nothing was found in step 1, find timezone from timezone offset portion of date string
+ // step 3: remove all non uppercase characters found in step 1 and 2
+ return this.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
+ },
+
+ /**
+ * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
+ * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
+ * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
+ */
+ getGMTOffset : function(colon) {
+ return (this.getTimezoneOffset() > 0 ? "-" : "+")
+ + String.leftPad(Math.floor(Math.abs(this.getTimezoneOffset()) / 60), 2, "0")
+ + (colon ? ":" : "")
+ + String.leftPad(Math.abs(this.getTimezoneOffset() % 60), 2, "0");
+ },
+
+ /**
+ * Get the numeric day number of the year, adjusted for leap year.
+ * @return {Number} 0 to 364 (365 in leap years).
+ */
+ getDayOfYear: function() {
+ var num = 0,
+ d = this.clone(),
+ m = this.getMonth(),
+ i;
+
+ for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
+ num += d.getDaysInMonth();
+ }
+ return num + this.getDate() - 1;
+ },
+
+ /**
+ * Get the numeric ISO-8601 week number of the year.
+ * (equivalent to the format specifier 'W', but without a leading zero).
+ * @return {Number} 1 to 53
+ */
+ getWeekOfYear : function() {
+ // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
+ var ms1d = 864e5, // milliseconds in a day
+ ms7d = 7 * ms1d; // milliseconds in a week
+
+ return function() { // return a closure so constants get calculated only once
+ var DC3 = Date.UTC(this.getFullYear(), this.getMonth(), this.getDate() + 3) / ms1d, // an Absolute Day Number
+ AWN = Math.floor(DC3 / 7), // an Absolute Week Number
+ Wyr = new Date(AWN * ms7d).getUTCFullYear();
+
+ return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
+ }
+ }(),
+
+ /**
+ * Checks if the current date falls within a leap year.
+ * @return {Boolean} True if the current date falls within a leap year, false otherwise.
+ */
+ isLeapYear : function() {
+ var year = this.getFullYear();
+ return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
+ },
+
+ /**
+ * Get the first day of the current month, adjusted for leap year. The returned value
+ * is the numeric day index within the week (0-6) which can be used in conjunction with
+ * the {@link #monthNames} array to retrieve the textual day name.
+ * Example:
+ * <pre><code>
+var dt = new Date('1/10/2007');
+document.write(Date.dayNames[dt.getFirstDayOfMonth()]); //output: 'Monday'
+</code></pre>
+ * @return {Number} The day number (0-6).
+ */
+ getFirstDayOfMonth : function() {
+ var day = (this.getDay() - (this.getDate() - 1)) % 7;
+ return (day < 0) ? (day + 7) : day;
+ },
+
+ /**
+ * Get the last day of the current month, adjusted for leap year. The returned value
+ * is the numeric day index within the week (0-6) which can be used in conjunction with
+ * the {@link #monthNames} array to retrieve the textual day name.
+ * Example:
+ * <pre><code>
+var dt = new Date('1/10/2007');
+document.write(Date.dayNames[dt.getLastDayOfMonth()]); //output: 'Wednesday'
+</code></pre>
+ * @return {Number} The day number (0-6).
+ */
+ getLastDayOfMonth : function() {
+ return this.getLastDateOfMonth().getDay();
+ },
+
+
+ /**
+ * Get the date of the first day of the month in which this date resides.
+ * @return {Date}
+ */
+ getFirstDateOfMonth : function() {
+ return new Date(this.getFullYear(), this.getMonth(), 1);
+ },
+
+ /**
+ * Get the date of the last day of the month in which this date resides.
+ * @return {Date}
+ */
+ getLastDateOfMonth : function() {
+ return new Date(this.getFullYear(), this.getMonth(), this.getDaysInMonth());
+ },
+
+ /**
+ * Get the number of days in the current month, adjusted for leap year.
+ * @return {Number} The number of days in the month.
+ */
+ getDaysInMonth: function() {
+ var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
+
+ return function() { // return a closure for efficiency
+ var m = this.getMonth();
+
+ return m == 1 && this.isLeapYear() ? 29 : daysInMonth[m];
+ }
+ }(),
+
+ /**
+ * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
+ * @return {String} 'st, 'nd', 'rd' or 'th'.
+ */
+ getSuffix : function() {
+ switch (this.getDate()) {
+ case 1:
+ case 21:
+ case 31:
+ return "st";
+ case 2:
+ case 22:
+ return "nd";
+ case 3:
+ case 23:
+ return "rd";
+ default:
+ return "th";
+ }
+ },
+
+ /**
+ * Creates and returns a new Date instance with the exact same date value as the called instance.
+ * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
+ * variable will also be changed. When the intention is to create a new variable that will not
+ * modify the original instance, you should create a clone.
+ *
+ * Example of correctly cloning a date:
+ * <pre><code>
+//wrong way:
+var orig = new Date('10/1/2006');
+var copy = orig;
+copy.setDate(5);
+document.write(orig); //returns 'Thu Oct 05 2006'!
+
+//correct way:
+var orig = new Date('10/1/2006');
+var copy = orig.clone();
+copy.setDate(5);
+document.write(orig); //returns 'Thu Oct 01 2006'
+</code></pre>
+ * @return {Date} The new Date instance.
+ */
+ clone : function() {
+ return new Date(this.getTime());
+ },
+
+ /**
+ * Checks if the current date is affected by Daylight Saving Time (DST).
+ * @return {Boolean} True if the current date is affected by DST.
+ */
+ isDST : function() {
+ // adapted from http://extjs.com/forum/showthread.php?p=247172#post247172
+ // courtesy of @geoffrey.mcgill
+ return new Date(this.getFullYear(), 0, 1).getTimezoneOffset() != this.getTimezoneOffset();
+ },
+
+ /**
+ * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
+ * automatically adjusting for Daylight Saving Time (DST) where applicable.
+ * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
+ * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
+ * @return {Date} this or the clone.
+ */
+ clearTime : function(clone) {
+ if (clone) {
+ return this.clone().clearTime();
+ }
+
+ // get current date before clearing time
+ var d = this.getDate();
+
+ // clear time
+ this.setHours(0);
+ this.setMinutes(0);
+ this.setSeconds(0);
+ this.setMilliseconds(0);
+
+ if (this.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
+ // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
+ // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
+
+ // increment hour until cloned date == current date
+ for (var hr = 1, c = this.add(Date.HOUR, hr); c.getDate() != d; hr++, c = this.add(Date.HOUR, hr));
+
+ this.setDate(d);
+ this.setHours(c.getHours());
+ }
+
+ return this;
+ },
+
+ /**
+ * Provides a convenient method for performing basic date arithmetic. This method
+ * does not modify the Date instance being called - it creates and returns
+ * a new Date instance containing the resulting date value.
+ *
+ * Examples:
+ * <pre><code>
+// Basic usage:
+var dt = new Date('10/29/2006').add(Date.DAY, 5);
+document.write(dt); //returns 'Fri Nov 03 2006 00:00:00'
+
+// Negative values will be subtracted:
+var dt2 = new Date('10/1/2006').add(Date.DAY, -5);
+document.write(dt2); //returns 'Tue Sep 26 2006 00:00:00'
+
+// You can even chain several calls together in one line:
+var dt3 = new Date('10/1/2006').add(Date.DAY, 5).add(Date.HOUR, 8).add(Date.MINUTE, -30);
+document.write(dt3); //returns 'Fri Oct 06 2006 07:30:00'
+</code></pre>
+ *
+ * @param {String} interval A valid date interval enum value.
+ * @param {Number} value The amount to add to the current date.
+ * @return {Date} The new Date instance.
+ */
+ add : function(interval, value) {
+ var d = this.clone();
+ if (!interval || value === 0) return d;
+
+ switch(interval.toLowerCase()) {
+ case Date.MILLI:
+ d.setMilliseconds(this.getMilliseconds() + value);
+ break;
+ case Date.SECOND:
+ d.setSeconds(this.getSeconds() + value);
+ break;
+ case Date.MINUTE:
+ d.setMinutes(this.getMinutes() + value);
+ break;
+ case Date.HOUR:
+ d.setHours(this.getHours() + value);
+ break;
+ case Date.DAY:
+ d.setDate(this.getDate() + value);
+ break;
+ case Date.MONTH:
+ var day = this.getDate();
+ if (day > 28) {
+ day = Math.min(day, this.getFirstDateOfMonth().add('mo', value).getLastDateOfMonth().getDate());
+ }
+ d.setDate(day);
+ d.setMonth(this.getMonth() + value);
+ break;
+ case Date.YEAR:
+ d.setFullYear(this.getFullYear() + value);
+ break;
+ }
+ return d;
+ },
+
+ /**
+ * Checks if this date falls on or between the given start and end dates.
+ * @param {Date} start Start date
+ * @param {Date} end End date
+ * @return {Boolean} true if this date falls on or between the given start and end dates.
+ */
+ between : function(start, end) {
+ var t = this.getTime();
+ return start.getTime() <= t && t <= end.getTime();
+ }
+});
+
+
+/**
+ * Formats a date given the supplied format string.
+ * @param {String} format The format string.
+ * @return {String} The formatted date.
+ * @method format
+ */
+Date.prototype.format = Date.prototype.dateFormat;
+
+
+// private
+if (Ext.isSafari && (navigator.userAgent.match(/WebKit\/(\d+)/)[1] || NaN) < 420) {
+ Ext.apply(Date.prototype, {
+ _xMonth : Date.prototype.setMonth,
+ _xDate : Date.prototype.setDate,
+
+ // Bug in Safari 1.3, 2.0 (WebKit build < 420)
+ // Date.setMonth does not work consistently if iMonth is not 0-11
+ setMonth : function(num) {
+ if (num <= -1) {
+ var n = Math.ceil(-num),
+ back_year = Math.ceil(n / 12),
+ month = (n % 12) ? 12 - n % 12 : 0;
+
+ this.setFullYear(this.getFullYear() - back_year);
+
+ return this._xMonth(month);
+ } else {
+ return this._xMonth(num);
+ }
+ },
+
+ // Bug in setDate() method (resolved in WebKit build 419.3, so to be safe we target Webkit builds < 420)
+ // The parameter for Date.setDate() is converted to a signed byte integer in Safari
+ // http://brianary.blogspot.com/2006/03/safari-date-bug.html
+ setDate : function(d) {
+ // use setTime() to workaround setDate() bug
+ // subtract current day of month in milliseconds, then add desired day of month in milliseconds
+ return this.setTime(this.getTime() - (this.getDate() - d) * 864e5);
+ }
+ });
+}
+
+
+
+/* Some basic Date tests... (requires Firebug)
+
+Date.parseDate('', 'c'); // call Date.parseDate() once to force computation of regex string so we can console.log() it
+console.log('Insane Regex for "c" format: %o', Date.parseCodes.c.s); // view the insane regex for the "c" format specifier
+
+// standard tests
+console.group('Standard Date.parseDate() Tests');
+ console.log('Date.parseDate("2009-01-05T11:38:56", "c") = %o', Date.parseDate("2009-01-05T11:38:56", "c")); // assumes browser's timezone setting
+ console.log('Date.parseDate("2009-02-04T12:37:55.001000", "c") = %o', Date.parseDate("2009-02-04T12:37:55.001000", "c")); // assumes browser's timezone setting
+ console.log('Date.parseDate("2009-03-03T13:36:54,101000Z", "c") = %o', Date.parseDate("2009-03-03T13:36:54,101000Z", "c")); // UTC
+ console.log('Date.parseDate("2009-04-02T14:35:53.901000-0530", "c") = %o', Date.parseDate("2009-04-02T14:35:53.901000-0530", "c")); // GMT-0530
+ console.log('Date.parseDate("2009-05-01T15:34:52,9876000+08:00", "c") = %o', Date.parseDate("2009-05-01T15:34:52,987600+08:00", "c")); // GMT+08:00
+console.groupEnd();
+
+// ISO-8601 format as specified in http://www.w3.org/TR/NOTE-datetime
+// -- accepts ALL 6 levels of date-time granularity
+console.group('ISO-8601 Granularity Test (see http://www.w3.org/TR/NOTE-datetime)');
+ console.log('Date.parseDate("1997", "c") = %o', Date.parseDate("1997", "c")); // YYYY (e.g. 1997)
+ console.log('Date.parseDate("1997-07", "c") = %o', Date.parseDate("1997-07", "c")); // YYYY-MM (e.g. 1997-07)
+ console.log('Date.parseDate("1997-07-16", "c") = %o', Date.parseDate("1997-07-16", "c")); // YYYY-MM-DD (e.g. 1997-07-16)
+ console.log('Date.parseDate("1997-07-16T19:20+01:00", "c") = %o', Date.parseDate("1997-07-16T19:20+01:00", "c")); // YYYY-MM-DDThh:mmTZD (e.g. 1997-07-16T19:20+01:00)
+ console.log('Date.parseDate("1997-07-16T19:20:30+01:00", "c") = %o', Date.parseDate("1997-07-16T19:20:30+01:00", "c")); // YYYY-MM-DDThh:mm:ssTZD (e.g. 1997-07-16T19:20:30+01:00)
+ console.log('Date.parseDate("1997-07-16T19:20:30.45+01:00", "c") = %o', Date.parseDate("1997-07-16T19:20:30.45+01:00", "c")); // YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45+01:00)
+ console.log('Date.parseDate("1997-07-16 19:20:30.45+01:00", "c") = %o', Date.parseDate("1997-07-16 19:20:30.45+01:00", "c")); // YYYY-MM-DD hh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45+01:00)
+ console.log('Date.parseDate("1997-13-16T19:20:30.45+01:00", "c", true)= %o', Date.parseDate("1997-13-16T19:20:30.45+01:00", "c", true)); // strict date parsing with invalid month value
+console.groupEnd();
+
+//*/
+/**
+ * @class Ext.util.MixedCollection
+ * @extends Ext.util.Observable
+ * A Collection class that maintains both numeric indexes and keys and exposes events.
+ * @constructor
+ * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
+ * function should add function references to the collection. Defaults to
+ * <tt>false</tt>.
+ * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
+ * and return the key value for that item. This is used when available to look up the key on items that
+ * were passed without an explicit key parameter to a MixedCollection method. Passing this parameter is
+ * equivalent to providing an implementation for the {@link #getKey} method.
+ */
+Ext.util.MixedCollection = function(allowFunctions, keyFn){
+ this.items = [];
+ this.map = {};
+ this.keys = [];
+ this.length = 0;
+ this.addEvents(
+ /**
+ * @event clear
+ * Fires when the collection is cleared.
+ */
+ 'clear',
+ /**
+ * @event add
+ * Fires when an item is added to the collection.
+ * @param {Number} index The index at which the item was added.
+ * @param {Object} o The item added.
+ * @param {String} key The key associated with the added item.
+ */
+ 'add',
+ /**
+ * @event replace
+ * Fires when an item is replaced in the collection.
+ * @param {String} key he key associated with the new added.
+ * @param {Object} old The item being replaced.
+ * @param {Object} new The new item.
+ */
+ 'replace',
+ /**
+ * @event remove
+ * Fires when an item is removed from the collection.
+ * @param {Object} o The item being removed.
+ * @param {String} key (optional) The key associated with the removed item.
+ */
+ 'remove',
+ 'sort'
+ );
+ this.allowFunctions = allowFunctions === true;
+ if(keyFn){
+ this.getKey = keyFn;
+ }
+ Ext.util.MixedCollection.superclass.constructor.call(this);
+};
+
+Ext.extend(Ext.util.MixedCollection, Ext.util.Observable, {
+
+ /**
+ * @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
+ * function should add function references to the collection. Defaults to
+ * <tt>false</tt>.
+ */
+ allowFunctions : false,
+
+ /**
+ * Adds an item to the collection. Fires the {@link #add} event when complete.
+ * @param {String} key <p>The key to associate with the item, or the new item.</p>
+ * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
+ * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
+ * the MixedCollection will be able to <i>derive</i> the key for the new item.
+ * In this case just pass the new item in this parameter.</p>
+ * @param {Object} o The item to add.
+ * @return {Object} The item added.
+ */
+ add : function(key, o){
+ if(arguments.length == 1){
+ o = arguments[0];
+ key = this.getKey(o);
+ }
+ if(typeof key != 'undefined' && key !== null){
+ var old = this.map[key];
+ if(typeof old != 'undefined'){
+ return this.replace(key, o);
+ }
+ this.map[key] = o;
+ }
+ this.length++;
+ this.items.push(o);
+ this.keys.push(key);
+ this.fireEvent('add', this.length-1, o, key);
+ return o;
+ },
+
+ /**
+ * MixedCollection has a generic way to fetch keys if you implement getKey. The default implementation
+ * simply returns <b><code>item.id</code></b> but you can provide your own implementation
+ * to return a different value as in the following examples:<pre><code>
+// normal way
+var mc = new Ext.util.MixedCollection();
+mc.add(someEl.dom.id, someEl);
+mc.add(otherEl.dom.id, otherEl);
+//and so on
+
+// using getKey
+var mc = new Ext.util.MixedCollection();
+mc.getKey = function(el){
+ return el.dom.id;
+};
+mc.add(someEl);
+mc.add(otherEl);
+
+// or via the constructor
+var mc = new Ext.util.MixedCollection(false, function(el){
+ return el.dom.id;
+});
+mc.add(someEl);
+mc.add(otherEl);
+ * </code></pre>
+ * @param {Object} item The item for which to find the key.
+ * @return {Object} The key for the passed item.
+ */
+ getKey : function(o){
+ return o.id;
+ },
+
+ /**
+ * Replaces an item in the collection. Fires the {@link #replace} event when complete.
+ * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
+ * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
+ * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
+ * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
+ * with one having the same key value, then just pass the replacement item in this parameter.</p>
+ * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
+ * with that key.
+ * @return {Object} The new item.
+ */
+ replace : function(key, o){
+ if(arguments.length == 1){
+ o = arguments[0];
+ key = this.getKey(o);
+ }
+ var old = this.map[key];
+ if(typeof key == 'undefined' || key === null || typeof old == 'undefined'){
+ return this.add(key, o);
+ }
+ var index = this.indexOfKey(key);
+ this.items[index] = o;
+ this.map[key] = o;
+ this.fireEvent('replace', key, old, o);
+ return o;
+ },
+
+ /**
+ * Adds all elements of an Array or an Object to the collection.
+ * @param {Object/Array} objs An Object containing properties which will be added
+ * to the collection, or an Array of values, each of which are added to the collection.
+ * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
+ * has been set to <tt>true</tt>.
+ */
+ addAll : function(objs){
+ if(arguments.length > 1 || Ext.isArray(objs)){
+ var args = arguments.length > 1 ? arguments : objs;
+ for(var i = 0, len = args.length; i < len; i++){
+ this.add(args[i]);
+ }
+ }else{
+ for(var key in objs){
+ if(this.allowFunctions || typeof objs[key] != 'function'){
+ this.add(key, objs[key]);
+ }
+ }
+ }
+ },
+
+ /**
+ * Executes the specified function once for every item in the collection, passing the following arguments:
+ * <div class="mdetail-params"><ul>
+ * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
+ * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
+ * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
+ * </ul></div>
+ * The function should return a boolean value. Returning false from the function will stop the iteration.
+ * @param {Function} fn The function to execute for each item.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current item in the iteration.
+ */
+ each : function(fn, scope){
+ var items = [].concat(this.items); // each safe for removal
+ for(var i = 0, len = items.length; i < len; i++){
+ if(fn.call(scope || items[i], items[i], i, len) === false){
+ break;
+ }
+ }
+ },
+
+ /**
+ * Executes the specified function once for every key in the collection, passing each
+ * key, and its associated item as the first two parameters.
+ * @param {Function} fn The function to execute for each item.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
+ */
+ eachKey : function(fn, scope){
+ for(var i = 0, len = this.keys.length; i < len; i++){
+ fn.call(scope || window, this.keys[i], this.items[i], i, len);
+ }
+ },
+
+ /**
+ * Returns the first item in the collection which elicits a true return value from the
+ * passed selection function.
+ * @param {Function} fn The selection function to execute for each item.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
+ * @return {Object} The first item in the collection which returned true from the selection function.
+ */
+ find : function(fn, scope){
+ for(var i = 0, len = this.items.length; i < len; i++){
+ if(fn.call(scope || window, this.items[i], this.keys[i])){
+ return this.items[i];
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
+ * @param {Number} index The index to insert the item at.
+ * @param {String} key The key to associate with the new item, or the item itself.
+ * @param {Object} o (optional) If the second parameter was a key, the new item.
+ * @return {Object} The item inserted.
+ */
+ insert : function(index, key, o){
+ if(arguments.length == 2){
+ o = arguments[1];
+ key = this.getKey(o);
+ }
+ if(this.containsKey(key)){
+ this.suspendEvents();
+ this.removeKey(key);
+ this.resumeEvents();
+ }
+ if(index >= this.length){
+ return this.add(key, o);
+ }
+ this.length++;
+ this.items.splice(index, 0, o);
+ if(typeof key != 'undefined' && key !== null){
+ this.map[key] = o;
+ }
+ this.keys.splice(index, 0, key);
+ this.fireEvent('add', index, o, key);
+ return o;
+ },
+
+ /**
+ * Remove an item from the collection.
+ * @param {Object} o The item to remove.
+ * @return {Object} The item removed or false if no item was removed.
+ */
+ remove : function(o){
+ return this.removeAt(this.indexOf(o));
+ },
+
+ /**
+ * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
+ * @param {Number} index The index within the collection of the item to remove.
+ * @return {Object} The item removed or false if no item was removed.
+ */
+ removeAt : function(index){
+ if(index < this.length && index >= 0){
+ this.length--;
+ var o = this.items[index];
+ this.items.splice(index, 1);
+ var key = this.keys[index];
+ if(typeof key != 'undefined'){
+ delete this.map[key];
+ }
+ this.keys.splice(index, 1);
+ this.fireEvent('remove', o, key);
+ return o;
+ }
+ return false;
+ },
+
+ /**
+ * Removed an item associated with the passed key fom the collection.
+ * @param {String} key The key of the item to remove.
+ * @return {Object} The item removed or false if no item was removed.
+ */
+ removeKey : function(key){
+ return this.removeAt(this.indexOfKey(key));
+ },
+
+ /**
+ * Returns the number of items in the collection.
+ * @return {Number} the number of items in the collection.
+ */
+ getCount : function(){
+ return this.length;
+ },
+
+ /**
+ * Returns index within the collection of the passed Object.
+ * @param {Object} o The item to find the index of.
+ * @return {Number} index of the item. Returns -1 if not found.
+ */
+ indexOf : function(o){
+ return this.items.indexOf(o);
+ },
+
+ /**
+ * Returns index within the collection of the passed key.
+ * @param {String} key The key to find the index of.
+ * @return {Number} index of the key.
+ */
+ indexOfKey : function(key){
+ return this.keys.indexOf(key);
+ },
+
+ /**
+ * Returns the item associated with the passed key OR index.
+ * Key has priority over index. This is the equivalent
+ * of calling {@link #key} first, then if nothing matched calling {@link #itemAt}.
+ * @param {String/Number} key The key or index of the item.
+ * @return {Object} If the item is found, returns the item. If the item was not found, returns <tt>undefined</tt>.
+ * If an item was found, but is a Class, returns <tt>null</tt>.
+ */
+ item : function(key){
+ var mk = this.map[key],
+ item = mk !== undefined ? mk : (typeof key == 'number') ? this.items[key] : undefined;
+ return !Ext.isFunction(item) || this.allowFunctions ? item : null; // for prototype!
+ },
+
+ /**
+ * Returns the item at the specified index.
+ * @param {Number} index The index of the item.
+ * @return {Object} The item at the specified index.
+ */
+ itemAt : function(index){
+ return this.items[index];
+ },
+
+ /**
+ * Returns the item associated with the passed key.
+ * @param {String/Number} key The key of the item.
+ * @return {Object} The item associated with the passed key.
+ */
+ key : function(key){
+ return this.map[key];
+ },
+
+ /**
+ * Returns true if the collection contains the passed Object as an item.
+ * @param {Object} o The Object to look for in the collection.
+ * @return {Boolean} True if the collection contains the Object as an item.
+ */
+ contains : function(o){
+ return this.indexOf(o) != -1;
+ },
+
+ /**
+ * Returns true if the collection contains the passed Object as a key.
+ * @param {String} key The key to look for in the collection.
+ * @return {Boolean} True if the collection contains the Object as a key.
+ */
+ containsKey : function(key){
+ return typeof this.map[key] != 'undefined';
+ },
+
+ /**
+ * Removes all items from the collection. Fires the {@link #clear} event when complete.
+ */
+ clear : function(){
+ this.length = 0;
+ this.items = [];
+ this.keys = [];
+ this.map = {};
+ this.fireEvent('clear');
+ },
+
+ /**
+ * Returns the first item in the collection.
+ * @return {Object} the first item in the collection..
+ */
+ first : function(){
+ return this.items[0];
+ },
+
+ /**
+ * Returns the last item in the collection.
+ * @return {Object} the last item in the collection..
+ */
+ last : function(){
+ return this.items[this.length-1];
+ },
+
+ /**
+ * @private
+ * Performs the actual sorting based on a direction and a sorting function. Internally,
+ * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
+ * the sorted array data back into this.items and this.keys
+ * @param {String} property Property to sort by ('key', 'value', or 'index')
+ * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
+ * @param {Function} fn (optional) Comparison function that defines the sort order.
+ * Defaults to sorting by numeric value.
+ */
+ _sort : function(property, dir, fn){
+ var i, len,
+ dsc = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
+
+ //this is a temporary array used to apply the sorting function
+ c = [],
+ keys = this.keys,
+ items = this.items;
+
+ //default to a simple sorter function if one is not provided
+ fn = fn || function(a, b) {
+ return a - b;
+ };
+
+ //copy all the items into a temporary array, which we will sort
+ for(i = 0, len = items.length; i < len; i++){
+ c[c.length] = {
+ key : keys[i],
+ value: items[i],
+ index: i
+ };
+ }
+
+ //sort the temporary array
+ c.sort(function(a, b){
+ var v = fn(a[property], b[property]) * dsc;
+ if(v === 0){
+ v = (a.index < b.index ? -1 : 1);
+ }
+ return v;
+ });
+
+ //copy the temporary array back into the main this.items and this.keys objects
+ for(i = 0, len = c.length; i < len; i++){
+ items[i] = c[i].value;
+ keys[i] = c[i].key;
+ }
+
+ this.fireEvent('sort', this);
+ },
+
+ /**
+ * Sorts this collection by <b>item</b> value with the passed comparison function.
+ * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
+ * @param {Function} fn (optional) Comparison function that defines the sort order.
+ * Defaults to sorting by numeric value.
+ */
+ sort : function(dir, fn){
+ this._sort('value', dir, fn);
+ },
+
+ /**
+ * Reorders each of the items based on a mapping from old index to new index. Internally this
+ * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
+ * @param {Object} mapping Mapping from old item index to new item index
+ */
+ reorder: function(mapping) {
+ this.suspendEvents();
+
+ var items = this.items,
+ index = 0,
+ length = items.length,
+ order = [],
+ remaining = [];
+
+ //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
+ for (oldIndex in mapping) {
+ order[mapping[oldIndex]] = items[oldIndex];
+ }
+
+ for (index = 0; index < length; index++) {
+ if (mapping[index] == undefined) {
+ remaining.push(items[index]);
+ }
+ }
+
+ for (index = 0; index < length; index++) {
+ if (order[index] == undefined) {
+ order[index] = remaining.shift();
+ }
+ }
+
+ this.clear();
+ this.addAll(order);
+
+ this.resumeEvents();
+ this.fireEvent('sort', this);
+ },
+
+ /**
+ * Sorts this collection by <b>key</b>s.
+ * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
+ * @param {Function} fn (optional) Comparison function that defines the sort order.
+ * Defaults to sorting by case insensitive string.
+ */
+ keySort : function(dir, fn){
+ this._sort('key', dir, fn || function(a, b){
+ var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
+ return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
+ });
+ },
+
+ /**
+ * Returns a range of items in this collection
+ * @param {Number} startIndex (optional) The starting index. Defaults to 0.
+ * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
+ * @return {Array} An array of items
+ */
+ getRange : function(start, end){
+ var items = this.items;
+ if(items.length < 1){
+ return [];
+ }
+ start = start || 0;
+ end = Math.min(typeof end == 'undefined' ? this.length-1 : end, this.length-1);
+ var i, r = [];
+ if(start <= end){
+ for(i = start; i <= end; i++) {
+ r[r.length] = items[i];
+ }
+ }else{
+ for(i = start; i >= end; i--) {
+ r[r.length] = items[i];
+ }
+ }
+ return r;
+ },
+
+ /**
+ * Filter the <i>objects</i> in this collection by a specific property.
+ * Returns a new collection that has been filtered.
+ * @param {String} property A property on your objects
+ * @param {String/RegExp} value Either string that the property values
+ * should start with or a RegExp to test against the property
+ * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
+ * @param {Boolean} caseSensitive (optional) True for case sensitive comparison (defaults to False).
+ * @return {MixedCollection} The new filtered collection
+ */
+ filter : function(property, value, anyMatch, caseSensitive){
+ if(Ext.isEmpty(value, false)){
+ return this.clone();
+ }
+ value = this.createValueMatcher(value, anyMatch, caseSensitive);
+ return this.filterBy(function(o){
+ return o && value.test(o[property]);
+ });
+ },
+
+ /**
+ * Filter by a function. Returns a <i>new</i> collection that has been filtered.
+ * The passed function will be called with each object in the collection.
+ * If the function returns true, the value is included otherwise it is filtered.
+ * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
+ * @return {MixedCollection} The new filtered collection
+ */
+ filterBy : function(fn, scope){
+ var r = new Ext.util.MixedCollection();
+ r.getKey = this.getKey;
+ var k = this.keys, it = this.items;
+ for(var i = 0, len = it.length; i < len; i++){
+ if(fn.call(scope||this, it[i], k[i])){
+ r.add(k[i], it[i]);
+ }
+ }
+ return r;
+ },
+
+ /**
+ * Finds the index of the first matching object in this collection by a specific property/value.
+ * @param {String} property The name of a property on your objects.
+ * @param {String/RegExp} value A string that the property values
+ * should start with or a RegExp to test against the property.
+ * @param {Number} start (optional) The index to start searching at (defaults to 0).
+ * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning.
+ * @param {Boolean} caseSensitive (optional) True for case sensitive comparison.
+ * @return {Number} The matched index or -1
+ */
+ findIndex : function(property, value, start, anyMatch, caseSensitive){
+ if(Ext.isEmpty(value, false)){
+ return -1;
+ }
+ value = this.createValueMatcher(value, anyMatch, caseSensitive);
+ return this.findIndexBy(function(o){
+ return o && value.test(o[property]);
+ }, null, start);
+ },
+
+ /**
+ * Find the index of the first matching object in this collection by a function.
+ * If the function returns <i>true</i> it is considered a match.
+ * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
+ * @param {Number} start (optional) The index to start searching at (defaults to 0).
+ * @return {Number} The matched index or -1
+ */
+ findIndexBy : function(fn, scope, start){
+ var k = this.keys, it = this.items;
+ for(var i = (start||0), len = it.length; i < len; i++){
+ if(fn.call(scope||this, it[i], k[i])){
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ /**
+ * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
+ * and by Ext.data.Store#filter
+ * @private
+ * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
+ * @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
+ * @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
+ * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
+ */
+ createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
+ if (!value.exec) { // not a regex
+ var er = Ext.escapeRe;
+ value = String(value);
+
+ if (anyMatch === true) {
+ value = er(value);
+ } else {
+ value = '^' + er(value);
+ if (exactMatch === true) {
+ value += '$';
+ }
+ }
+ value = new RegExp(value, caseSensitive ? '' : 'i');
+ }
+ return value;
+ },
+
+ /**
+ * Creates a shallow copy of this collection
+ * @return {MixedCollection}
+ */
+ clone : function(){
+ var r = new Ext.util.MixedCollection();
+ var k = this.keys, it = this.items;
+ for(var i = 0, len = it.length; i < len; i++){
+ r.add(k[i], it[i]);
+ }
+ r.getKey = this.getKey;
+ return r;
+ }
+});
+/**
+ * This method calls {@link #item item()}.
+ * Returns the item associated with the passed key OR index. Key has priority
+ * over index. This is the equivalent of calling {@link #key} first, then if
+ * nothing matched calling {@link #itemAt}.
+ * @param {String/Number} key The key or index of the item.
+ * @return {Object} If the item is found, returns the item. If the item was
+ * not found, returns <tt>undefined</tt>. If an item was found, but is a Class,
+ * returns <tt>null</tt>.
+ */
+Ext.util.MixedCollection.prototype.get = Ext.util.MixedCollection.prototype.item;/**
+ * @class Ext.util.JSON
+ * Modified version of Douglas Crockford"s json.js that doesn"t
+ * mess with the Object prototype
+ * http://www.json.org/js.html
+ * @singleton
+ */
+Ext.util.JSON = new (function(){
+ var useHasOwn = !!{}.hasOwnProperty,
+ isNative = function() {
+ var useNative = null;
+
+ return function() {
+ if (useNative === null) {
+ useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
+ }
+
+ return useNative;
+ };
+ }(),
+ pad = function(n) {
+ return n < 10 ? "0" + n : n;
+ },
+ doDecode = function(json){
+ return eval("(" + json + ')');
+ },
+ doEncode = function(o){
+ if(!Ext.isDefined(o) || o === null){
+ return "null";
+ }else if(Ext.isArray(o)){
+ return encodeArray(o);
+ }else if(Ext.isDate(o)){
+ return Ext.util.JSON.encodeDate(o);
+ }else if(Ext.isString(o)){
+ return encodeString(o);
+ }else if(typeof o == "number"){
+ //don't use isNumber here, since finite checks happen inside isNumber
+ return isFinite(o) ? String(o) : "null";
+ }else if(Ext.isBoolean(o)){
+ return String(o);
+ }else {
+ var a = ["{"], b, i, v;
+ for (i in o) {
+ // don't encode DOM objects
+ if(!o.getElementsByTagName){
+ if(!useHasOwn || o.hasOwnProperty(i)) {
+ v = o[i];
+ switch (typeof v) {
+ case "undefined":
+ case "function":
+ case "unknown":
+ break;
+ default:
+ if(b){
+ a.push(',');
+ }
+ a.push(doEncode(i), ":",
+ v === null ? "null" : doEncode(v));
+ b = true;
+ }
+ }
+ }
+ }
+ a.push("}");
+ return a.join("");
+ }
+ },
+ m = {
+ "\b": '\\b',
+ "\t": '\\t',
+ "\n": '\\n',
+ "\f": '\\f',
+ "\r": '\\r',
+ '"' : '\\"',
+ "\\": '\\\\'
+ },
+ encodeString = function(s){
+ if (/["\\\x00-\x1f]/.test(s)) {
+ return '"' + s.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+ var c = m[b];
+ if(c){
+ return c;
+ }
+ c = b.charCodeAt();
+ return "\\u00" +
+ Math.floor(c / 16).toString(16) +
+ (c % 16).toString(16);
+ }) + '"';
+ }
+ return '"' + s + '"';
+ },
+ encodeArray = function(o){
+ var a = ["["], b, i, l = o.length, v;
+ for (i = 0; i < l; i += 1) {
+ v = o[i];
+ switch (typeof v) {
+ case "undefined":
+ case "function":
+ case "unknown":
+ break;
+ default:
+ if (b) {
+ a.push(',');
+ }
+ a.push(v === null ? "null" : Ext.util.JSON.encode(v));
+ b = true;
+ }
+ }
+ a.push("]");
+ return a.join("");
+ };
+
+ /**
+ * <p>Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
+ * <b>The returned value includes enclosing double quotation marks.</b></p>
+ * <p>The default return format is "yyyy-mm-ddThh:mm:ss".</p>
+ * <p>To override this:</p><pre><code>
+Ext.util.JSON.encodeDate = function(d) {
+ return d.format('"Y-m-d"');
+};
+</code></pre>
+ * @param {Date} d The Date to encode
+ * @return {String} The string literal to use in a JSON string.
+ */
+ this.encodeDate = function(o){
+ return '"' + o.getFullYear() + "-" +
+ pad(o.getMonth() + 1) + "-" +
+ pad(o.getDate()) + "T" +
+ pad(o.getHours()) + ":" +
+ pad(o.getMinutes()) + ":" +
+ pad(o.getSeconds()) + '"';
+ };
+
+ /**
+ * Encodes an Object, Array or other value
+ * @param {Mixed} o The variable to encode
+ * @return {String} The JSON string
+ */
+ this.encode = function() {
+ var ec;
+ return function(o) {
+ if (!ec) {
+ // setup encoding function on first access
+ ec = isNative() ? JSON.stringify : doEncode;
+ }
+ return ec(o);
+ };
+ }();
+
+
+ /**
+ * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError unless the safe option is set.
+ * @param {String} json The JSON string
+ * @return {Object} The resulting object
+ */
+ this.decode = function() {
+ var dc;
+ return function(json) {
+ if (!dc) {
+ // setup decoding function on first access
+ dc = isNative() ? JSON.parse : doDecode;
+ }
+ return dc(json);
+ };
+ }();
+
+})();
+/**
+ * Shorthand for {@link Ext.util.JSON#encode}
+ * @param {Mixed} o The variable to encode
+ * @return {String} The JSON string
+ * @member Ext
+ * @method encode
+ */
+Ext.encode = Ext.util.JSON.encode;
+/**
+ * Shorthand for {@link Ext.util.JSON#decode}
+ * @param {String} json The JSON string
+ * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
+ * @return {Object} The resulting object
+ * @member Ext
+ * @method decode
+ */
+Ext.decode = Ext.util.JSON.decode;
+/**
+ * @class Ext.util.Format
+ * Reusable data formatting functions
+ * @singleton
+ */
+Ext.util.Format = function(){
+ var trimRe = /^\s+|\s+$/g,
+ stripTagsRE = /<\/?[^>]+>/gi,
+ stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
+ nl2brRe = /\r?\n/g;
+
+ return {
+ /**
+ * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
+ * @param {String} value The string to truncate
+ * @param {Number} length The maximum length to allow before truncating
+ * @param {Boolean} word True to try to find a common work break
+ * @return {String} The converted text
+ */
+ ellipsis : function(value, len, word){
+ if(value && value.length > len){
+ if(word){
+ var vs = value.substr(0, len - 2),
+ index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
+ if(index == -1 || index < (len - 15)){
+ return value.substr(0, len - 3) + "...";
+ }else{
+ return vs.substr(0, index) + "...";
+ }
+ } else{
+ return value.substr(0, len - 3) + "...";
+ }
+ }
+ return value;
+ },
+
+ /**
+ * Checks a reference and converts it to empty string if it is undefined
+ * @param {Mixed} value Reference to check
+ * @return {Mixed} Empty string if converted, otherwise the original value
+ */
+ undef : function(value){
+ return value !== undefined ? value : "";
+ },
+
+ /**
+ * Checks a reference and converts it to the default value if it's empty
+ * @param {Mixed} value Reference to check
+ * @param {String} defaultValue The value to insert of it's undefined (defaults to "")
+ * @return {String}
+ */
+ defaultValue : function(value, defaultValue){
+ return value !== undefined && value !== '' ? value : defaultValue;
+ },
+
+ /**
+ * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
+ * @param {String} value The string to encode
+ * @return {String} The encoded text
+ */
+ htmlEncode : function(value){
+ return !value ? value : String(value).replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/"/g, """);
+ },
+
+ /**
+ * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
+ * @param {String} value The string to decode
+ * @return {String} The decoded text
+ */
+ htmlDecode : function(value){
+ return !value ? value : String(value).replace(/>/g, ">").replace(/</g, "<").replace(/"/g, '"').replace(/&/g, "&");
+ },
+
+ /**
+ * Trims any whitespace from either side of a string
+ * @param {String} value The text to trim
+ * @return {String} The trimmed text
+ */
+ trim : function(value){
+ return String(value).replace(trimRe, "");
+ },
+
+ /**
+ * Returns a substring from within an original string
+ * @param {String} value The original text
+ * @param {Number} start The start index of the substring
+ * @param {Number} length The length of the substring
+ * @return {String} The substring
+ */
+ substr : function(value, start, length){
+ return String(value).substr(start, length);
+ },
+
+ /**
+ * Converts a string to all lower case letters
+ * @param {String} value The text to convert
+ * @return {String} The converted text
+ */
+ lowercase : function(value){
+ return String(value).toLowerCase();
+ },
+
+ /**
+ * Converts a string to all upper case letters
+ * @param {String} value The text to convert
+ * @return {String} The converted text
+ */
+ uppercase : function(value){
+ return String(value).toUpperCase();
+ },
+
+ /**
+ * Converts the first character only of a string to upper case
+ * @param {String} value The text to convert
+ * @return {String} The converted text
+ */
+ capitalize : function(value){
+ return !value ? value : value.charAt(0).toUpperCase() + value.substr(1).toLowerCase();
+ },
+
+ // private
+ call : function(value, fn){
+ if(arguments.length > 2){
+ var args = Array.prototype.slice.call(arguments, 2);
+ args.unshift(value);
+ return eval(fn).apply(window, args);
+ }else{
+ return eval(fn).call(window, value);
+ }
+ },
+
+ /**
+ * Format a number as US currency
+ * @param {Number/String} value The numeric value to format
+ * @return {String} The formatted currency string
+ */
+ usMoney : function(v){
+ v = (Math.round((v-0)*100))/100;
+ v = (v == Math.floor(v)) ? v + ".00" : ((v*10 == Math.floor(v*10)) ? v + "0" : v);
+ v = String(v);
+ var ps = v.split('.'),
+ whole = ps[0],
+ sub = ps[1] ? '.'+ ps[1] : '.00',
+ r = /(\d+)(\d{3})/;
+ while (r.test(whole)) {
+ whole = whole.replace(r, '$1' + ',' + '$2');
+ }
+ v = whole + sub;
+ if(v.charAt(0) == '-'){
+ return '-$' + v.substr(1);
+ }
+ return "$" + v;
+ },
+
+ /**
+ * Parse a value into a formatted date using the specified format pattern.
+ * @param {String/Date} value The value to format (Strings must conform to the format expected by the javascript Date object's <a href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> method)
+ * @param {String} format (optional) Any valid date format string (defaults to 'm/d/Y')
+ * @return {String} The formatted date string
+ */
+ date : function(v, format){
+ if(!v){
+ return "";
+ }
+ if(!Ext.isDate(v)){
+ v = new Date(Date.parse(v));
+ }
+ return v.dateFormat(format || "m/d/Y");
+ },
+
+ /**
+ * Returns a date rendering function that can be reused to apply a date format multiple times efficiently
+ * @param {String} format Any valid date format string
+ * @return {Function} The date formatting function
+ */
+ dateRenderer : function(format){
+ return function(v){
+ return Ext.util.Format.date(v, format);
+ };
+ },
+
+ /**
+ * Strips all HTML tags
+ * @param {Mixed} value The text from which to strip tags
+ * @return {String} The stripped text
+ */
+ stripTags : function(v){
+ return !v ? v : String(v).replace(stripTagsRE, "");
+ },
+
+ /**
+ * Strips all script tags
+ * @param {Mixed} value The text from which to strip script tags
+ * @return {String} The stripped text
+ */
+ stripScripts : function(v){
+ return !v ? v : String(v).replace(stripScriptsRe, "");
+ },
+
+ /**
+ * Simple format for a file size (xxx bytes, xxx KB, xxx MB)
+ * @param {Number/String} size The numeric value to format
+ * @return {String} The formatted file size
+ */
+ fileSize : function(size){
+ if(size < 1024) {
+ return size + " bytes";
+ } else if(size < 1048576) {
+ return (Math.round(((size*10) / 1024))/10) + " KB";
+ } else {
+ return (Math.round(((size*10) / 1048576))/10) + " MB";
+ }
+ },
+
+ /**
+ * It does simple math for use in a template, for example:<pre><code>
+ * var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}');
+ * </code></pre>
+ * @return {Function} A function that operates on the passed value.
+ */
+ math : function(){
+ var fns = {};
+ return function(v, a){
+ if(!fns[a]){
+ fns[a] = new Function('v', 'return v ' + a + ';');
+ }
+ return fns[a](v);
+ }
+ }(),
+
+ /**
+ * Rounds the passed number to the required decimal precision.
+ * @param {Number/String} value The numeric value to round.
+ * @param {Number} precision The number of decimal places to which to round the first parameter's value.
+ * @return {Number} The rounded value.
+ */
+ round : function(value, precision) {
+ var result = Number(value);
+ if (typeof precision == 'number') {
+ precision = Math.pow(10, precision);
+ result = Math.round(value * precision) / precision;
+ }
+ return result;
+ },
+
+ /**
+ * Formats the number according to the format string.
+ * <div style="margin-left:40px">examples (123456.789):
+ * <div style="margin-left:10px">
+ * 0 - (123456) show only digits, no precision<br>
+ * 0.00 - (123456.78) show only digits, 2 precision<br>
+ * 0.0000 - (123456.7890) show only digits, 4 precision<br>
+ * 0,000 - (123,456) show comma and digits, no precision<br>
+ * 0,000.00 - (123,456.78) show comma and digits, 2 precision<br>
+ * 0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision<br>
+ * To reverse the grouping (,) and decimal (.) for international numbers, add /i to the end.
+ * For example: 0.000,00/i
+ * </div></div>
+ * @param {Number} v The number to format.
+ * @param {String} format The way you would like to format this text.
+ * @return {String} The formatted number.
+ */
+ number: function(v, format) {
+ if(!format){
+ return v;
+ }
+ v = Ext.num(v, NaN);
+ if (isNaN(v)){
+ return '';
+ }
+ var comma = ',',
+ dec = '.',
+ i18n = false,
+ neg = v < 0;
+
+ v = Math.abs(v);
+ if(format.substr(format.length - 2) == '/i'){
+ format = format.substr(0, format.length - 2);
+ i18n = true;
+ comma = '.';
+ dec = ',';
+ }
+
+ var hasComma = format.indexOf(comma) != -1,
+ psplit = (i18n ? format.replace(/[^\d\,]/g, '') : format.replace(/[^\d\.]/g, '')).split(dec);
+
+ if(1 < psplit.length){
+ v = v.toFixed(psplit[1].length);
+ }else if(2 < psplit.length){
+ throw ('NumberFormatException: invalid format, formats should have no more than 1 period: ' + format);
+ }else{
+ v = v.toFixed(0);
+ }
+
+ var fnum = v.toString();
+
+ psplit = fnum.split('.');
+
+ if (hasComma) {
+ var cnum = psplit[0], parr = [], j = cnum.length, m = Math.floor(j / 3), n = cnum.length % 3 || 3;
+
+ for (var i = 0; i < j; i += n) {
+ if (i != 0) {
+ n = 3;
+ }
+ parr[parr.length] = cnum.substr(i, n);
+ m -= 1;
+ }
+ fnum = parr.join(comma);
+ if (psplit[1]) {
+ fnum += dec + psplit[1];
+ }
+ } else {
+ if (psplit[1]) {
+ fnum = psplit[0] + dec + psplit[1];
+ }
+ }
+
+ return (neg ? '-' : '') + format.replace(/[\d,?\.?]+/, fnum);
+ },
+
+ /**
+ * Returns a number rendering function that can be reused to apply a number format multiple times efficiently
+ * @param {String} format Any valid number format string for {@link #number}
+ * @return {Function} The number formatting function
+ */
+ numberRenderer : function(format){
+ return function(v){
+ return Ext.util.Format.number(v, format);
+ };
+ },
+
+ /**
+ * Selectively do a plural form of a word based on a numeric value. For example, in a template,
+ * {commentCount:plural("Comment")} would result in "1 Comment" if commentCount was 1 or would be "x Comments"
+ * if the value is 0 or greater than 1.
+ * @param {Number} value The value to compare against
+ * @param {String} singular The singular form of the word
+ * @param {String} plural (optional) The plural form of the word (defaults to the singular with an "s")
+ */
+ plural : function(v, s, p){
+ return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
+ },
+
+ /**
+ * Converts newline characters to the HTML tag <br/>
+ * @param {String} The string value to format.
+ * @return {String} The string with embedded <br/> tags in place of newlines.
+ */
+ nl2br : function(v){
+ return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
+ }
+ }
+}();
+/**
+ * @class Ext.XTemplate
+ * @extends Ext.Template
+ * <p>A template class that supports advanced functionality like:<div class="mdetail-params"><ul>
+ * <li>Autofilling arrays using templates and sub-templates</li>
+ * <li>Conditional processing with basic comparison operators</li>
+ * <li>Basic math function support</li>
+ * <li>Execute arbitrary inline code with special built-in template variables</li>
+ * <li>Custom member functions</li>
+ * <li>Many special tags and built-in operators that aren't defined as part of
+ * the API, but are supported in the templates that can be created</li>
+ * </ul></div></p>
+ * <p>XTemplate provides the templating mechanism built into:<div class="mdetail-params"><ul>
+ * <li>{@link Ext.DataView}</li>
+ * <li>{@link Ext.ListView}</li>
+ * <li>{@link Ext.form.ComboBox}</li>
+ * <li>{@link Ext.grid.TemplateColumn}</li>
+ * <li>{@link Ext.grid.GroupingView}</li>
+ * <li>{@link Ext.menu.Item}</li>
+ * <li>{@link Ext.layout.MenuLayout}</li>
+ * <li>{@link Ext.ColorPalette}</li>
+ * </ul></div></p>
+ *
+ * <p>For example usage {@link #XTemplate see the constructor}.</p>
+ *
+ * @constructor
+ * The {@link Ext.Template#Template Ext.Template constructor} describes
+ * the acceptable parameters to pass to the constructor. The following
+ * examples demonstrate all of the supported features.</p>
+ *
+ * <div class="mdetail-params"><ul>
+ *
+ * <li><b><u>Sample Data</u></b>
+ * <div class="sub-desc">
+ * <p>This is the data object used for reference in each code example:</p>
+ * <pre><code>
+var data = {
+ name: 'Jack Slocum',
+ title: 'Lead Developer',
+ company: 'Ext JS, LLC',
+ email: 'jack@extjs.com',
+ address: '4 Red Bulls Drive',
+ city: 'Cleveland',
+ state: 'Ohio',
+ zip: '44102',
+ drinks: ['Red Bull', 'Coffee', 'Water'],
+ kids: [{
+ name: 'Sara Grace',
+ age:3
+ },{
+ name: 'Zachary',
+ age:2
+ },{
+ name: 'John James',
+ age:0
+ }]
+};
+ * </code></pre>
+ * </div>
+ * </li>
+ *
+ *
+ * <li><b><u>Auto filling of arrays</u></b>
+ * <div class="sub-desc">
+ * <p>The <b><tt>tpl</tt></b> tag and the <b><tt>for</tt></b> operator are used
+ * to process the provided data object:
+ * <ul>
+ * <li>If the value specified in <tt>for</tt> is an array, it will auto-fill,
+ * repeating the template block inside the <tt>tpl</tt> tag for each item in the
+ * array.</li>
+ * <li>If <tt>for="."</tt> is specified, the data object provided is examined.</li>
+ * <li>While processing an array, the special variable <tt>{#}</tt>
+ * will provide the current array index + 1 (starts at 1, not 0).</li>
+ * </ul>
+ * </p>
+ * <pre><code>
+<tpl <b>for</b>=".">...</tpl> // loop through array at root node
+<tpl <b>for</b>="foo">...</tpl> // loop through array at foo node
+<tpl <b>for</b>="foo.bar">...</tpl> // loop through array at foo.bar node
+ * </code></pre>
+ * Using the sample data above:
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Kids: ',
+ '<tpl <b>for</b>=".">', // process the data.kids node
+ '<p>{#}. {name}</p>', // use current array index to autonumber
+ '</tpl></p>'
+);
+tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
+ * </code></pre>
+ * <p>An example illustrating how the <b><tt>for</tt></b> property can be leveraged
+ * to access specified members of the provided data object to populate the template:</p>
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Name: {name}</p>',
+ '<p>Title: {title}</p>',
+ '<p>Company: {company}</p>',
+ '<p>Kids: ',
+ '<tpl <b>for="kids"</b>>', // interrogate the kids property within the data
+ '<p>{name}</p>',
+ '</tpl></p>'
+);
+tpl.overwrite(panel.body, data); // pass the root node of the data object
+ * </code></pre>
+ * <p>Flat arrays that contain values (and not objects) can be auto-rendered
+ * using the special <b><tt>{.}</tt></b> variable inside a loop. This variable
+ * will represent the value of the array at the current index:</p>
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>{name}\'s favorite beverages:</p>',
+ '<tpl for="drinks">',
+ '<div> - {.}</div>',
+ '</tpl>'
+);
+tpl.overwrite(panel.body, data);
+ * </code></pre>
+ * <p>When processing a sub-template, for example while looping through a child array,
+ * you can access the parent object's members via the <b><tt>parent</tt></b> object:</p>
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Name: {name}</p>',
+ '<p>Kids: ',
+ '<tpl for="kids">',
+ '<tpl if="age > 1">',
+ '<p>{name}</p>',
+ '<p>Dad: {<b>parent</b>.name}</p>',
+ '</tpl>',
+ '</tpl></p>'
+);
+tpl.overwrite(panel.body, data);
+ * </code></pre>
+ * </div>
+ * </li>
+ *
+ *
+ * <li><b><u>Conditional processing with basic comparison operators</u></b>
+ * <div class="sub-desc">
+ * <p>The <b><tt>tpl</tt></b> tag and the <b><tt>if</tt></b> operator are used
+ * to provide conditional checks for deciding whether or not to render specific
+ * parts of the template. Notes:<div class="sub-desc"><ul>
+ * <li>Double quotes must be encoded if used within the conditional</li>
+ * <li>There is no <tt>else</tt> operator — if needed, two opposite
+ * <tt>if</tt> statements should be used.</li>
+ * </ul></div>
+ * <pre><code>
+<tpl if="age > 1 && age < 10">Child</tpl>
+<tpl if="age >= 10 && age < 18">Teenager</tpl>
+<tpl <b>if</b>="this.isGirl(name)">...</tpl>
+<tpl <b>if</b>="id==\'download\'">...</tpl>
+<tpl <b>if</b>="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
+// no good:
+<tpl if="name == "Jack"">Hello</tpl>
+// encode " if it is part of the condition, e.g.
+<tpl if="name == &quot;Jack&quot;">Hello</tpl>
+ * </code></pre>
+ * Using the sample data above:
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Name: {name}</p>',
+ '<p>Kids: ',
+ '<tpl for="kids">',
+ '<tpl if="age > 1">',
+ '<p>{name}</p>',
+ '</tpl>',
+ '</tpl></p>'
+);
+tpl.overwrite(panel.body, data);
+ * </code></pre>
+ * </div>
+ * </li>
+ *
+ *
+ * <li><b><u>Basic math support</u></b>
+ * <div class="sub-desc">
+ * <p>The following basic math operators may be applied directly on numeric
+ * data values:</p><pre>
+ * + - * /
+ * </pre>
+ * For example:
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Name: {name}</p>',
+ '<p>Kids: ',
+ '<tpl for="kids">',
+ '<tpl if="age &gt; 1">', // <-- Note that the > is encoded
+ '<p>{#}: {name}</p>', // <-- Auto-number each item
+ '<p>In 5 Years: {age+5}</p>', // <-- Basic math
+ '<p>Dad: {parent.name}</p>',
+ '</tpl>',
+ '</tpl></p>'
+);
+tpl.overwrite(panel.body, data);
+</code></pre>
+ * </div>
+ * </li>
+ *
+ *
+ * <li><b><u>Execute arbitrary inline code with special built-in template variables</u></b>
+ * <div class="sub-desc">
+ * <p>Anything between <code>{[ ... ]}</code> is considered code to be executed
+ * in the scope of the template. There are some special variables available in that code:
+ * <ul>
+ * <li><b><tt>values</tt></b>: The values in the current scope. If you are using
+ * scope changing sub-templates, you can change what <tt>values</tt> is.</li>
+ * <li><b><tt>parent</tt></b>: The scope (values) of the ancestor template.</li>
+ * <li><b><tt>xindex</tt></b>: If you are in a looping template, the index of the
+ * loop you are in (1-based).</li>
+ * <li><b><tt>xcount</tt></b>: If you are in a looping template, the total length
+ * of the array you are looping.</li>
+ * <li><b><tt>fm</tt></b>: An alias for <tt>Ext.util.Format</tt>.</li>
+ * </ul>
+ * This example demonstrates basic row striping using an inline code block and the
+ * <tt>xindex</tt> variable:</p>
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Name: {name}</p>',
+ '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
+ '<p>Kids: ',
+ '<tpl for="kids">',
+ '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
+ '{name}',
+ '</div>',
+ '</tpl></p>'
+);
+tpl.overwrite(panel.body, data);
+ * </code></pre>
+ * </div>
+ * </li>
+ *
+ * <li><b><u>Template member functions</u></b>
+ * <div class="sub-desc">
+ * <p>One or more member functions can be specified in a configuration
+ * object passed into the XTemplate constructor for more complex processing:</p>
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Name: {name}</p>',
+ '<p>Kids: ',
+ '<tpl for="kids">',
+ '<tpl if="this.isGirl(name)">',
+ '<p>Girl: {name} - {age}</p>',
+ '</tpl>',
+ // use opposite if statement to simulate 'else' processing:
+ '<tpl if="this.isGirl(name) == false">',
+ '<p>Boy: {name} - {age}</p>',
+ '</tpl>',
+ '<tpl if="this.isBaby(age)">',
+ '<p>{name} is a baby!</p>',
+ '</tpl>',
+ '</tpl></p>',
+ {
+ // XTemplate configuration:
+ compiled: true,
+ disableFormats: true,
+ // member functions:
+ isGirl: function(name){
+ return name == 'Sara Grace';
+ },
+ isBaby: function(age){
+ return age < 1;
+ }
+ }
+);
+tpl.overwrite(panel.body, data);
+ * </code></pre>
+ * </div>
+ * </li>
+ *
+ * </ul></div>
+ *
+ * @param {Mixed} config
+ */
+Ext.XTemplate = function(){
+ Ext.XTemplate.superclass.constructor.apply(this, arguments);
+
+ var me = this,
+ s = me.html,
+ re = /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
+ nameRe = /^<tpl\b[^>]*?for="(.*?)"/,
+ ifRe = /^<tpl\b[^>]*?if="(.*?)"/,
+ execRe = /^<tpl\b[^>]*?exec="(.*?)"/,
+ m,
+ id = 0,
+ tpls = [],
+ VALUES = 'values',
+ PARENT = 'parent',
+ XINDEX = 'xindex',
+ XCOUNT = 'xcount',
+ RETURN = 'return ',
+ WITHVALUES = 'with(values){ ';
+
+ s = ['<tpl>', s, '</tpl>'].join('');
+
+ while((m = s.match(re))){
+ var m2 = m[0].match(nameRe),
+ m3 = m[0].match(ifRe),
+ m4 = m[0].match(execRe),
+ exp = null,
+ fn = null,
+ exec = null,
+ name = m2 && m2[1] ? m2[1] : '';
+
+ if (m3) {
+ exp = m3 && m3[1] ? m3[1] : null;
+ if(exp){
+ fn = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + RETURN +(Ext.util.Format.htmlDecode(exp))+'; }');
+ }
+ }
+ if (m4) {
+ exp = m4 && m4[1] ? m4[1] : null;
+ if(exp){
+ exec = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES +(Ext.util.Format.htmlDecode(exp))+'; }');
+ }
+ }
+ if(name){
+ switch(name){
+ case '.': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + VALUES + '; }'); break;
+ case '..': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + PARENT + '; }'); break;
+ default: name = new Function(VALUES, PARENT, WITHVALUES + RETURN + name + '; }');
+ }
+ }
+ tpls.push({
+ id: id,
+ target: name,
+ exec: exec,
+ test: fn,
+ body: m[1]||''
+ });
+ s = s.replace(m[0], '{xtpl'+ id + '}');
+ ++id;
+ }
+ Ext.each(tpls, function(t) {
+ me.compileTpl(t);
+ });
+ me.master = tpls[tpls.length-1];
+ me.tpls = tpls;
+};
+Ext.extend(Ext.XTemplate, Ext.Template, {
+ // private
+ re : /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\\]\s?[\d\.\+\-\*\\\(\)]+)?\}/g,
+ // private
+ codeRe : /\{\[((?:\\\]|.|\n)*?)\]\}/g,
+
+ // private
+ applySubTemplate : function(id, values, parent, xindex, xcount){
+ var me = this,
+ len,
+ t = me.tpls[id],
+ vs,
+ buf = [];
+ if ((t.test && !t.test.call(me, values, parent, xindex, xcount)) ||
+ (t.exec && t.exec.call(me, values, parent, xindex, xcount))) {
+ return '';
+ }
+ vs = t.target ? t.target.call(me, values, parent) : values;
+ len = vs.length;
+ parent = t.target ? values : parent;
+ if(t.target && Ext.isArray(vs)){
+ Ext.each(vs, function(v, i) {
+ buf[buf.length] = t.compiled.call(me, v, parent, i+1, len);
+ });
+ return buf.join('');
+ }
+ return t.compiled.call(me, vs, parent, xindex, xcount);
+ },
+
+ // private
+ compileTpl : function(tpl){
+ var fm = Ext.util.Format,
+ useF = this.disableFormats !== true,
+ sep = Ext.isGecko ? "+" : ",",
+ body;
+
+ function fn(m, name, format, args, math){
+ if(name.substr(0, 4) == 'xtpl'){
+ return "'"+ sep +'this.applySubTemplate('+name.substr(4)+', values, parent, xindex, xcount)'+sep+"'";
+ }
+ var v;
+ if(name === '.'){
+ v = 'values';
+ }else if(name === '#'){
+ v = 'xindex';
+ }else if(name.indexOf('.') != -1){
+ v = name;
+ }else{
+ v = "values['" + name + "']";
+ }
+ if(math){
+ v = '(' + v + math + ')';
+ }
+ if (format && useF) {
+ args = args ? ',' + args : "";
+ if(format.substr(0, 5) != "this."){
+ format = "fm." + format + '(';
+ }else{
+ format = 'this.call("'+ format.substr(5) + '", ';
+ args = ", values";
+ }
+ } else {
+ args= ''; format = "("+v+" === undefined ? '' : ";
+ }
+ return "'"+ sep + format + v + args + ")"+sep+"'";
+ }
+
+ function codeFn(m, code){
+ // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
+ return "'" + sep + '(' + code.replace(/\\'/g, "'") + ')' + sep + "'";
+ }
+
+ // branched to use + in gecko and [].join() in others
+ if(Ext.isGecko){
+ body = "tpl.compiled = function(values, parent, xindex, xcount){ return '" +
+ tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn) +
+ "';};";
+ }else{
+ body = ["tpl.compiled = function(values, parent, xindex, xcount){ return ['"];
+ body.push(tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn));
+ body.push("'].join('');};");
+ body = body.join('');
+ }
+ eval(body);
+ return this;
+ },
+
+ /**
+ * Returns an HTML fragment of this template with the specified values applied.
+ * @param {Object} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @return {String} The HTML fragment
+ */
+ applyTemplate : function(values){
+ return this.master.compiled.call(this, values, {}, 1, 1);
+ },
+
+ /**
+ * Compile the template to a function for optimized performance. Recommended if the template will be used frequently.
+ * @return {Function} The compiled function
+ */
+ compile : function(){return this;}
+
+ /**
+ * @property re
+ * @hide
+ */
+ /**
+ * @property disableFormats
+ * @hide
+ */
+ /**
+ * @method set
+ * @hide
+ */
+
+});
+/**
+ * Alias for {@link #applyTemplate}
+ * Returns an HTML fragment of this template with the specified values applied.
+ * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @return {String} The HTML fragment
+ * @member Ext.XTemplate
+ * @method apply
+ */
+Ext.XTemplate.prototype.apply = Ext.XTemplate.prototype.applyTemplate;
+
+/**
+ * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
+ * @param {String/HTMLElement} el A DOM element or its id
+ * @return {Ext.Template} The created template
+ * @static
+ */
+Ext.XTemplate.from = function(el){
+ el = Ext.getDom(el);
+ return new Ext.XTemplate(el.value || el.innerHTML);
+};/**
+ * @class Ext.util.CSS
+ * Utility class for manipulating CSS rules
+ * @singleton
+ */
+Ext.util.CSS = function(){
+ var rules = null;
+ var doc = document;
+
+ var camelRe = /(-[a-z])/gi;
+ var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
+
+ return {
+ /**
+ * Creates a stylesheet from a text blob of rules.
+ * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
+ * @param {String} cssText The text containing the css rules
+ * @param {String} id An id to add to the stylesheet for later removal
+ * @return {StyleSheet}
+ */
+ createStyleSheet : function(cssText, id){
+ var ss;
+ var head = doc.getElementsByTagName("head")[0];
+ var rules = doc.createElement("style");
+ rules.setAttribute("type", "text/css");
+ if(id){
+ rules.setAttribute("id", id);
+ }
+ if(Ext.isIE){
+ head.appendChild(rules);
+ ss = rules.styleSheet;
+ ss.cssText = cssText;
+ }else{
+ try{
+ rules.appendChild(doc.createTextNode(cssText));
+ }catch(e){
+ rules.cssText = cssText;
+ }
+ head.appendChild(rules);
+ ss = rules.styleSheet ? rules.styleSheet : (rules.sheet || doc.styleSheets[doc.styleSheets.length-1]);
+ }
+ this.cacheStyleSheet(ss);
+ return ss;
+ },
+
+ /**
+ * Removes a style or link tag by id
+ * @param {String} id The id of the tag
+ */
+ removeStyleSheet : function(id){
+ var existing = doc.getElementById(id);
+ if(existing){
+ existing.parentNode.removeChild(existing);
+ }
+ },
+
+ /**
+ * Dynamically swaps an existing stylesheet reference for a new one
+ * @param {String} id The id of an existing link tag to remove
+ * @param {String} url The href of the new stylesheet to include
+ */
+ swapStyleSheet : function(id, url){
+ this.removeStyleSheet(id);
+ var ss = doc.createElement("link");
+ ss.setAttribute("rel", "stylesheet");
+ ss.setAttribute("type", "text/css");
+ ss.setAttribute("id", id);
+ ss.setAttribute("href", url);
+ doc.getElementsByTagName("head")[0].appendChild(ss);
+ },
+
+ /**
+ * Refresh the rule cache if you have dynamically added stylesheets
+ * @return {Object} An object (hash) of rules indexed by selector
+ */
+ refreshCache : function(){
+ return this.getRules(true);
+ },
+
+ // private
+ cacheStyleSheet : function(ss){
+ if(!rules){
+ rules = {};
+ }
+ try{// try catch for cross domain access issue
+ var ssRules = ss.cssRules || ss.rules;
+ for(var j = ssRules.length-1; j >= 0; --j){
+ rules[ssRules[j].selectorText.toLowerCase()] = ssRules[j];
+ }
+ }catch(e){}
+ },
+
+ /**
+ * Gets all css rules for the document
+ * @param {Boolean} refreshCache true to refresh the internal cache
+ * @return {Object} An object (hash) of rules indexed by selector
+ */
+ getRules : function(refreshCache){
+ if(rules === null || refreshCache){
+ rules = {};
+ var ds = doc.styleSheets;
+ for(var i =0, len = ds.length; i < len; i++){
+ try{
+ this.cacheStyleSheet(ds[i]);
+ }catch(e){}
+ }
+ }
+ return rules;
+ },
+
+ /**
+ * Gets an an individual CSS rule by selector(s)
+ * @param {String/Array} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
+ * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
+ * @return {CSSRule} The CSS rule or null if one is not found
+ */
+ getRule : function(selector, refreshCache){
+ var rs = this.getRules(refreshCache);
+ if(!Ext.isArray(selector)){
+ return rs[selector.toLowerCase()];
+ }
+ for(var i = 0; i < selector.length; i++){
+ if(rs[selector[i]]){
+ return rs[selector[i].toLowerCase()];
+ }
+ }
+ return null;
+ },
+
+
+ /**
+ * Updates a rule property
+ * @param {String/Array} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
+ * @param {String} property The css property
+ * @param {String} value The new value for the property
+ * @return {Boolean} true If a rule was found and updated
+ */
+ updateRule : function(selector, property, value){
+ if(!Ext.isArray(selector)){
+ var rule = this.getRule(selector);
+ if(rule){
+ rule.style[property.replace(camelRe, camelFn)] = value;
+ return true;
+ }
+ }else{
+ for(var i = 0; i < selector.length; i++){
+ if(this.updateRule(selector[i], property, value)){
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ };
+}();/**
+ @class Ext.util.ClickRepeater
+ @extends Ext.util.Observable
+
+ A wrapper class which can be applied to any element. Fires a "click" event while the
+ mouse is pressed. The interval between firings may be specified in the config but
+ defaults to 20 milliseconds.
+
+ Optionally, a CSS class may be applied to the element during the time it is pressed.
+
+ @cfg {Mixed} el The element to act as a button.
+ @cfg {Number} delay The initial delay before the repeating event begins firing.
+ Similar to an autorepeat key delay.
+ @cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
+ @cfg {String} pressClass A CSS class name to be applied to the element while pressed.
+ @cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
+ "interval" and "delay" are ignored.
+ @cfg {Boolean} preventDefault True to prevent the default click event
+ @cfg {Boolean} stopDefault True to stop the default click event
+
+ @history
+ 2007-02-02 jvs Original code contributed by Nige "Animal" White
+ 2007-02-02 jvs Renamed to ClickRepeater
+ 2007-02-03 jvs Modifications for FF Mac and Safari
+
+ @constructor
+ @param {Mixed} el The element to listen on
+ @param {Object} config
+ */
+Ext.util.ClickRepeater = function(el, config)
+{
+ this.el = Ext.get(el);
+ this.el.unselectable();
+
+ Ext.apply(this, config);
+
+ this.addEvents(
+ /**
+ * @event mousedown
+ * Fires when the mouse button is depressed.
+ * @param {Ext.util.ClickRepeater} this
+ */
+ "mousedown",
+ /**
+ * @event click
+ * Fires on a specified interval during the time the element is pressed.
+ * @param {Ext.util.ClickRepeater} this
+ */
+ "click",
+ /**
+ * @event mouseup
+ * Fires when the mouse key is released.
+ * @param {Ext.util.ClickRepeater} this
+ */
+ "mouseup"
+ );
+
+ if(!this.disabled){
+ this.disabled = true;
+ this.enable();
+ }
+
+ // allow inline handler
+ if(this.handler){
+ this.on("click", this.handler, this.scope || this);
+ }
+
+ Ext.util.ClickRepeater.superclass.constructor.call(this);
+};
+
+Ext.extend(Ext.util.ClickRepeater, Ext.util.Observable, {
+ interval : 20,
+ delay: 250,
+ preventDefault : true,
+ stopDefault : false,
+ timer : 0,
+
+ /**
+ * Enables the repeater and allows events to fire.
+ */
+ enable: function(){
+ if(this.disabled){
+ this.el.on('mousedown', this.handleMouseDown, this);
+ if (Ext.isIE){
+ this.el.on('dblclick', this.handleDblClick, this);
+ }
+ if(this.preventDefault || this.stopDefault){
+ this.el.on('click', this.eventOptions, this);
+ }
+ }
+ this.disabled = false;
+ },
+
+ /**
+ * Disables the repeater and stops events from firing.
+ */
+ disable: function(/* private */ force){
+ if(force || !this.disabled){
+ clearTimeout(this.timer);
+ if(this.pressClass){
+ this.el.removeClass(this.pressClass);
+ }
+ Ext.getDoc().un('mouseup', this.handleMouseUp, this);
+ this.el.removeAllListeners();
+ }
+ this.disabled = true;
+ },
+
+ /**
+ * Convenience function for setting disabled/enabled by boolean.
+ * @param {Boolean} disabled
+ */
+ setDisabled: function(disabled){
+ this[disabled ? 'disable' : 'enable']();
+ },
+
+ eventOptions: function(e){
+ if(this.preventDefault){
+ e.preventDefault();
+ }
+ if(this.stopDefault){
+ e.stopEvent();
+ }
+ },
+
+ // private
+ destroy : function() {
+ this.disable(true);
+ Ext.destroy(this.el);
+ this.purgeListeners();
+ },
+
+ handleDblClick : function(){
+ clearTimeout(this.timer);
+ this.el.blur();
+
+ this.fireEvent("mousedown", this);
+ this.fireEvent("click", this);
+ },
+
+ // private
+ handleMouseDown : function(){
+ clearTimeout(this.timer);
+ this.el.blur();
+ if(this.pressClass){
+ this.el.addClass(this.pressClass);
+ }
+ this.mousedownTime = new Date();
+
+ Ext.getDoc().on("mouseup", this.handleMouseUp, this);
+ this.el.on("mouseout", this.handleMouseOut, this);
+
+ this.fireEvent("mousedown", this);
+ this.fireEvent("click", this);
+
+ // Do not honor delay or interval if acceleration wanted.
+ if (this.accelerate) {
+ this.delay = 400;
+ }
+ this.timer = this.click.defer(this.delay || this.interval, this);
+ },
+
+ // private
+ click : function(){
+ this.fireEvent("click", this);
+ this.timer = this.click.defer(this.accelerate ?
+ this.easeOutExpo(this.mousedownTime.getElapsed(),
+ 400,
+ -390,
+ 12000) :
+ this.interval, this);
+ },
+
+ easeOutExpo : function (t, b, c, d) {
+ return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
+ },
+
+ // private
+ handleMouseOut : function(){
+ clearTimeout(this.timer);
+ if(this.pressClass){
+ this.el.removeClass(this.pressClass);
+ }
+ this.el.on("mouseover", this.handleMouseReturn, this);
+ },
+
+ // private
+ handleMouseReturn : function(){
+ this.el.un("mouseover", this.handleMouseReturn, this);
+ if(this.pressClass){
+ this.el.addClass(this.pressClass);
+ }
+ this.click();
+ },
+
+ // private
+ handleMouseUp : function(){
+ clearTimeout(this.timer);
+ this.el.un("mouseover", this.handleMouseReturn, this);
+ this.el.un("mouseout", this.handleMouseOut, this);
+ Ext.getDoc().un("mouseup", this.handleMouseUp, this);
+ this.el.removeClass(this.pressClass);
+ this.fireEvent("mouseup", this);
+ }
+});/**
+ * @class Ext.KeyNav
+ * <p>Provides a convenient wrapper for normalized keyboard navigation. KeyNav allows you to bind
+ * navigation keys to function calls that will get called when the keys are pressed, providing an easy
+ * way to implement custom navigation schemes for any UI component.</p>
+ * <p>The following are all of the possible keys that can be implemented: enter, left, right, up, down, tab, esc,
+ * pageUp, pageDown, del, home, end. Usage:</p>
+ <pre><code>
+var nav = new Ext.KeyNav("my-element", {
+ "left" : function(e){
+ this.moveLeft(e.ctrlKey);
+ },
+ "right" : function(e){
+ this.moveRight(e.ctrlKey);
+ },
+ "enter" : function(e){
+ this.save();
+ },
+ scope : this
+});
+</code></pre>
+ * @constructor
+ * @param {Mixed} el The element to bind to
+ * @param {Object} config The config
+ */
+Ext.KeyNav = function(el, config){
+ this.el = Ext.get(el);
+ Ext.apply(this, config);
+ if(!this.disabled){
+ this.disabled = true;
+ this.enable();
+ }
+};
+
+Ext.KeyNav.prototype = {
+ /**
+ * @cfg {Boolean} disabled
+ * True to disable this KeyNav instance (defaults to false)
+ */
+ disabled : false,
+ /**
+ * @cfg {String} defaultEventAction
+ * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key. Valid values are
+ * {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
+ * {@link Ext.EventObject#stopPropagation} (defaults to 'stopEvent')
+ */
+ defaultEventAction: "stopEvent",
+ /**
+ * @cfg {Boolean} forceKeyDown
+ * Handle the keydown event instead of keypress (defaults to false). KeyNav automatically does this for IE since
+ * IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
+ * handle keydown instead of keypress.
+ */
+ forceKeyDown : false,
+
+ // private
+ relay : function(e){
+ var k = e.getKey();
+ var h = this.keyToHandler[k];
+ if(h && this[h]){
+ if(this.doRelay(e, this[h], h) !== true){
+ e[this.defaultEventAction]();
+ }
+ }
+ },
+
+ // private
+ doRelay : function(e, h, hname){
+ return h.call(this.scope || this, e);
+ },
+
+ // possible handlers
+ enter : false,
+ left : false,
+ right : false,
+ up : false,
+ down : false,
+ tab : false,
+ esc : false,
+ pageUp : false,
+ pageDown : false,
+ del : false,
+ home : false,
+ end : false,
+
+ // quick lookup hash
+ keyToHandler : {
+ 37 : "left",
+ 39 : "right",
+ 38 : "up",
+ 40 : "down",
+ 33 : "pageUp",
+ 34 : "pageDown",
+ 46 : "del",
+ 36 : "home",
+ 35 : "end",
+ 13 : "enter",
+ 27 : "esc",
+ 9 : "tab"
+ },
+
+ stopKeyUp: function(e) {
+ var k = e.getKey();
+
+ if (k >= 37 && k <= 40) {
+ // *** bugfix - safari 2.x fires 2 keyup events on cursor keys
+ // *** (note: this bugfix sacrifices the "keyup" event originating from keyNav elements in Safari 2)
+ e.stopEvent();
+ }
+ },
+
+ /**
+ * Destroy this KeyNav (this is the same as calling disable).
+ */
+ destroy: function(){
+ this.disable();
+ },
+
+ /**
+ * Enable this KeyNav
+ */
+ enable: function() {
+ if (this.disabled) {
+ if (Ext.isSafari2) {
+ // call stopKeyUp() on "keyup" event
+ this.el.on('keyup', this.stopKeyUp, this);
+ }
+
+ this.el.on(this.isKeydown()? 'keydown' : 'keypress', this.relay, this);
+ this.disabled = false;
+ }
+ },
+
+ /**
+ * Disable this KeyNav
+ */
+ disable: function() {
+ if (!this.disabled) {
+ if (Ext.isSafari2) {
+ // remove "keyup" event handler
+ this.el.un('keyup', this.stopKeyUp, this);
+ }
+
+ this.el.un(this.isKeydown()? 'keydown' : 'keypress', this.relay, this);
+ this.disabled = true;
+ }
+ },
+
+ /**
+ * Convenience function for setting disabled/enabled by boolean.
+ * @param {Boolean} disabled
+ */
+ setDisabled : function(disabled){
+ this[disabled ? "disable" : "enable"]();
+ },
+
+ // private
+ isKeydown: function(){
+ return this.forceKeyDown || Ext.EventManager.useKeydown;
+ }
+};
+/**
+ * @class Ext.KeyMap
+ * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
+ * The constructor accepts the same config object as defined by {@link #addBinding}.
+ * If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
+ * combination it will call the function with this signature (if the match is a multi-key
+ * combination the callback will still be called only once): (String key, Ext.EventObject e)
+ * A KeyMap can also handle a string representation of keys.<br />
+ * Usage:
+ <pre><code>
+// map one key by key code
+var map = new Ext.KeyMap("my-element", {
+ key: 13, // or Ext.EventObject.ENTER
+ fn: myHandler,
+ scope: myObject
+});
+
+// map multiple keys to one action by string
+var map = new Ext.KeyMap("my-element", {
+ key: "a\r\n\t",
+ fn: myHandler,
+ scope: myObject
+});
+
+// map multiple keys to multiple actions by strings and array of codes
+var map = new Ext.KeyMap("my-element", [
+ {
+ key: [10,13],
+ fn: function(){ alert("Return was pressed"); }
+ }, {
+ key: "abc",
+ fn: function(){ alert('a, b or c was pressed'); }
+ }, {
+ key: "\t",
+ ctrl:true,
+ shift:true,
+ fn: function(){ alert('Control + shift + tab was pressed.'); }
+ }
+]);
+</code></pre>
+ * <b>Note: A KeyMap starts enabled</b>
+ * @constructor
+ * @param {Mixed} el The element to bind to
+ * @param {Object} config The config (see {@link #addBinding})
+ * @param {String} eventName (optional) The event to bind to (defaults to "keydown")
+ */
+Ext.KeyMap = function(el, config, eventName){
+ this.el = Ext.get(el);
+ this.eventName = eventName || "keydown";
+ this.bindings = [];
+ if(config){
+ this.addBinding(config);
+ }
+ this.enable();
+};
+
+Ext.KeyMap.prototype = {
+ /**
+ * True to stop the event from bubbling and prevent the default browser action if the
+ * key was handled by the KeyMap (defaults to false)
+ * @type Boolean
+ */
+ stopEvent : false,
+
+ /**
+ * Add a new binding to this KeyMap. The following config object properties are supported:
+ * <pre>
+Property Type Description
+---------- --------------- ----------------------------------------------------------------------
+key String/Array A single keycode or an array of keycodes to handle
+shift Boolean True to handle key only when shift is pressed, False to handle the key only when shift is not pressed (defaults to undefined)
+ctrl Boolean True to handle key only when ctrl is pressed, False to handle the key only when ctrl is not pressed (defaults to undefined)
+alt Boolean True to handle key only when alt is pressed, False to handle the key only when alt is not pressed (defaults to undefined)
+handler Function The function to call when KeyMap finds the expected key combination
+fn Function Alias of handler (for backwards-compatibility)
+scope Object The scope of the callback function
+stopEvent Boolean True to stop the event from bubbling and prevent the default browser action if the key was handled by the KeyMap (defaults to false)
+</pre>
+ *
+ * Usage:
+ * <pre><code>
+// Create a KeyMap
+var map = new Ext.KeyMap(document, {
+ key: Ext.EventObject.ENTER,
+ fn: handleKey,
+ scope: this
+});
+
+//Add a new binding to the existing KeyMap later
+map.addBinding({
+ key: 'abc',
+ shift: true,
+ fn: handleKey,
+ scope: this
+});
+</code></pre>
+ * @param {Object/Array} config A single KeyMap config or an array of configs
+ */
+ addBinding : function(config){
+ if(Ext.isArray(config)){
+ Ext.each(config, function(c){
+ this.addBinding(c);
+ }, this);
+ return;
+ }
+ var keyCode = config.key,
+ fn = config.fn || config.handler,
+ scope = config.scope;
+
+ if (config.stopEvent) {
+ this.stopEvent = config.stopEvent;
+ }
+
+ if(typeof keyCode == "string"){
+ var ks = [];
+ var keyString = keyCode.toUpperCase();
+ for(var j = 0, len = keyString.length; j < len; j++){
+ ks.push(keyString.charCodeAt(j));
+ }
+ keyCode = ks;
+ }
+ var keyArray = Ext.isArray(keyCode);
+
+ var handler = function(e){
+ if(this.checkModifiers(config, e)){
+ var k = e.getKey();
+ if(keyArray){
+ for(var i = 0, len = keyCode.length; i < len; i++){
+ if(keyCode[i] == k){
+ if(this.stopEvent){
+ e.stopEvent();
+ }
+ fn.call(scope || window, k, e);
+ return;
+ }
+ }
+ }else{
+ if(k == keyCode){
+ if(this.stopEvent){
+ e.stopEvent();
+ }
+ fn.call(scope || window, k, e);
+ }
+ }
+ }
+ };
+ this.bindings.push(handler);
+ },
+
+ // private
+ checkModifiers: function(config, e){
+ var val, key, keys = ['shift', 'ctrl', 'alt'];
+ for (var i = 0, len = keys.length; i < len; ++i){
+ key = keys[i];
+ val = config[key];
+ if(!(val === undefined || (val === e[key + 'Key']))){
+ return false;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Shorthand for adding a single key listener
+ * @param {Number/Array/Object} key Either the numeric key code, array of key codes or an object with the
+ * following options:
+ * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
+ */
+ on : function(key, fn, scope){
+ var keyCode, shift, ctrl, alt;
+ if(typeof key == "object" && !Ext.isArray(key)){
+ keyCode = key.key;
+ shift = key.shift;
+ ctrl = key.ctrl;
+ alt = key.alt;
+ }else{
+ keyCode = key;
+ }
+ this.addBinding({
+ key: keyCode,
+ shift: shift,
+ ctrl: ctrl,
+ alt: alt,
+ fn: fn,
+ scope: scope
+ });
+ },
+
+ // private
+ handleKeyDown : function(e){
+ if(this.enabled){ //just in case
+ var b = this.bindings;
+ for(var i = 0, len = b.length; i < len; i++){
+ b[i].call(this, e);
+ }
+ }
+ },
+
+ /**
+ * Returns true if this KeyMap is enabled
+ * @return {Boolean}
+ */
+ isEnabled : function(){
+ return this.enabled;
+ },
+
+ /**
+ * Enables this KeyMap
+ */
+ enable: function(){
+ if(!this.enabled){
+ this.el.on(this.eventName, this.handleKeyDown, this);
+ this.enabled = true;
+ }
+ },
+
+ /**
+ * Disable this KeyMap
+ */
+ disable: function(){
+ if(this.enabled){
+ this.el.removeListener(this.eventName, this.handleKeyDown, this);
+ this.enabled = false;
+ }
+ },
+
+ /**
+ * Convenience function for setting disabled/enabled by boolean.
+ * @param {Boolean} disabled
+ */
+ setDisabled : function(disabled){
+ this[disabled ? "disable" : "enable"]();
+ }
+};/**
+ * @class Ext.util.TextMetrics
+ * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
+ * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
+ * should not contain any HTML, otherwise it may not be measured correctly.
+ * @singleton
+ */
+Ext.util.TextMetrics = function(){
+ var shared;
+ return {
+ /**
+ * Measures the size of the specified text
+ * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
+ * that can affect the size of the rendered text
+ * @param {String} text The text to measure
+ * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
+ * in order to accurately measure the text height
+ * @return {Object} An object containing the text's size {width: (width), height: (height)}
+ */
+ measure : function(el, text, fixedWidth){
+ if(!shared){
+ shared = Ext.util.TextMetrics.Instance(el, fixedWidth);
+ }
+ shared.bind(el);
+ shared.setFixedWidth(fixedWidth || 'auto');
+ return shared.getSize(text);
+ },
+
+ /**
+ * Return a unique TextMetrics instance that can be bound directly to an element and reused. This reduces
+ * the overhead of multiple calls to initialize the style properties on each measurement.
+ * @param {String/HTMLElement} el The element, dom node or id that the instance will be bound to
+ * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
+ * in order to accurately measure the text height
+ * @return {Ext.util.TextMetrics.Instance} instance The new instance
+ */
+ createInstance : function(el, fixedWidth){
+ return Ext.util.TextMetrics.Instance(el, fixedWidth);
+ }
+ };
+}();
+
+Ext.util.TextMetrics.Instance = function(bindTo, fixedWidth){
+ var ml = new Ext.Element(document.createElement('div'));
+ document.body.appendChild(ml.dom);
+ ml.position('absolute');
+ ml.setLeftTop(-1000, -1000);
+ ml.hide();
+
+ if(fixedWidth){
+ ml.setWidth(fixedWidth);
+ }
+
+ var instance = {
+ /**
+ * <p><b>Only available on the instance returned from {@link #createInstance}, <u>not</u> on the singleton.</b></p>
+ * Returns the size of the specified text based on the internal element's style and width properties
+ * @param {String} text The text to measure
+ * @return {Object} An object containing the text's size {width: (width), height: (height)}
+ */
+ getSize : function(text){
+ ml.update(text);
+ var s = ml.getSize();
+ ml.update('');
+ return s;
+ },
+
+ /**
+ * <p><b>Only available on the instance returned from {@link #createInstance}, <u>not</u> on the singleton.</b></p>
+ * Binds this TextMetrics instance to an element from which to copy existing CSS styles
+ * that can affect the size of the rendered text
+ * @param {String/HTMLElement} el The element, dom node or id
+ */
+ bind : function(el){
+ ml.setStyle(
+ Ext.fly(el).getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
+ );
+ },
+
+ /**
+ * <p><b>Only available on the instance returned from {@link #createInstance}, <u>not</u> on the singleton.</b></p>
+ * Sets a fixed width on the internal measurement element. If the text will be multiline, you have
+ * to set a fixed width in order to accurately measure the text height.
+ * @param {Number} width The width to set on the element
+ */
+ setFixedWidth : function(width){
+ ml.setWidth(width);
+ },
+
+ /**
+ * <p><b>Only available on the instance returned from {@link #createInstance}, <u>not</u> on the singleton.</b></p>
+ * Returns the measured width of the specified text
+ * @param {String} text The text to measure
+ * @return {Number} width The width in pixels
+ */
+ getWidth : function(text){
+ ml.dom.style.width = 'auto';
+ return this.getSize(text).width;
+ },
+
+ /**
+ * <p><b>Only available on the instance returned from {@link #createInstance}, <u>not</u> on the singleton.</b></p>
+ * Returns the measured height of the specified text. For multiline text, be sure to call
+ * {@link #setFixedWidth} if necessary.
+ * @param {String} text The text to measure
+ * @return {Number} height The height in pixels
+ */
+ getHeight : function(text){
+ return this.getSize(text).height;
+ }
+ };
+
+ instance.bind(bindTo);
+
+ return instance;
+};
+
+Ext.Element.addMethods({
+ /**
+ * Returns the width in pixels of the passed text, or the width of the text in this Element.
+ * @param {String} text The text to measure. Defaults to the innerHTML of the element.
+ * @param {Number} min (Optional) The minumum value to return.
+ * @param {Number} max (Optional) The maximum value to return.
+ * @return {Number} The text width in pixels.
+ * @member Ext.Element getTextWidth
+ */
+ getTextWidth : function(text, min, max){
+ return (Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width).constrain(min || 0, max || 1000000);
+ }
+});
+/**
+ * @class Ext.util.Cookies
+ * Utility class for managing and interacting with cookies.
+ * @singleton
+ */
+Ext.util.Cookies = {
+ /**
+ * Create a cookie with the specified name and value. Additional settings
+ * for the cookie may be optionally specified (for example: expiration,
+ * access restriction, SSL).
+ * @param {String} name The name of the cookie to set.
+ * @param {Mixed} value The value to set for the cookie.
+ * @param {Object} expires (Optional) Specify an expiration date the
+ * cookie is to persist until. Note that the specified Date object will
+ * be converted to Greenwich Mean Time (GMT).
+ * @param {String} path (Optional) Setting a path on the cookie restricts
+ * access to pages that match that path. Defaults to all pages (<tt>'/'</tt>).
+ * @param {String} domain (Optional) Setting a domain restricts access to
+ * pages on a given domain (typically used to allow cookie access across
+ * subdomains). For example, "extjs.com" will create a cookie that can be
+ * accessed from any subdomain of extjs.com, including www.extjs.com,
+ * support.extjs.com, etc.
+ * @param {Boolean} secure (Optional) Specify true to indicate that the cookie
+ * should only be accessible via SSL on a page using the HTTPS protocol.
+ * Defaults to <tt>false</tt>. Note that this will only work if the page
+ * calling this code uses the HTTPS protocol, otherwise the cookie will be
+ * created with default options.
+ */
+ set : function(name, value){
+ var argv = arguments;
+ var argc = arguments.length;
+ var expires = (argc > 2) ? argv[2] : null;
+ var path = (argc > 3) ? argv[3] : '/';
+ var domain = (argc > 4) ? argv[4] : null;
+ var secure = (argc > 5) ? argv[5] : false;
+ document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
+ },
+
+ /**
+ * Retrieves cookies that are accessible by the current page. If a cookie
+ * does not exist, <code>get()</code> returns <tt>null</tt>. The following
+ * example retrieves the cookie called "valid" and stores the String value
+ * in the variable <tt>validStatus</tt>.
+ * <pre><code>
+ * var validStatus = Ext.util.Cookies.get("valid");
+ * </code></pre>
+ * @param {String} name The name of the cookie to get
+ * @return {Mixed} Returns the cookie value for the specified name;
+ * null if the cookie name does not exist.
+ */
+ get : function(name){
+ var arg = name + "=";
+ var alen = arg.length;
+ var clen = document.cookie.length;
+ var i = 0;
+ var j = 0;
+ while(i < clen){
+ j = i + alen;
+ if(document.cookie.substring(i, j) == arg){
+ return Ext.util.Cookies.getCookieVal(j);
+ }
+ i = document.cookie.indexOf(" ", i) + 1;
+ if(i === 0){
+ break;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Removes a cookie with the provided name from the browser
+ * if found by setting its expiration date to sometime in the past.
+ * @param {String} name The name of the cookie to remove
+ */
+ clear : function(name){
+ if(Ext.util.Cookies.get(name)){
+ document.cookie = name + "=" + "; expires=Thu, 01-Jan-70 00:00:01 GMT";
+ }
+ },
+ /**
+ * @private
+ */
+ getCookieVal : function(offset){
+ var endstr = document.cookie.indexOf(";", offset);
+ if(endstr == -1){
+ endstr = document.cookie.length;
+ }
+ return unescape(document.cookie.substring(offset, endstr));
+ }
+};/**
+ * Framework-wide error-handler. Developers can override this method to provide
+ * custom exception-handling. Framework errors will often extend from the base
+ * Ext.Error class.
+ * @param {Object/Error} e The thrown exception object.
+ */
+Ext.handleError = function(e) {
+ throw e;
+};
+
+/**
+ * @class Ext.Error
+ * @extends Error
+ * <p>A base error class. Future implementations are intended to provide more
+ * robust error handling throughout the framework (<b>in the debug build only</b>)
+ * to check for common errors and problems. The messages issued by this class
+ * will aid error checking. Error checks will be automatically removed in the
+ * production build so that performance is not negatively impacted.</p>
+ * <p>Some sample messages currently implemented:</p><pre>
+"DataProxy attempted to execute an API-action but found an undefined
+url / function. Please review your Proxy url/api-configuration."
+ * </pre><pre>
+"Could not locate your "root" property in your server response.
+Please review your JsonReader config to ensure the config-property
+"root" matches the property your server-response. See the JsonReader
+docs for additional assistance."
+ * </pre>
+ * <p>An example of the code used for generating error messages:</p><pre><code>
+try {
+ generateError({
+ foo: 'bar'
+ });
+}
+catch (e) {
+ console.error(e);
+}
+function generateError(data) {
+ throw new Ext.Error('foo-error', data);
+}
+ * </code></pre>
+ * @param {String} message
+ */
+Ext.Error = function(message) {
+ // Try to read the message from Ext.Error.lang
+ this.message = (this.lang[message]) ? this.lang[message] : message;
+};
+
+Ext.Error.prototype = new Error();
+Ext.apply(Ext.Error.prototype, {
+ // protected. Extensions place their error-strings here.
+ lang: {},
+
+ name: 'Ext.Error',
+ /**
+ * getName
+ * @return {String}
+ */
+ getName : function() {
+ return this.name;
+ },
+ /**
+ * getMessage
+ * @return {String}
+ */
+ getMessage : function() {
+ return this.message;
+ },
+ /**
+ * toJson
+ * @return {String}
+ */
+ toJson : function() {
+ return Ext.encode(this);
+ }
+});
+/**
+ * @class Ext.ComponentMgr
+ * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
+ * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
+ * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
+ * <p>This object also provides a registry of available Component <i>classes</i>
+ * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
+ * The <code>{@link Ext.Component#xtype xtype}</code> provides a way to avoid instantiating child Components
+ * when creating a full, nested config object for a complete Ext page.</p>
+ * <p>A child Component may be specified simply as a <i>config object</i>
+ * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
+ * needs rendering, the correct type can be looked up for lazy instantiation.</p>
+ * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
+ * @singleton
+ */
+Ext.ComponentMgr = function(){
+ var all = new Ext.util.MixedCollection();
+ var types = {};
+ var ptypes = {};
+
+ return {
+ /**
+ * Registers a component.
+ * @param {Ext.Component} c The component
+ */
+ register : function(c){
+ all.add(c);
+ },
+
+ /**
+ * Unregisters a component.
+ * @param {Ext.Component} c The component
+ */
+ unregister : function(c){
+ all.remove(c);
+ },
+
+ /**
+ * Returns a component by {@link Ext.Component#id id}.
+ * For additional details see {@link Ext.util.MixedCollection#get}.
+ * @param {String} id The component {@link Ext.Component#id id}
+ * @return Ext.Component The Component, <code>undefined</code> if not found, or <code>null</code> if a
+ * Class was found.
+ */
+ get : function(id){
+ return all.get(id);
+ },
+
+ /**
+ * 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 (<code>this</code> reference) in which the callback is executed. Defaults to the Component.
+ */
+ onAvailable : function(id, fn, scope){
+ all.on("add", function(index, o){
+ if(o.id == id){
+ fn.call(scope || o, o);
+ all.un("add", fn, scope);
+ }
+ });
+ },
+
+ /**
+ * The MixedCollection used internally for the component cache. An example usage may be subscribing to
+ * events on the MixedCollection to monitor addition or removal. Read-only.
+ * @type {MixedCollection}
+ */
+ 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
+ * @return {Boolean} Whether the type is registered.
+ */
+ 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;
+ },
+
+ /**
+ * <p>Registers a new Component constructor, keyed by a new
+ * {@link Ext.Component#xtype}.</p>
+ * <p>Use this method (or its alias {@link Ext#reg Ext.reg}) to register new
+ * subclasses of {@link Ext.Component} so that lazy instantiation may be used when specifying
+ * child Components.
+ * see {@link Ext.Container#items}</p>
+ * @param {String} xtype The mnemonic string by which the Component class may be looked up.
+ * @param {Constructor} cls The new Component class.
+ */
+ registerType : function(xtype, cls){
+ types[xtype] = cls;
+ cls.xtype = xtype;
+ },
+
+ /**
+ * Creates a new Component from the specified config object using the
+ * config object's {@link Ext.component#xtype xtype} to determine the class to instantiate.
+ * @param {Object} config A configuration object for the Component you wish to create.
+ * @param {Constructor} defaultType The constructor to provide the default Component type if
+ * the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
+ * @return {Ext.Component} The newly instantiated Component.
+ */
+ create : function(config, defaultType){
+ return config.render ? config : new types[config.xtype || defaultType](config);
+ },
+
+ /**
+ * <p>Registers a new Plugin constructor, keyed by a new
+ * {@link Ext.Component#ptype}.</p>
+ * <p>Use this method (or its alias {@link Ext#preg Ext.preg}) to register new
+ * plugins for {@link Ext.Component}s so that lazy instantiation may be used when specifying
+ * Plugins.</p>
+ * @param {String} ptype The mnemonic string by which the Plugin class may be looked up.
+ * @param {Constructor} cls The new Plugin class.
+ */
+ registerPlugin : function(ptype, cls){
+ ptypes[ptype] = cls;
+ cls.ptype = ptype;
+ },
+
+ /**
+ * Creates a new Plugin from the specified config object using the
+ * config object's {@link Ext.component#ptype ptype} to determine the class to instantiate.
+ * @param {Object} config A configuration object for the Plugin you wish to create.
+ * @param {Constructor} defaultType The constructor to provide the default Plugin type if
+ * the config object does not contain a <code>ptype</code>. (Optional if the config contains a <code>ptype</code>).
+ * @return {Ext.Component} The newly instantiated Plugin.
+ */
+ createPlugin : function(config, defaultType){
+ var PluginCls = ptypes[config.ptype || defaultType];
+ if (PluginCls.init) {
+ return PluginCls;
+ } else {
+ return new PluginCls(config);
+ }
+ }
+ };
+}();
+
+/**
+ * Shorthand for {@link Ext.ComponentMgr#registerType}
+ * @param {String} xtype The {@link Ext.component#xtype mnemonic string} by which the Component class
+ * may be looked up.
+ * @param {Constructor} cls The new Component class.
+ * @member Ext
+ * @method reg
+ */
+Ext.reg = Ext.ComponentMgr.registerType; // this will be called a lot internally, shorthand to keep the bytes down
+/**
+ * Shorthand for {@link Ext.ComponentMgr#registerPlugin}
+ * @param {String} ptype The {@link Ext.component#ptype mnemonic string} by which the Plugin class
+ * may be looked up.
+ * @param {Constructor} cls The new Plugin class.
+ * @member Ext
+ * @method preg
+ */
+Ext.preg = Ext.ComponentMgr.registerPlugin;
+/**
+ * Shorthand for {@link Ext.ComponentMgr#create}
+ * Creates a new Component from the specified config object using the
+ * config object's {@link Ext.component#xtype xtype} to determine the class to instantiate.
+ * @param {Object} config A configuration object for the Component you wish to create.
+ * @param {Constructor} defaultType The constructor to provide the default Component type if
+ * the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
+ * @return {Ext.Component} The newly instantiated Component.
+ * @member Ext
+ * @method create
+ */
+Ext.create = Ext.ComponentMgr.create;/**
+ * @class Ext.Component
+ * @extends Ext.util.Observable
+ * <p>Base class for all Ext components. All subclasses of Component may participate in the automated
+ * Ext component lifecycle of creation, rendering and destruction which is provided by the {@link Ext.Container Container} class.
+ * Components may be added to a Container through the {@link Ext.Container#items items} config option at the time the Container is created,
+ * or they may be added dynamically via the {@link Ext.Container#add add} method.</p>
+ * <p>The Component base class has built-in support for basic hide/show and enable/disable behavior.</p>
+ * <p>All Components are registered with the {@link Ext.ComponentMgr} on construction so that they can be referenced at any time via
+ * {@link Ext#getCmp}, passing the {@link #id}.</p>
+ * <p>All user-developed visual widgets that are required to participate in automated lifecycle and size management should subclass Component (or
+ * {@link Ext.BoxComponent} if managed box model handling is required, ie height and width management).</p>
+ * <p>See the <a href="http://extjs.com/learn/Tutorial:Creating_new_UI_controls">Creating new UI controls</a> tutorial for details on how
+ * and to either extend or augment ExtJs base classes to create custom Components.</p>
+ * <p>Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the
+ * xtype like {@link #getXType} and {@link #isXType}. This is the list of all valid xtypes:</p>
+ * <pre>
+xtype Class
+------------- ------------------
+box {@link Ext.BoxComponent}
+button {@link Ext.Button}
+buttongroup {@link Ext.ButtonGroup}
+colorpalette {@link Ext.ColorPalette}
+component {@link Ext.Component}
+container {@link Ext.Container}
+cycle {@link Ext.CycleButton}
+dataview {@link Ext.DataView}
+datepicker {@link Ext.DatePicker}
+editor {@link Ext.Editor}
+editorgrid {@link Ext.grid.EditorGridPanel}
+flash {@link Ext.FlashComponent}
+grid {@link Ext.grid.GridPanel}
+listview {@link Ext.ListView}
+panel {@link Ext.Panel}
+progress {@link Ext.ProgressBar}
+propertygrid {@link Ext.grid.PropertyGrid}
+slider {@link Ext.Slider}
+spacer {@link Ext.Spacer}
+splitbutton {@link Ext.SplitButton}
+tabpanel {@link Ext.TabPanel}
+treepanel {@link Ext.tree.TreePanel}
+viewport {@link Ext.ViewPort}
+window {@link Ext.Window}
+
+Toolbar components
+---------------------------------------
+paging {@link Ext.PagingToolbar}
+toolbar {@link Ext.Toolbar}
+tbbutton {@link Ext.Toolbar.Button} (deprecated; use button)
+tbfill {@link Ext.Toolbar.Fill}
+tbitem {@link Ext.Toolbar.Item}
+tbseparator {@link Ext.Toolbar.Separator}
+tbspacer {@link Ext.Toolbar.Spacer}
+tbsplit {@link Ext.Toolbar.SplitButton} (deprecated; use splitbutton)
+tbtext {@link Ext.Toolbar.TextItem}
+
+Menu components
+---------------------------------------
+menu {@link Ext.menu.Menu}
+colormenu {@link Ext.menu.ColorMenu}
+datemenu {@link Ext.menu.DateMenu}
+menubaseitem {@link Ext.menu.BaseItem}
+menucheckitem {@link Ext.menu.CheckItem}
+menuitem {@link Ext.menu.Item}
+menuseparator {@link Ext.menu.Separator}
+menutextitem {@link Ext.menu.TextItem}
+
+Form components
+---------------------------------------
+form {@link Ext.form.FormPanel}
+checkbox {@link Ext.form.Checkbox}
+checkboxgroup {@link Ext.form.CheckboxGroup}
+combo {@link Ext.form.ComboBox}
+datefield {@link Ext.form.DateField}
+displayfield {@link Ext.form.DisplayField}
+field {@link Ext.form.Field}
+fieldset {@link Ext.form.FieldSet}
+hidden {@link Ext.form.Hidden}
+htmleditor {@link Ext.form.HtmlEditor}
+label {@link Ext.form.Label}
+numberfield {@link Ext.form.NumberField}
+radio {@link Ext.form.Radio}
+radiogroup {@link Ext.form.RadioGroup}
+textarea {@link Ext.form.TextArea}
+textfield {@link Ext.form.TextField}
+timefield {@link Ext.form.TimeField}
+trigger {@link Ext.form.TriggerField}
+
+Chart components
+---------------------------------------
+chart {@link Ext.chart.Chart}
+barchart {@link Ext.chart.BarChart}
+cartesianchart {@link Ext.chart.CartesianChart}
+columnchart {@link Ext.chart.ColumnChart}
+linechart {@link Ext.chart.LineChart}
+piechart {@link Ext.chart.PieChart}
+
+Store xtypes
+---------------------------------------
+arraystore {@link Ext.data.ArrayStore}
+directstore {@link Ext.data.DirectStore}
+groupingstore {@link Ext.data.GroupingStore}
+jsonstore {@link Ext.data.JsonStore}
+simplestore {@link Ext.data.SimpleStore} (deprecated; use arraystore)
+store {@link Ext.data.Store}
+xmlstore {@link Ext.data.XmlStore}
+</pre>
+ * @constructor
+ * @param {Ext.Element/String/Object} config The configuration options may be specified as either:
+ * <div class="mdetail-params"><ul>
+ * <li><b>an element</b> :
+ * <p class="sub-desc">it is set as the internal element and its id used as the component id</p></li>
+ * <li><b>a string</b> :
+ * <p class="sub-desc">it is assumed to be the id of an existing element and is used as the component id</p></li>
+ * <li><b>anything else</b> :
+ * <p class="sub-desc">it is assumed to be a standard config object and is applied to the component</p></li>
+ * </ul></div>
+ */
+Ext.Component = function(config){
+ config = config || {};
+ if(config.initialConfig){
+ if(config.isAction){ // actions
+ this.baseAction = config;
+ }
+ config = config.initialConfig; // component cloning / action set up
+ }else if(config.tagName || config.dom || Ext.isString(config)){ // element object
+ config = {applyTo: config, id: config.id || config};
+ }
+
+ /**
+ * This Component's initial configuration specification. Read-only.
+ * @type Object
+ * @property initialConfig
+ */
+ this.initialConfig = 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.
+ * @param {Ext.Component} this
+ */
+ 'disable',
+ /**
+ * @event enable
+ * Fires after the component is enabled.
+ * @param {Ext.Component} this
+ */
+ 'enable',
+ /**
+ * @event beforeshow
+ * Fires before the component is shown by calling the {@link #show} method.
+ * Return false from an event handler to stop the show.
+ * @param {Ext.Component} this
+ */
+ 'beforeshow',
+ /**
+ * @event show
+ * Fires after the component is shown when calling the {@link #show} method.
+ * @param {Ext.Component} this
+ */
+ 'show',
+ /**
+ * @event beforehide
+ * Fires before the component is hidden by calling the {@link #hide} method.
+ * Return false from an event handler to stop the hide.
+ * @param {Ext.Component} this
+ */
+ 'beforehide',
+ /**
+ * @event hide
+ * Fires after the component is hidden.
+ * Fires after the component is hidden when calling the {@link #hide} method.
+ * @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
+ * event handler to stop the {@link #render}.
+ * @param {Ext.Component} this
+ */
+ 'beforerender',
+ /**
+ * @event render
+ * Fires after the component markup is {@link #rendered}.
+ * @param {Ext.Component} this
+ */
+ 'render',
+ /**
+ * @event afterrender
+ * <p>Fires after the component rendering is finished.</p>
+ * <p>The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed
+ * by any afterRender method defined for the Component, and, if {@link #stateful}, after state
+ * has been restored.</p>
+ * @param {Ext.Component} this
+ */
+ 'afterrender',
+ /**
+ * @event beforedestroy
+ * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the {@link #destroy}.
+ * @param {Ext.Component} this
+ */
+ 'beforedestroy',
+ /**
+ * @event destroy
+ * Fires after the component is {@link #destroy}ed.
+ * @param {Ext.Component} this
+ */
+ 'destroy',
+ /**
+ * @event beforestaterestore
+ * Fires before the state of the component is restored. Return false from an event handler to stop the restore.
+ * @param {Ext.Component} this
+ * @param {Object} state The hash of state values returned from the StateProvider. If this
+ * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
+ * that simply copies property values into this Component. The method maybe overriden to
+ * provide custom state restoration.
+ */
+ 'beforestaterestore',
+ /**
+ * @event staterestore
+ * Fires after the state of the component is restored.
+ * @param {Ext.Component} this
+ * @param {Object} state The hash of state values returned from the StateProvider. This is passed
+ * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
+ * Component. The method maybe overriden to provide custom state restoration.
+ */
+ 'staterestore',
+ /**
+ * @event beforestatesave
+ * Fires before the state of the component is saved to the configured state provider. Return false to stop the save.
+ * @param {Ext.Component} this
+ * @param {Object} state The hash of state values. This is determined by calling
+ * <b><tt>getState()</tt></b> on the Component. This method must be provided by the
+ * developer to return whetever representation of state is required, by default, Ext.Component
+ * has a null implementation.
+ */
+ 'beforestatesave',
+ /**
+ * @event statesave
+ * Fires after the state of the component is saved to the configured state provider.
+ * @param {Ext.Component} this
+ * @param {Object} state The hash of state values. This is determined by calling
+ * <b><tt>getState()</tt></b> on the Component. This method must be provided by the
+ * developer to return whetever representation of state is required, by default, Ext.Component
+ * has a null implementation.
+ */
+ 'statesave'
+ );
+ this.getId();
+ Ext.ComponentMgr.register(this);
+ Ext.Component.superclass.constructor.call(this);
+
+ if(this.baseAction){
+ this.baseAction.addComponent(this);
+ }
+
+ this.initComponent();
+
+ if(this.plugins){
+ if(Ext.isArray(this.plugins)){
+ for(var i = 0, len = this.plugins.length; i < len; i++){
+ this.plugins[i] = this.initPlugin(this.plugins[i]);
+ }
+ }else{
+ this.plugins = this.initPlugin(this.plugins);
+ }
+ }
+
+ if(this.stateful !== false){
+ this.initState();
+ }
+
+ if(this.applyTo){
+ this.applyToMarkup(this.applyTo);
+ delete this.applyTo;
+ }else if(this.renderTo){
+ this.render(this.renderTo);
+ delete this.renderTo;
+ }
+};
+
+// private
+Ext.Component.AUTO_ID = 1000;
+
+Ext.extend(Ext.Component, Ext.util.Observable, {
+ // Configs below are used for all Components when rendered by FormLayout.
+ /**
+ * @cfg {String} fieldLabel <p>The label text to display next to this Component (defaults to '').</p>
+ * <br><p><b>Note</b>: this config is only used when this Component is rendered by a Container which
+ * has been configured to use the <b>{@link Ext.layout.FormLayout FormLayout}</b> layout manager (e.g.
+ * {@link Ext.form.FormPanel} or specifying <tt>layout:'form'</tt>).</p><br>
+ * <p>Also see <tt>{@link #hideLabel}</tt> and
+ * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl}.</p>
+ * Example use:<pre><code>
+new Ext.FormPanel({
+ height: 100,
+ renderTo: Ext.getBody(),
+ items: [{
+ xtype: 'textfield',
+ fieldLabel: 'Name'
+ }]
+});
+</code></pre>
+ */
+ /**
+ * @cfg {String} labelStyle <p>A CSS style specification string to apply directly to this field's
+ * label. Defaults to the container's labelStyle value if set (e.g.,
+ * <tt>{@link Ext.layout.FormLayout#labelStyle}</tt> , or '').</p>
+ * <br><p><b>Note</b>: see the note for <code>{@link #clearCls}</code>.</p><br>
+ * <p>Also see <code>{@link #hideLabel}</code> and
+ * <code>{@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl}.</code></p>
+ * Example use:<pre><code>
+new Ext.FormPanel({
+ height: 100,
+ renderTo: Ext.getBody(),
+ items: [{
+ xtype: 'textfield',
+ fieldLabel: 'Name',
+ labelStyle: 'font-weight:bold;'
+ }]
+});
+</code></pre>
+ */
+ /**
+ * @cfg {String} labelSeparator <p>The separator to display after the text of each
+ * <tt>{@link #fieldLabel}</tt>. This property may be configured at various levels.
+ * The order of precedence is:
+ * <div class="mdetail-params"><ul>
+ * <li>field / component level</li>
+ * <li>container level</li>
+ * <li>{@link Ext.layout.FormLayout#labelSeparator layout level} (defaults to colon <tt>':'</tt>)</li>
+ * </ul></div>
+ * To display no separator for this field's label specify empty string ''.</p>
+ * <br><p><b>Note</b>: see the note for <tt>{@link #clearCls}</tt>.</p><br>
+ * <p>Also see <tt>{@link #hideLabel}</tt> and
+ * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl}.</p>
+ * Example use:<pre><code>
+new Ext.FormPanel({
+ height: 100,
+ renderTo: Ext.getBody(),
+ layoutConfig: {
+ labelSeparator: '~' // layout config has lowest priority (defaults to ':')
+ },
+ {@link Ext.layout.FormLayout#labelSeparator labelSeparator}: '>>', // config at container level
+ items: [{
+ xtype: 'textfield',
+ fieldLabel: 'Field 1',
+ labelSeparator: '...' // field/component level config supersedes others
+ },{
+ xtype: 'textfield',
+ fieldLabel: 'Field 2' // labelSeparator will be '='
+ }]
+});
+</code></pre>
+ */
+ /**
+ * @cfg {Boolean} hideLabel <p><tt>true</tt> to completely hide the label element
+ * ({@link #fieldLabel label} and {@link #labelSeparator separator}). Defaults to <tt>false</tt>.
+ * By default, even if you do not specify a <tt>{@link #fieldLabel}</tt> the space will still be
+ * reserved so that the field will line up with other fields that do have labels.
+ * Setting this to <tt>true</tt> will cause the field to not reserve that space.</p>
+ * <br><p><b>Note</b>: see the note for <tt>{@link #clearCls}</tt>.</p><br>
+ * Example use:<pre><code>
+new Ext.FormPanel({
+ height: 100,
+ renderTo: Ext.getBody(),
+ items: [{
+ xtype: 'textfield'
+ hideLabel: true
+ }]
+});
+</code></pre>
+ */
+ /**
+ * @cfg {String} clearCls <p>The CSS class used to to apply to the special clearing div rendered
+ * directly after each form field wrapper to provide field clearing (defaults to
+ * <tt>'x-form-clear-left'</tt>).</p>
+ * <br><p><b>Note</b>: this config is only used when this Component is rendered by a Container
+ * which has been configured to use the <b>{@link Ext.layout.FormLayout FormLayout}</b> layout
+ * manager (e.g. {@link Ext.form.FormPanel} or specifying <tt>layout:'form'</tt>) and either a
+ * <tt>{@link #fieldLabel}</tt> is specified or <tt>isFormField=true</tt> is specified.</p><br>
+ * <p>See {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl} also.</p>
+ */
+ /**
+ * @cfg {String} itemCls
+ * <p><b>Note</b>: this config is only used when this Component is rendered by a Container which
+ * has been configured to use the <b>{@link Ext.layout.FormLayout FormLayout}</b> layout manager (e.g.
+ * {@link Ext.form.FormPanel} or specifying <tt>layout:'form'</tt>).</p><br>
+ * <p>An additional CSS class to apply to the div wrapping the form item
+ * element of this field. If supplied, <tt>itemCls</tt> at the <b>field</b> level will override
+ * the default <tt>itemCls</tt> supplied at the <b>container</b> level. The value specified for
+ * <tt>itemCls</tt> will be added to the default class (<tt>'x-form-item'</tt>).</p>
+ * <p>Since it is applied to the item wrapper (see
+ * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl}), it allows
+ * you to write standard CSS rules that can apply to the field, the label (if specified), or
+ * any other element within the markup for the field.</p>
+ * <br><p><b>Note</b>: see the note for <tt>{@link #fieldLabel}</tt>.</p><br>
+ * Example use:<pre><code>
+// Apply a style to the field's label:
+<style>
+ .required .x-form-item-label {font-weight:bold;color:red;}
+</style>
+
+new Ext.FormPanel({
+ height: 100,
+ renderTo: Ext.getBody(),
+ items: [{
+ xtype: 'textfield',
+ fieldLabel: 'Name',
+ itemCls: 'required' //this label will be styled
+ },{
+ xtype: 'textfield',
+ fieldLabel: 'Favorite Color'
+ }]
+});
+</code></pre>
+ */
+
+ /**
+ * @cfg {String} id
+ * <p>The <b>unique</b> id of this component (defaults to an {@link #getId auto-assigned id}).
+ * You should assign an id if you need to be able to access the component later and you do
+ * not have an object reference available (e.g., using {@link Ext}.{@link Ext#getCmp getCmp}).</p>
+ * <p>Note that this id will also be used as the element id for the containing HTML element
+ * that is rendered to the page for this component. This allows you to write id-based CSS
+ * rules to style the specific instance of this component uniquely, and also to select
+ * sub-elements using this component's id as the parent.</p>
+ * <p><b>Note</b>: to avoid complications imposed by a unique <tt>id</tt> also see
+ * <code>{@link #itemId}</code> and <code>{@link #ref}</code>.</p>
+ * <p><b>Note</b>: to access the container of an item see <code>{@link #ownerCt}</code>.</p>
+ */
+ /**
+ * @cfg {String} itemId
+ * <p>An <tt>itemId</tt> can be used as an alternative way to get a reference to a component
+ * when no object reference is available. Instead of using an <code>{@link #id}</code> with
+ * {@link Ext}.{@link Ext#getCmp getCmp}, use <code>itemId</code> with
+ * {@link Ext.Container}.{@link Ext.Container#getComponent getComponent} which will retrieve
+ * <code>itemId</code>'s or <tt>{@link #id}</tt>'s. Since <code>itemId</code>'s are an index to the
+ * container's internal MixedCollection, the <code>itemId</code> is scoped locally to the container --
+ * avoiding potential conflicts with {@link Ext.ComponentMgr} which requires a <b>unique</b>
+ * <code>{@link #id}</code>.</p>
+ * <pre><code>
+var c = new Ext.Panel({ //
+ {@link Ext.BoxComponent#height height}: 300,
+ {@link #renderTo}: document.body,
+ {@link Ext.Container#layout layout}: 'auto',
+ {@link Ext.Container#items items}: [
+ {
+ itemId: 'p1',
+ {@link Ext.Panel#title title}: 'Panel 1',
+ {@link Ext.BoxComponent#height height}: 150
+ },
+ {
+ itemId: 'p2',
+ {@link Ext.Panel#title title}: 'Panel 2',
+ {@link Ext.BoxComponent#height height}: 150
+ }
+ ]
+})
+p1 = c.{@link Ext.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
+p2 = p1.{@link #ownerCt}.{@link Ext.Container#getComponent getComponent}('p2'); // reference via a sibling
+ * </code></pre>
+ * <p>Also see <tt>{@link #id}</tt> and <code>{@link #ref}</code>.</p>
+ * <p><b>Note</b>: to access the container of an item see <tt>{@link #ownerCt}</tt>.</p>
+ */
+ /**
+ * @cfg {String} xtype
+ * The registered <tt>xtype</tt> to create. This config option is not used when passing
+ * a config object into a constructor. This config option is used only when
+ * lazy instantiation is being used, and a child item of a Container is being
+ * specified not as a fully instantiated Component, but as a <i>Component config
+ * object</i>. The <tt>xtype</tt> will be looked up at render time up to determine what
+ * type of child Component to create.<br><br>
+ * The predefined xtypes are listed {@link Ext.Component here}.
+ * <br><br>
+ * If you subclass Components to create your own Components, you may register
+ * them using {@link Ext.ComponentMgr#registerType} in order to be able to
+ * take advantage of lazy instantiation and rendering.
+ */
+ /**
+ * @cfg {String} ptype
+ * The registered <tt>ptype</tt> to create. This config option is not used when passing
+ * a config object into a constructor. This config option is used only when
+ * lazy instantiation is being used, and a Plugin is being
+ * specified not as a fully instantiated Component, but as a <i>Component config
+ * object</i>. The <tt>ptype</tt> will be looked up at render time up to determine what
+ * type of Plugin to create.<br><br>
+ * If you create your own Plugins, you may register them using
+ * {@link Ext.ComponentMgr#registerPlugin} in order to be able to
+ * take advantage of lazy instantiation and rendering.
+ */
+ /**
+ * @cfg {String} cls
+ * An optional extra CSS class that will be added to this component's Element (defaults to ''). This can be
+ * useful for adding customized styles to the component or any of its children using standard CSS rules.
+ */
+ /**
+ * @cfg {String} overCls
+ * An optional extra CSS class that will be added to this component's Element when the mouse moves
+ * over the Element, and removed when the mouse moves out. (defaults to ''). This can be
+ * useful for adding customized 'active' or 'hover' styles to the component or any of its children using standard CSS rules.
+ */
+ /**
+ * @cfg {String} style
+ * A custom style specification to be applied to this component's Element. Should be a valid argument to
+ * {@link Ext.Element#applyStyles}.
+ * <pre><code>
+new Ext.Panel({
+ title: 'Some Title',
+ renderTo: Ext.getBody(),
+ width: 400, height: 300,
+ layout: 'form',
+ items: [{
+ xtype: 'textarea',
+ style: {
+ width: '95%',
+ marginBottom: '10px'
+ }
+ },
+ new Ext.Button({
+ text: 'Send',
+ minWidth: '100',
+ style: {
+ marginBottom: '10px'
+ }
+ })
+ ]
+});
+ * </code></pre>
+ */
+ /**
+ * @cfg {String} ctCls
+ * <p>An optional extra CSS class that will be added to this component's container. This can be useful for
+ * adding customized styles to the container or any of its children using standard CSS rules. See
+ * {@link Ext.layout.ContainerLayout}.{@link Ext.layout.ContainerLayout#extraCls extraCls} also.</p>
+ * <p><b>Note</b>: <tt>ctCls</tt> defaults to <tt>''</tt> except for the following class
+ * which assigns a value by default:
+ * <div class="mdetail-params"><ul>
+ * <li>{@link Ext.layout.Box Box Layout} : <tt>'x-box-layout-ct'</tt></li>
+ * </ul></div>
+ * To configure the above Class with an extra CSS class append to the default. For example,
+ * for BoxLayout (Hbox and Vbox):<pre><code>
+ * ctCls: 'x-box-layout-ct custom-class'
+ * </code></pre>
+ * </p>
+ */
+ /**
+ * @cfg {Boolean} disabled
+ * Render this component disabled (default is false).
+ */
+ disabled : false,
+ /**
+ * @cfg {Boolean} hidden
+ * Render this component hidden (default is false). If <tt>true</tt>, the
+ * {@link #hide} method will be called internally.
+ */
+ hidden : false,
+ /**
+ * @cfg {Object/Array} plugins
+ * An object or array of objects that will provide custom functionality for this component. The only
+ * requirement for a valid plugin is that it contain an init method that accepts a reference of type Ext.Component.
+ * When a component is created, if any plugins are available, the component will call the init method on each
+ * plugin, passing a reference to itself. Each plugin can then call methods or respond to events on the
+ * component as needed to provide its functionality.
+ */
+ /**
+ * @cfg {Mixed} applyTo
+ * <p>Specify the id of the element, a DOM element or an existing Element corresponding to a DIV
+ * that is already present in the document that specifies some structural markup for this
+ * component.</p><div><ul>
+ * <li><b>Description</b> : <ul>
+ * <div class="sub-desc">When <tt>applyTo</tt> is used, constituent parts of the component can also be specified
+ * by id or CSS class name within the main element, and the component being created may attempt
+ * to create its subcomponents from that markup if applicable.</div>
+ * </ul></li>
+ * <li><b>Notes</b> : <ul>
+ * <div class="sub-desc">When using this config, a call to render() is not required.</div>
+ * <div class="sub-desc">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 component's container.</div>
+ * </ul></li>
+ * </ul></div>
+ */
+ /**
+ * @cfg {Mixed} renderTo
+ * <p>Specify the id of the element, a DOM element or an existing Element that this component
+ * will be rendered into.</p><div><ul>
+ * <li><b>Notes</b> : <ul>
+ * <div class="sub-desc">Do <u>not</u> use this option if the Component is to be a child item of
+ * a {@link Ext.Container Container}. It is the responsibility of the
+ * {@link Ext.Container Container}'s {@link Ext.Container#layout layout manager}
+ * to render and manage its child items.</div>
+ * <div class="sub-desc">When using this config, a call to render() is not required.</div>
+ * </ul></li>
+ * </ul></div>
+ * <p>See <tt>{@link #render}</tt> also.</p>
+ */
+ /**
+ * @cfg {Boolean} stateful
+ * <p>A flag which causes the Component to attempt to restore the state of
+ * internal properties from a saved state on startup. The component must have
+ * either a <code>{@link #stateId}</code> or <code>{@link #id}</code> assigned
+ * for state to be managed. Auto-generated ids are not guaranteed to be stable
+ * across page loads and cannot be relied upon to save and restore the same
+ * state for a component.<p>
+ * <p>For state saving to work, the state manager's provider must have been
+ * set to an implementation of {@link Ext.state.Provider} which overrides the
+ * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
+ * methods to save and recall name/value pairs. A built-in implementation,
+ * {@link Ext.state.CookieProvider} is available.</p>
+ * <p>To set the state provider for the current page:</p>
+ * <pre><code>
+Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
+ expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
+}));
+ * </code></pre>
+ * <p>A stateful Component attempts to save state when one of the events
+ * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
+ * <p>To save state, a stateful Component first serializes its state by
+ * calling <b><code>getState</code></b>. By default, this function does
+ * nothing. The developer must provide an implementation which returns an
+ * object hash which represents the Component's restorable state.</p>
+ * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
+ * which uses the configured {@link Ext.state.Provider} to save the object
+ * keyed by the Component's <code>{@link stateId}</code>, or, if that is not
+ * specified, its <code>{@link #id}</code>.</p>
+ * <p>During construction, a stateful Component attempts to <i>restore</i>
+ * its state by calling {@link Ext.state.Manager#get} passing the
+ * <code>{@link #stateId}</code>, or, if that is not specified, the
+ * <code>{@link #id}</code>.</p>
+ * <p>The resulting object is passed to <b><code>applyState</code></b>.
+ * The default implementation of <code>applyState</code> simply copies
+ * properties into the object, but a developer may override this to support
+ * more behaviour.</p>
+ * <p>You can perform extra processing on state save and restore by attaching
+ * handlers to the {@link #beforestaterestore}, {@link #staterestore},
+ * {@link #beforestatesave} and {@link #statesave} events.</p>
+ */
+ /**
+ * @cfg {String} stateId
+ * The unique id for this component to use for state management purposes
+ * (defaults to the component id if one was set, otherwise null if the
+ * component is using a generated id).
+ * <p>See <code>{@link #stateful}</code> for an explanation of saving and
+ * restoring Component state.</p>
+ */
+ /**
+ * @cfg {Array} stateEvents
+ * <p>An array of events that, when fired, should trigger this component to
+ * save its state (defaults to none). <code>stateEvents</code> may be any type
+ * of event supported by this component, including browser or custom events
+ * (e.g., <tt>['click', 'customerchange']</tt>).</p>
+ * <p>See <code>{@link #stateful}</code> for an explanation of saving and
+ * restoring Component state.</p>
+ */
+ /**
+ * @cfg {Mixed} autoEl
+ * <p>A tag name or {@link Ext.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
+ * encapsulate this Component.</p>
+ * <p>You do not normally need to specify this. For the base classes {@link Ext.Component}, {@link Ext.BoxComponent},
+ * and {@link Ext.Container}, this defaults to <b><tt>'div'</tt></b>. The more complex Ext classes use a more complex
+ * DOM structure created by their own onRender methods.</p>
+ * <p>This is intended to allow the developer to create application-specific utility Components encapsulated by
+ * different DOM elements. Example usage:</p><pre><code>
+{
+ xtype: 'box',
+ autoEl: {
+ tag: 'img',
+ src: 'http://www.example.com/example.jpg'
+ }
+}, {
+ xtype: 'box',
+ autoEl: {
+ tag: 'blockquote',
+ html: 'autoEl is cool!'
+ }
+}, {
+ xtype: 'container',
+ autoEl: 'ul',
+ cls: 'ux-unordered-list',
+ items: {
+ xtype: 'box',
+ autoEl: 'li',
+ html: 'First list item'
+ }
+}
+</code></pre>
+ */
+ autoEl : 'div',
+
+ /**
+ * @cfg {String} disabledClass
+ * CSS class added to the component when it is disabled (defaults to 'x-item-disabled').
+ */
+ disabledClass : 'x-item-disabled',
+ /**
+ * @cfg {Boolean} allowDomMove
+ * Whether the component can move the Dom node when rendering (defaults to true).
+ */
+ allowDomMove : true,
+ /**
+ * @cfg {Boolean} autoShow
+ * True if the component should check for hidden classes (e.g. 'x-hidden' or 'x-hide-display') and remove
+ * them on render (defaults to false).
+ */
+ autoShow : false,
+ /**
+ * @cfg {String} hideMode
+ * <p>How this component should be hidden. Supported values are <tt>'visibility'</tt>
+ * (css visibility), <tt>'offsets'</tt> (negative offset position) and <tt>'display'</tt>
+ * (css display).</p>
+ * <br><p><b>Note</b>: the default of <tt>'display'</tt> is generally preferred
+ * since items are automatically laid out when they are first shown (no sizing
+ * is done while hidden).</p>
+ */
+ hideMode : 'display',
+ /**
+ * @cfg {Boolean} hideParent
+ * True to hide and show the component's container when hide/show is called on the component, false to hide
+ * and show the component itself (defaults to false). For example, this can be used as a shortcut for a hide
+ * button on a window by setting hide:true on the button when adding it to its parent container.
+ */
+ hideParent : false,
+ /**
+ * <p>The {@link Ext.Element} which encapsulates this Component. Read-only.</p>
+ * <p>This will <i>usually</i> be a <DIV> element created by the class's onRender method, but
+ * that may be overridden using the <code>{@link #autoEl}</code> config.</p>
+ * <br><p><b>Note</b>: this element will not be available until this Component has been rendered.</p><br>
+ * <p>To add listeners for <b>DOM events</b> to this Component (as opposed to listeners
+ * for this Component's own Observable events), see the {@link Ext.util.Observable#listeners listeners}
+ * config for a suggestion, or use a render listener directly:</p><pre><code>
+new Ext.Panel({
+ title: 'The Clickable Panel',
+ listeners: {
+ render: function(p) {
+ // Append the Panel to the click handler's argument list.
+ p.getEl().on('click', handlePanelClick.createDelegate(null, [p], true));
+ },
+ single: true // Remove the listener after first invocation
+ }
+});
+</code></pre>
+ * <p>See also <tt>{@link #getEl getEl}</p>
+ * @type Ext.Element
+ * @property el
+ */
+ /**
+ * This Component's owner {@link Ext.Container Container} (defaults to undefined, and is set automatically when
+ * this Component is added to a Container). Read-only.
+ * <p><b>Note</b>: to access items within the Container see <tt>{@link #itemId}</tt>.</p>
+ * @type Ext.Container
+ * @property ownerCt
+ */
+ /**
+ * True if this component is hidden. Read-only.
+ * @type Boolean
+ * @property hidden
+ */
+ /**
+ * True if this component is disabled. Read-only.
+ * @type Boolean
+ * @property disabled
+ */
+ /**
+ * True if this component has been rendered. Read-only.
+ * @type Boolean
+ * @property rendered
+ */
+ rendered : false,
+
+ /**
+ * @cfg {String} contentEl
+ * <p>Optional. Specify an existing HTML element, or the <code>id</code> of an existing HTML element to use as the content
+ * for this component.</p>
+ * <ul>
+ * <li><b>Description</b> :
+ * <div class="sub-desc">This config option is used to take an existing HTML element and place it in the layout element
+ * of a new component (it simply moves the specified DOM element <i>after the Component is rendered</i> to use as the content.</div></li>
+ * <li><b>Notes</b> :
+ * <div class="sub-desc">The specified HTML element is appended to the layout element of the component <i>after any configured
+ * {@link #html HTML} has been inserted</i>, and so the document will not contain this element at the time the {@link #render} event is fired.</div>
+ * <div class="sub-desc">The specified HTML element used will not participate in any <code><b>{@link Ext.Container#layout layout}</b></code>
+ * scheme that the Component may use. It is just HTML. Layouts operate on child <code><b>{@link Ext.Container#items items}</b></code>.</div>
+ * <div class="sub-desc">Add either the <code>x-hidden</code> or the <code>x-hide-display</code> CSS class to
+ * prevent a brief flicker of the content before it is rendered to the panel.</div></li>
+ * </ul>
+ */
+ /**
+ * @cfg {String/Object} html
+ * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the layout element
+ * content (defaults to ''). The HTML content is added after the component is rendered,
+ * so the document will not contain this HTML at the time the {@link #render} event is fired.
+ * This content is inserted into the body <i>before</i> any configured {@link #contentEl} is appended.
+ */
+
+ /**
+ * @cfg {Mixed} tpl
+ * An <bold>{@link Ext.Template}</bold>, <bold>{@link Ext.XTemplate}</bold>
+ * or an array of strings to form an Ext.XTemplate.
+ * Used in conjunction with the <code>{@link #data}</code> and
+ * <code>{@link #tplWriteMode}</code> configurations.
+ */
+
+ /**
+ * @cfg {String} tplWriteMode The Ext.(X)Template method to use when
+ * updating the content area of the Component. Defaults to <tt>'overwrite'</tt>
+ * (see <code>{@link Ext.XTemplate#overwrite}</code>).
+ */
+ tplWriteMode : 'overwrite',
+
+ /**
+ * @cfg {Mixed} data
+ * The initial set of data to apply to the <code>{@link #tpl}</code> to
+ * update the content area of the Component.
+ */
+
+ /**
+ * @cfg {Array} bubbleEvents
+ * <p>An array of events that, when fired, should be bubbled to any parent container.
+ * See {@link Ext.util.Observable#enableBubble}.
+ * Defaults to <tt>[]</tt>.
+ */
+ bubbleEvents: [],
+
+
+ // private
+ ctype : 'Ext.Component',
+
+ // private
+ actionMode : 'el',
+
+ // private
+ getActionEl : function(){
+ return this[this.actionMode];
+ },
+
+ initPlugin : function(p){
+ if(p.ptype && !Ext.isFunction(p.init)){
+ p = Ext.ComponentMgr.createPlugin(p);
+ }else if(Ext.isString(p)){
+ p = Ext.ComponentMgr.createPlugin({
+ ptype: p
+ });
+ }
+ p.init(this);
+ return p;
+ },
+
+ /* // protected
+ * Function to be implemented by Component subclasses to be part of standard component initialization flow (it is empty by default).
+ * <pre><code>
+// Traditional constructor:
+Ext.Foo = function(config){
+ // call superclass constructor:
+ Ext.Foo.superclass.constructor.call(this, config);
+
+ this.addEvents({
+ // add events
+ });
+};
+Ext.extend(Ext.Foo, Ext.Bar, {
+ // class body
+}
+
+// initComponent replaces the constructor:
+Ext.Foo = Ext.extend(Ext.Bar, {
+ initComponent : function(){
+ // call superclass initComponent
+ Ext.Container.superclass.initComponent.call(this);
+
+ this.addEvents({
+ // add events
+ });
+ }
+}
+</code></pre>
+ */
+ 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);
+ },
+
+ /**
+ * <p>Render this Component into the passed HTML element.</p>
+ * <p><b>If you are using a {@link Ext.Container Container} object to house this Component, then
+ * do not use the render method.</b></p>
+ * <p>A Container's child Components are rendered by that Container's
+ * {@link Ext.Container#layout layout} manager when the Container is first rendered.</p>
+ * <p>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}, {@link Ext.layout.TableLayout}.</p>
+ * <p>If the Container is already rendered when a new child Component is added, you may need to call
+ * the Container's {@link Ext.Container#doLayout 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.</p>
+ * <p>When creating complex UIs, it is important to remember that sizing and positioning
+ * of child items is the responsibility of the Container's {@link Ext.Container#layout layout} manager.
+ * If you expect child items to be sized in response to user interactions, you must
+ * configure the Container with a layout manager which creates and manages the type of layout you
+ * have in mind.</p>
+ * <p><b>Omitting the Container's {@link Ext.Container#layout layout} config means that a basic
+ * layout manager is used which does nothing but render child components sequentially into the
+ * Container. No sizing or positioning will be performed in this situation.</b></p>
+ * @param {Element/HTMLElement/String} container (optional) The element this Component should be
+ * rendered into. If it is being created from existing markup, this should be omitted.
+ * @param {String/Number} position (optional) The element ID or DOM node index within the container <b>before</b>
+ * which this component will be inserted (defaults to appending to the end of the container)
+ */
+ render : function(container, position){
+ if(!this.rendered && this.fireEvent('beforerender', this) !== false){
+ if(!container && this.el){
+ this.el = Ext.get(this.el);
+ container = this.el.dom.parentNode;
+ this.allowDomMove = false;
+ }
+ this.container = Ext.get(container);
+ if(this.ctCls){
+ this.container.addClass(this.ctCls);
+ }
+ this.rendered = true;
+ if(position !== undefined){
+ if(Ext.isNumber(position)){
+ position = this.container.dom.childNodes[position];
+ }else{
+ position = Ext.getDom(position);
+ }
+ }
+ this.onRender(this.container, position || null);
+ if(this.autoShow){
+ this.el.removeClass(['x-hidden','x-hide-' + this.hideMode]);
+ }
+ if(this.cls){
+ this.el.addClass(this.cls);
+ delete this.cls;
+ }
+ if(this.style){
+ this.el.applyStyles(this.style);
+ delete this.style;
+ }
+ if(this.overCls){
+ 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();
+ }
+ if(this.disabled){
+ // pass silent so the event doesn't fire the first time.
+ this.disable(true);
+ }
+
+ if(this.stateful !== false){
+ this.initStateEvents();
+ }
+ this.fireEvent('afterrender', this);
+ }
+ return this;
+ },
+
+
+ /**
+ * 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
+ * <p>A path specification, relative to the Component's <code>{@link #ownerCt}</code>
+ * specifying into which ancestor Container to place a named reference to this Component.</p>
+ * <p>The ancestor axis can be traversed by using '/' characters in the path.
+ * For example, to put a reference to a Toolbar Button into <i>the Panel which owns the Toolbar</i>:</p><pre><code>
+var myGrid = new Ext.grid.EditorGridPanel({
+ title: 'My EditorGridPanel',
+ store: myStore,
+ colModel: myColModel,
+ tbar: [{
+ text: 'Save',
+ handler: saveChanges,
+ disabled: true,
+ ref: '../saveButton'
+ }],
+ listeners: {
+ afteredit: function() {
+// The button reference is in the GridPanel
+ myGrid.saveButton.enable();
+ }
+ }
+});
+</code></pre>
+ * <p>In the code above, if the <code>ref</code> had been <code>'saveButton'</code>
+ * the reference would have been placed into the Toolbar. Each '/' in the <code>ref</code>
+ * moves up one level from the Component's <code>{@link #ownerCt}</code>.</p>
+ * <p>Also see the <code>{@link #added}</code> and <code>{@link #removed}</code> events.</p>
+ */
+ if(this.ref && !this.refOwner){
+ var levels = this.ref.split('/'),
+ last = levels.length,
+ i = 0,
+ t = this;
+
+ while(t && i < last){
+ t = t.ownerCt;
+ ++i;
+ }
+ if(t){
+ t[this.refName = levels[--i]] = this;
+ /**
+ * @type Ext.Container
+ * @property refOwner
+ * The ancestor Container into which the {@link #ref} reference was inserted if this Component
+ * is a child of a Container, and has been configured with a <code>ref</code>.
+ */
+ this.refOwner = t;
+ }
+ }
+ },
+
+ removeRef : function() {
+ if (this.refOwner && this.refName) {
+ delete this.refOwner[this.refName];
+ delete this.refOwner;
+ }
+ },
+
+ // private
+ 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(Ext.apply({}, state));
+ this.fireEvent('staterestore', this, state);
+ }
+ }
+ }
+ }
+ },
+
+ // private
+ getStateId : function(){
+ return this.stateId || ((/^(ext-comp-|ext-gen)/).test(String(this.id)) ? null : this.id);
+ },
+
+ // private
+ initStateEvents : function(){
+ if(this.stateEvents){
+ for(var i = 0, e; e = this.stateEvents[i]; i++){
+ this.on(e, this.saveState, this, {delay:100});
+ }
+ }
+ },
+
+ // private
+ applyState : function(state){
+ if(state){
+ Ext.apply(this, state);
+ }
+ },
+
+ // private
+ getState : function(){
+ return null;
+ },
+
+ // private
+ saveState : function(){
+ if(Ext.state.Manager && this.stateful !== false){
+ var id = this.getStateId();
+ if(id){
+ var state = this.getState();
+ if(this.fireEvent('beforestatesave', this, state) !== false){
+ Ext.state.Manager.set(id, state);
+ this.fireEvent('statesave', this, state);
+ }
+ }
+ }
+ },
+
+ /**
+ * Apply this component to existing markup that is valid. With this function, no call to render() is required.
+ * @param {String/HTMLElement} el
+ */
+ applyToMarkup : function(el){
+ this.allowDomMove = false;
+ this.el = Ext.get(el);
+ this.render(this.el.dom.parentNode);
+ },
+
+ /**
+ * Adds a CSS class to the component's underlying element.
+ * @param {string} cls The CSS class name to add
+ * @return {Ext.Component} this
+ */
+ addClass : function(cls){
+ if(this.el){
+ this.el.addClass(cls);
+ }else{
+ this.cls = this.cls ? this.cls + ' ' + cls : cls;
+ }
+ return this;
+ },
+
+ /**
+ * Removes a CSS class from the component's underlying element.
+ * @param {string} cls The CSS class name to remove
+ * @return {Ext.Component} this
+ */
+ removeClass : function(cls){
+ if(this.el){
+ this.el.removeClass(cls);
+ }else if(this.cls){
+ this.cls = this.cls.split(' ').remove(cls).join(' ');
+ }
+ return this;
+ },
+
+ // private
+ // default function is not really useful
+ onRender : function(ct, position){
+ if(!this.el && this.autoEl){
+ if(Ext.isString(this.autoEl)){
+ this.el = document.createElement(this.autoEl);
+ }else{
+ var div = document.createElement('div');
+ Ext.DomHelper.overwrite(div, this.autoEl);
+ this.el = div.firstChild;
+ }
+ if (!this.el.id) {
+ this.el.id = this.getId();
+ }
+ }
+ if(this.el){
+ this.el = Ext.get(this.el);
+ if(this.allowDomMove !== false){
+ ct.dom.insertBefore(this.el.dom, position);
+ if (div) {
+ Ext.removeNode(div);
+ div = null;
+ }
+ }
+ }
+ },
+
+ // private
+ getAutoCreate : function(){
+ var cfg = Ext.isObject(this.autoCreate) ?
+ this.autoCreate : Ext.apply({}, this.defaultAutoCreate);
+ if(this.id && !cfg.id){
+ cfg.id = this.id;
+ }
+ return cfg;
+ },
+
+ // private
+ afterRender : Ext.emptyFn,
+
+ /**
+ * Destroys this component by purging any event listeners, removing the component's element from the DOM,
+ * removing the component from its {@link Ext.Container} (if applicable) and unregistering it from
+ * {@link Ext.ComponentMgr}. Destruction is generally handled automatically by the framework and this method
+ * should usually not need to be called directly.
+ *
+ */
+ 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.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,
+
+ // private
+ onDestroy : Ext.emptyFn,
+
+ /**
+ * <p>Returns the {@link Ext.Element} which encapsulates this Component.</p>
+ * <p>This will <i>usually</i> be a <DIV> element created by the class's onRender method, but
+ * that may be overridden using the {@link #autoEl} config.</p>
+ * <br><p><b>Note</b>: this element will not be available until this Component has been rendered.</p><br>
+ * <p>To add listeners for <b>DOM events</b> to this Component (as opposed to listeners
+ * for this Component's own Observable events), see the {@link #listeners} config for a suggestion,
+ * or use a render listener directly:</p><pre><code>
+new Ext.Panel({
+ title: 'The Clickable Panel',
+ listeners: {
+ render: function(p) {
+ // Append the Panel to the click handler's argument list.
+ p.getEl().on('click', handlePanelClick.createDelegate(null, [p], true));
+ },
+ single: true // Remove the listener after first invocation
+ }
+});
+</code></pre>
+ * @return {Ext.Element} The Element which encapsulates this Component.
+ */
+ getEl : function(){
+ return this.el;
+ },
+
+ // private
+ getContentTarget : function(){
+ return this.el;
+ },
+
+ /**
+ * Returns the <code>id</code> of this component or automatically generates and
+ * returns an <code>id</code> if an <code>id</code> is not defined yet:<pre><code>
+ * 'ext-comp-' + (++Ext.Component.AUTO_ID)
+ * </code></pre>
+ * @return {String} id
+ */
+ getId : function(){
+ return this.id || (this.id = 'ext-comp-' + (++Ext.Component.AUTO_ID));
+ },
+
+ /**
+ * Returns the <code>{@link #itemId}</code> of this component. If an
+ * <code>{@link #itemId}</code> was not assigned through configuration the
+ * <code>id</code> is returned using <code>{@link #getId}</code>.
+ * @return {String}
+ */
+ getItemId : function(){
+ return this.itemId || this.getId();
+ },
+
+ /**
+ * Try to focus this component.
+ * @param {Boolean} selectText (optional) If applicable, true to also select the text in this component
+ * @param {Boolean/Number} delay (optional) Delay the focus this number of milliseconds (true for 10 milliseconds)
+ * @return {Ext.Component} this
+ */
+ focus : function(selectText, delay){
+ if(delay){
+ this.focusTask = new Ext.util.DelayedTask(this.focus, this, [selectText, false]);
+ this.focusTask.delay(Ext.isNumber(delay) ? delay : 10);
+ return;
+ }
+ if(this.rendered && !this.isDestroyed){
+ this.el.focus();
+ if(selectText === true){
+ this.el.dom.select();
+ }
+ }
+ return this;
+ },
+
+ // private
+ blur : function(){
+ if(this.rendered){
+ this.el.blur();
+ }
+ return this;
+ },
+
+ /**
+ * Disable this component and fire the 'disable' event.
+ * @return {Ext.Component} this
+ */
+ disable : function(/* private */ silent){
+ if(this.rendered){
+ this.onDisable();
+ }
+ this.disabled = true;
+ if(silent !== true){
+ this.fireEvent('disable', this);
+ }
+ return this;
+ },
+
+ // private
+ onDisable : function(){
+ this.getActionEl().addClass(this.disabledClass);
+ this.el.dom.disabled = true;
+ },
+
+ /**
+ * Enable this component and fire the 'enable' event.
+ * @return {Ext.Component} this
+ */
+ enable : function(){
+ if(this.rendered){
+ this.onEnable();
+ }
+ this.disabled = false;
+ this.fireEvent('enable', this);
+ return this;
+ },
+
+ // private
+ onEnable : function(){
+ this.getActionEl().removeClass(this.disabledClass);
+ this.el.dom.disabled = false;
+ },
+
+ /**
+ * Convenience function for setting disabled/enabled by boolean.
+ * @param {Boolean} disabled
+ * @return {Ext.Component} this
+ */
+ setDisabled : function(disabled){
+ return this[disabled ? 'disable' : 'enable']();
+ },
+
+ /**
+ * Show this component. Listen to the '{@link #beforeshow}' event and return
+ * <tt>false</tt> to cancel showing the component. Fires the '{@link #show}'
+ * event after showing the component.
+ * @return {Ext.Component} this
+ */
+ show : function(){
+ if(this.fireEvent('beforeshow', this) !== false){
+ this.hidden = false;
+ if(this.autoRender){
+ this.render(Ext.isBoolean(this.autoRender) ? Ext.getBody() : this.autoRender);
+ }
+ if(this.rendered){
+ this.onShow();
+ }
+ this.fireEvent('show', this);
+ }
+ return this;
+ },
+
+ // private
+ onShow : function(){
+ this.getVisibilityEl().removeClass('x-hide-' + this.hideMode);
+ },
+
+ /**
+ * Hide this component. Listen to the '{@link #beforehide}' event and return
+ * <tt>false</tt> to cancel hiding the component. Fires the '{@link #hide}'
+ * event after hiding the component. Note this method is called internally if
+ * the component is configured to be <code>{@link #hidden}</code>.
+ * @return {Ext.Component} this
+ */
+ hide : function(){
+ if(this.fireEvent('beforehide', this) !== false){
+ this.doHide();
+ this.fireEvent('hide', this);
+ }
+ return this;
+ },
+
+ // private
+ doHide: function(){
+ this.hidden = true;
+ if(this.rendered){
+ this.onHide();
+ }
+ },
+
+ // private
+ onHide : function(){
+ this.getVisibilityEl().addClass('x-hide-' + this.hideMode);
+ },
+
+ // private
+ getVisibilityEl : function(){
+ return this.hideParent ? this.container : this.getActionEl();
+ },
+
+ /**
+ * Convenience function to hide or show this component by boolean.
+ * @param {Boolean} visible True to show, false to hide
+ * @return {Ext.Component} this
+ */
+ setVisible : function(visible){
+ return this[visible ? 'show' : 'hide']();
+ },
+
+ /**
+ * Returns true if this component is visible.
+ * @return {Boolean} True if this component is visible, false otherwise.
+ */
+ isVisible : function(){
+ return this.rendered && this.getVisibilityEl().isVisible();
+ },
+
+ /**
+ * Clone the current component using the original config values passed into this instance by default.
+ * @param {Object} overrides A new config containing any properties to override in the cloned version.
+ * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
+ * @return {Ext.Component} clone The cloned copy of this component
+ */
+ cloneConfig : function(overrides){
+ overrides = overrides || {};
+ var id = overrides.id || Ext.id();
+ var cfg = Ext.applyIf(overrides, this.initialConfig);
+ cfg.id = id; // prevent dup id
+ return new this.constructor(cfg);
+ },
+
+ /**
+ * Gets the xtype for this component as registered with {@link Ext.ComponentMgr}. For a list of all
+ * available xtypes, see the {@link Ext.Component} header. Example usage:
+ * <pre><code>
+var t = new Ext.form.TextField();
+alert(t.getXType()); // alerts 'textfield'
+</code></pre>
+ * @return {String} The xtype
+ */
+ getXType : function(){
+ return this.constructor.xtype;
+ },
+
+ /**
+ * <p>Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
+ * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).</p>
+ * <p><b>If using your own subclasses, be aware that a Component must register its own xtype
+ * to participate in determination of inherited xtypes.</b></p>
+ * <p>For a list of all available xtypes, see the {@link Ext.Component} header.</p>
+ * <p>Example usage:</p>
+ * <pre><code>
+var t = new Ext.form.TextField();
+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
+</code></pre>
+ * @param {String} xtype The xtype to check for this 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.
+ */
+ isXType : function(xtype, shallow){
+ //assume a string by default
+ if (Ext.isFunction(xtype)){
+ xtype = xtype.xtype; //handle being passed the class, e.g. Ext.Component
+ }else if (Ext.isObject(xtype)){
+ xtype = xtype.constructor.xtype; //handle being passed an instance
+ }
+
+ return !shallow ? ('/' + this.getXTypes() + '/').indexOf('/' + xtype + '/') != -1 : this.constructor.xtype == xtype;
+ },
+
+ /**
+ * <p>Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all
+ * available xtypes, see the {@link Ext.Component} header.</p>
+ * <p><b>If using your own subclasses, be aware that a Component must register its own xtype
+ * to participate in determination of inherited xtypes.</b></p>
+ * <p>Example usage:</p>
+ * <pre><code>
+var t = new Ext.form.TextField();
+alert(t.getXTypes()); // alerts 'component/box/field/textfield'
+</code></pre>
+ * @return {String} The xtype hierarchy string
+ */
+ getXTypes : function(){
+ var tc = this.constructor;
+ if(!tc.xtypes){
+ var c = [], sc = this;
+ while(sc && sc.constructor.xtype){
+ c.unshift(sc.constructor.xtype);
+ sc = sc.constructor.superclass;
+ }
+ tc.xtypeChain = c;
+ tc.xtypes = c.join('/');
+ }
+ return tc.xtypes;
+ },
+
+ /**
+ * Find a container above this component at any level by a custom function. If the passed function returns
+ * true, the container will be returned.
+ * @param {Function} fn The custom function to call with the arguments (container, this component).
+ * @return {Ext.Container} The first Container for which the custom function returns true
+ */
+ findParentBy : function(fn) {
+ for (var p = this.ownerCt; (p != null) && !fn(p, this); p = p.ownerCt);
+ return p || null;
+ },
+
+ /**
+ * 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
+ * @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;
+ });
+ },
+
+ // protected
+ getPositionEl : function(){
+ return this.positionEl || this.el;
+ },
+
+ // private
+ purgeListeners : function(){
+ Ext.Component.superclass.purgeListeners.call(this);
+ if(this.mons){
+ this.on('beforedestroy', this.clearMons, this, {single: true});
+ }
+ },
+
+ // private
+ clearMons : function(){
+ Ext.each(this.mons, function(m){
+ m.item.un(m.ename, m.fn, m.scope);
+ }, this);
+ this.mons = [];
+ },
+
+ // private
+ createMons: function(){
+ if(!this.mons){
+ this.mons = [];
+ this.on('beforedestroy', this.clearMons, this, {single: true});
+ }
+ },
+
+ /**
+ * <p>Adds listeners to any Observable object (or Elements) which are automatically removed when this Component
+ * is destroyed. Usage:</p><code><pre>
+myGridPanel.mon(myGridPanel.getSelectionModel(), 'selectionchange', handleSelectionChange, null, {buffer: 50});
+</pre></code>
+ * <p>or:</p><code><pre>
+myGridPanel.mon(myGridPanel.getSelectionModel(), {
+ selectionchange: handleSelectionChange,
+ buffer: 50
+});
+</pre></code>
+ * @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 <code>ename</code> parameter was an event name, this
+ * is the handler function.
+ * @param {Object} scope Optional. If the <code>ename</code> parameter was an event name, this
+ * is the scope (<code>this</code> reference) in which the handler function is executed.
+ * @param {Object} opt Optional. If the <code>ename</code> parameter was an event name, this
+ * is the {@link Ext.util.Observable#addListener addListener} options.
+ */
+ mon : function(item, ename, fn, scope, opt){
+ this.createMons();
+ if(Ext.isObject(ename)){
+ var propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/;
+
+ var o = ename;
+ for(var e in o){
+ if(propRe.test(e)){
+ continue;
+ }
+ if(Ext.isFunction(o[e])){
+ // shared options
+ this.mons.push({
+ item: item, ename: e, fn: o[e], scope: o.scope
+ });
+ item.on(e, o[e], o.scope, o);
+ }else{
+ // individual options
+ this.mons.push({
+ item: item, ename: e, fn: o[e], scope: o.scope
+ });
+ item.on(e, o[e]);
+ }
+ }
+ return;
+ }
+
+ this.mons.push({
+ item: item, ename: ename, fn: fn, scope: scope
+ });
+ item.on(ename, fn, scope, opt);
+ },
+
+ /**
+ * 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 <code>ename</code> parameter was an event name, this
+ * is the handler function.
+ * @param {Object} scope Optional. If the <code>ename</code> parameter was an event name, this
+ * is the scope (<code>this</code> reference) in which the handler function is executed.
+ */
+ mun : function(item, ename, fn, scope){
+ var found, mon;
+ this.createMons();
+ for(var i = 0, len = this.mons.length; i < len; ++i){
+ mon = this.mons[i];
+ if(item === mon.item && ename == mon.ename && fn === mon.fn && scope === mon.scope){
+ this.mons.splice(i, 1);
+ item.un(ename, fn, scope);
+ found = true;
+ break;
+ }
+ }
+ return found;
+ },
+
+ /**
+ * Returns the next component in the owning container
+ * @return Ext.Component
+ */
+ nextSibling : function(){
+ if(this.ownerCt){
+ var index = this.ownerCt.items.indexOf(this);
+ if(index != -1 && index+1 < this.ownerCt.items.getCount()){
+ return this.ownerCt.items.itemAt(index+1);
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Returns the previous component in the owning container
+ * @return Ext.Component
+ */
+ previousSibling : function(){
+ if(this.ownerCt){
+ var index = this.ownerCt.items.indexOf(this);
+ if(index > 0){
+ return this.ownerCt.items.itemAt(index-1);
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
+ * @return {Ext.Container} the Container which owns this Component.
+ */
+ getBubbleTarget : function(){
+ return this.ownerCt;
+ }
+});
+
+Ext.reg('component', Ext.Component);/**
+ * @class Ext.Action
+ * <p>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).</p>
+ * <p>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).</p>
+ * Example usage:<br>
+ * <pre><code>
+// 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');
+</code></pre>
+ * @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 '').
+ * <p>An example of specifying a custom icon class would be something like:
+ * </p><pre><code>
+// 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; }
+</code></pre>
+ */
+ /**
+ * @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 (<tt><b>this</b></tt> reference) in which the
+ * <code>{@link #handler}</code> 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 <code>{@link #hide}</code> and <code>{@link #show}</code>.
+ * @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 (<code>this</code> 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 (<code>this</code> 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 <code>{@link #setHandler}</code>. 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
+ * An extended {@link Ext.Element} object that supports a shadow and shim, constrain to viewport and
+ * automatic maintaining of shadow/shim positions.
+ * @cfg {Boolean} shim False to disable the iframe shim in browsers which need one (defaults to true)
+ * @cfg {String/Boolean} shadow True to automatically create an {@link Ext.Shadow}, or a string indicating the
+ * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow. (defaults to false)
+ * @cfg {Object} dh DomHelper object config to create element with (defaults to {tag: 'div', cls: 'x-layer'}).
+ * @cfg {Boolean} constrain False to disable constrain to viewport (defaults to true)
+ * @cfg {String} cls CSS class to add to the element
+ * @cfg {Number} zindex Starting z-index (defaults to 11000)
+ * @cfg {Number} shadowOffset Number of pixels to offset the shadow (defaults to 4)
+ * @cfg {Boolean} useDisplay
+ * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
+ * to use css style <tt>'display:none;'</tt> to hide the Layer.
+ * @constructor
+ * @param {Object} config An object with config options.
+ * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element. If the element is not found it creates it.
+ */
+(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){
+ this.dom = Ext.getDom(existingEl);
+ }
+ if(!this.dom){
+ var o = config.dh || {tag: 'div', cls: 'x-layer'};
+ this.dom = dh.append(pel, o);
+ }
+ if(config.cls){
+ this.addClass(config.cls);
+ }
+ this.constrain = config.constrain !== false;
+ this.setVisibilityMode(Ext.Element.VISIBILITY);
+ if(config.id){
+ this.id = this.dom.id = config.id;
+ }else{
+ this.id = Ext.id(this.dom);
+ }
+ this.zindex = config.zindex || this.getZIndex();
+ this.position('absolute', this.zindex);
+ if(config.shadow){
+ this.shadowOffset = config.shadowOffset || 4;
+ this.shadow = new Ext.Shadow({
+ offset : this.shadowOffset,
+ mode : config.shadow
+ });
+ }else{
+ this.shadowOffset = 0;
+ }
+ this.useShim = config.shim !== false && Ext.useShims;
+ this.useDisplay = config.useDisplay;
+ this.hide();
+};
+
+var supr = Ext.Element.prototype;
+
+// shims are shared among layer to keep from having 100 iframes
+var shims = [];
+
+Ext.extend(Ext.Layer, Ext.Element, {
+
+ getZIndex : function(){
+ return this.zindex || parseInt((this.getShim() || this).getStyle('z-index'), 10) || 11000;
+ },
+
+ getShim : function(){
+ if(!this.useShim){
+ return null;
+ }
+ if(this.shim){
+ return this.shim;
+ }
+ var shim = shims.shift();
+ if(!shim){
+ shim = this.createShim();
+ shim.enableDisplayMode('block');
+ shim.dom.style.display = 'none';
+ shim.dom.style.visibility = 'visible';
+ }
+ var pn = this.dom.parentNode;
+ if(shim.dom.parentNode != pn){
+ pn.insertBefore(shim.dom, this.dom);
+ }
+ shim.setStyle('z-index', this.getZIndex()-2);
+ this.shim = shim;
+ return shim;
+ },
+
+ hideShim : function(){
+ if(this.shim){
+ this.shim.setDisplayed(false);
+ shims.push(this.shim);
+ delete this.shim;
+ }
+ },
+
+ disableShadow : function(){
+ if(this.shadow){
+ this.shadowDisabled = true;
+ this.shadow.hide();
+ this.lastShadowOffset = this.shadowOffset;
+ this.shadowOffset = 0;
+ }
+ },
+
+ enableShadow : function(show){
+ if(this.shadow){
+ this.shadowDisabled = false;
+ this.shadowOffset = this.lastShadowOffset;
+ delete this.lastShadowOffset;
+ if(show){
+ this.sync(true);
+ }
+ }
+ },
+
+ // private
+ // 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 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(shadow && !this.shadowDisabled){
+ if(doShow && !shadow.isVisible()){
+ shadow.show(this);
+ }else{
+ shadow.realign(l, t, w, h);
+ }
+ if(shim){
+ if(doShow){
+ shim.show();
+ }
+ // fit the shim behind the shadow, so it is shimmed too
+ 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(shim){
+ if(doShow){
+ shim.show();
+ }
+ shim.setSize(w, h);
+ shim.setLeftTop(l, t);
+ }
+ }
+ },
+
+ // private
+ destroy : function(){
+ this.hideShim();
+ if(this.shadow){
+ this.shadow.hide();
+ }
+ this.removeAllListeners();
+ Ext.removeNode(this.dom);
+ delete this.dom;
+ },
+
+ remove : function(){
+ this.destroy();
+ },
+
+ // private
+ beginUpdate : function(){
+ this.updating = true;
+ },
+
+ // private
+ endUpdate : function(){
+ this.updating = false;
+ this.sync(true);
+ },
+
+ // private
+ hideUnders : function(negOffset){
+ if(this.shadow){
+ this.shadow.hide();
+ }
+ this.hideShim();
+ },
+
+ // private
+ constrainXY : function(){
+ if(this.constrain){
+ var vw = Ext.lib.Dom.getViewWidth(),
+ vh = Ext.lib.Dom.getViewHeight();
+ var s = Ext.getDoc().getScroll();
+
+ var xy = this.getXY();
+ var x = xy[0], y = xy[1];
+ var so = this.shadowOffset;
+ var w = this.dom.offsetWidth+so, h = this.dom.offsetHeight+so;
+ // only move it if it needs it
+ var moved = false;
+ // first validate right/bottom
+ if((x + w) > vw+s.left){
+ x = vw - w - so;
+ moved = true;
+ }
+ if((y + h) > vh+s.top){
+ y = vh - h - so;
+ moved = true;
+ }
+ // then make sure top/left isn't negative
+ if(x < s.left){
+ x = s.left;
+ moved = true;
+ }
+ if(y < s.top){
+ y = s.top;
+ moved = true;
+ }
+ if(moved){
+ if(this.avoidY){
+ var ay = this.avoidY;
+ if(y <= ay && (y+h) >= ay){
+ y = ay-h-5;
+ }
+ }
+ xy = [x, y];
+ this.storeXY(xy);
+ supr.setXY.call(this, xy);
+ this.sync();
+ }
+ }
+ return this;
+ },
+
+ isVisible : function(){
+ return this.visible;
+ },
+
+ // private
+ showAction : function(){
+ this.visible = true; // track visibility to prevent getStyle calls
+ if(this.useDisplay === true){
+ this.setDisplayed('');
+ }else if(this.lastXY){
+ supr.setXY.call(this, this.lastXY);
+ }else if(this.lastLT){
+ supr.setLeftTop.call(this, this.lastLT[0], this.lastLT[1]);
+ }
+ },
+
+ // private
+ hideAction : function(){
+ this.visible = false;
+ if(this.useDisplay === true){
+ this.setDisplayed(false);
+ }else{
+ this.setLeftTop(-10000,-10000);
+ }
+ },
+
+ // overridden Element method
+ setVisible : function(v, a, d, c, e){
+ if(v){
+ this.showAction();
+ }
+ if(a && v){
+ var cb = function(){
+ this.sync(true);
+ if(c){
+ c();
+ }
+ }.createDelegate(this);
+ supr.setVisible.call(this, true, true, d, cb, e);
+ }else{
+ if(!v){
+ this.hideUnders(true);
+ }
+ var cb = c;
+ if(a){
+ cb = function(){
+ this.hideAction();
+ if(c){
+ c();
+ }
+ }.createDelegate(this);
+ }
+ supr.setVisible.call(this, v, a, d, cb, e);
+ if(v){
+ this.sync(true);
+ }else if(!a){
+ this.hideAction();
+ }
+ }
+ return this;
+ },
+
+ storeXY : function(xy){
+ delete this.lastLT;
+ this.lastXY = xy;
+ },
+
+ storeLeftTop : function(left, top){
+ delete this.lastXY;
+ this.lastLT = [left, top];
+ },
+
+ // private
+ beforeFx : function(){
+ this.beforeAction();
+ return Ext.Layer.superclass.beforeFx.apply(this, arguments);
+ },
+
+ // private
+ afterFx : function(){
+ Ext.Layer.superclass.afterFx.apply(this, arguments);
+ this.sync(this.isVisible());
+ },
+
+ // private
+ beforeAction : function(){
+ if(!this.updating && this.shadow){
+ this.shadow.hide();
+ }
+ },
+
+ // overridden Element method
+ setLeft : function(left){
+ this.storeLeftTop(left, this.getTop(true));
+ supr.setLeft.apply(this, arguments);
+ this.sync();
+ return this;
+ },
+
+ setTop : function(top){
+ this.storeLeftTop(this.getLeft(true), top);
+ supr.setTop.apply(this, arguments);
+ this.sync();
+ return this;
+ },
+
+ setLeftTop : function(left, top){
+ this.storeLeftTop(left, top);
+ supr.setLeftTop.apply(this, arguments);
+ this.sync();
+ return this;
+ },
+
+ setXY : function(xy, a, d, c, e){
+ this.fixDisplay();
+ this.beforeAction();
+ this.storeXY(xy);
+ var cb = this.createCB(c);
+ supr.setXY.call(this, xy, a, d, cb, e);
+ if(!a){
+ cb();
+ }
+ return this;
+ },
+
+ // private
+ createCB : function(c){
+ var el = this;
+ return function(){
+ el.constrainXY();
+ el.sync(true);
+ if(c){
+ c();
+ }
+ };
+ },
+
+ // overridden Element method
+ setX : function(x, a, d, c, e){
+ this.setXY([x, this.getY()], a, d, c, e);
+ return this;
+ },
+
+ // overridden Element method
+ setY : function(y, a, d, c, e){
+ this.setXY([this.getX(), y], a, d, c, e);
+ return this;
+ },
+
+ // overridden Element method
+ setSize : function(w, h, a, d, c, e){
+ this.beforeAction();
+ var cb = this.createCB(c);
+ supr.setSize.call(this, w, h, a, d, cb, e);
+ if(!a){
+ cb();
+ }
+ return this;
+ },
+
+ // overridden Element method
+ setWidth : function(w, a, d, c, e){
+ this.beforeAction();
+ var cb = this.createCB(c);
+ supr.setWidth.call(this, w, a, d, cb, e);
+ if(!a){
+ cb();
+ }
+ return this;
+ },
+
+ // overridden Element method
+ setHeight : function(h, a, d, c, e){
+ this.beforeAction();
+ var cb = this.createCB(c);
+ supr.setHeight.call(this, h, a, d, cb, e);
+ if(!a){
+ cb();
+ }
+ return this;
+ },
+
+ // overridden Element method
+ setBounds : function(x, y, w, h, a, d, c, e){
+ this.beforeAction();
+ var cb = this.createCB(c);
+ if(!a){
+ this.storeXY([x, y]);
+ supr.setXY.call(this, [x, y]);
+ supr.setSize.call(this, w, h, a, d, cb, e);
+ cb();
+ }else{
+ supr.setBounds.call(this, x, y, w, h, a, d, cb, e);
+ }
+ return this;
+ },
+
+ /**
+ * Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
+ * incremented by two more than the value passed in so that it always shows above any shadow or shim (the shadow
+ * element, if any, will be assigned z-index + 1, and the shim element, if any, will be assigned the unmodified z-index).
+ * @param {Number} zindex The new z-index to set
+ * @return {this} The Layer
+ */
+ setZIndex : function(zindex){
+ this.zindex = zindex;
+ this.setStyle('z-index', zindex + 2);
+ if(this.shadow){
+ this.shadow.setZIndex(zindex + 1);
+ }
+ if(this.shim){
+ this.shim.setStyle('z-index', zindex);
+ }
+ 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
+ * functionality that can also provide the same shadow effect, see the {@link Ext.Layer} class.
+ * @constructor
+ * Create a new Shadow
+ * @param {Object} config The config object
+ */
+Ext.Shadow = function(config){
+ Ext.apply(this, config);
+ 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
+ case "drop":
+ a.w = 0;
+ a.l = a.t = o;
+ a.t -= 1;
+ if(Ext.isIE){
+ a.l -= this.offset + rad;
+ a.t -= this.offset + rad;
+ a.w -= rad;
+ a.h -= rad;
+ a.t += 1;
+ }
+ break;
+ case "sides":
+ a.w = (o*2);
+ a.l = -o;
+ 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 -= rad + 1;
+ a.h -= 1;
+ }
+ break;
+ case "frame":
+ a.w = a.h = (o*2);
+ a.l = a.t = -o;
+ a.t += 1;
+ a.h -= 2;
+ if(Ext.isIE){
+ a.l -= (this.offset - rad);
+ a.t -= (this.offset - rad);
+ a.l += 1;
+ a.w -= (this.offset + rad + 1);
+ a.h -= (this.offset + rad);
+ a.h += 1;
+ }
+ break;
+ };
+
+ this.adjusts = a;
+};
+
+Ext.Shadow.prototype = {
+ /**
+ * @cfg {String} mode
+ * The shadow display mode. Supports the following options:<div class="mdetail-params"><ul>
+ * <li><b><tt>sides</tt></b> : Shadow displays on both sides and bottom only</li>
+ * <li><b><tt>frame</tt></b> : Shadow displays equally on all four sides</li>
+ * <li><b><tt>drop</tt></b> : Traditional bottom-right drop shadow</li>
+ * </ul></div>
+ */
+ /**
+ * @cfg {String} offset
+ * The number of pixels to offset the shadow from the element (defaults to <tt>4</tt>)
+ */
+ offset: 4,
+
+ // private
+ defaultMode: "drop",
+
+ /**
+ * Displays the shadow under the target element
+ * @param {Mixed} targetEl The id or element under which the shadow should display
+ */
+ show : function(target){
+ target = Ext.get(target);
+ if(!this.el){
+ this.el = Ext.Shadow.Pool.pull();
+ 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.realign(
+ target.getLeft(true),
+ target.getTop(true),
+ target.getWidth(),
+ target.getHeight()
+ );
+ this.el.dom.style.display = "block";
+ },
+
+ /**
+ * Returns true if the shadow is visible, else false
+ */
+ isVisible : function(){
+ return this.el ? true : false;
+ },
+
+ /**
+ * Direct alignment when values are already available. Show must be called at least once before
+ * calling this method to ensure it is initialized.
+ * @param {Number} left The target element left position
+ * @param {Number} top The target element top position
+ * @param {Number} width The target element width
+ * @param {Number} height The target element height
+ */
+ 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){
+ s.width = sws;
+ s.height = shs;
+ if(!Ext.isIE){
+ var cn = d.childNodes;
+ var 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";
+ }
+ }
+ },
+
+ /**
+ * Hides this shadow
+ */
+ hide : function(){
+ if(this.el){
+ this.el.dom.style.display = "none";
+ Ext.Shadow.Pool.push(this.el);
+ delete this.el;
+ }
+ },
+
+ /**
+ * Adjust the z-index of this shadow
+ * @param {Number} zindex The new z-index
+ */
+ setZIndex : function(z){
+ this.zIndex = z;
+ 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 ?
+ '<div class="x-ie-shadow"></div>' :
+ '<div class="x-shadow"><div class="xst"><div class="xstl"></div><div class="xstc"></div><div class="xstr"></div></div><div class="xsc"><div class="xsml"></div><div class="xsmc"></div><div class="xsmr"></div></div><div class="xsb"><div class="xsbl"></div><div class="xsbc"></div><div class="xsbr"></div></div></div>';
+ return {
+ pull : function(){
+ var sh = p.shift();
+ if(!sh){
+ sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, markup));
+ sh.autoBoxAdjust = false;
+ }
+ return sh;
+ },
+
+ push : function(sh){
+ p.push(sh);
+ }
+ };
+}();/**
+ * @class Ext.BoxComponent
+ * @extends Ext.Component
+ * <p>Base class for any {@link Ext.Component Component} that is to be sized as a box, using width and height.</p>
+ * <p>BoxComponent provides automatic box model adjustments for sizing and positioning and will work correctly
+ * within the Component rendering model.</p>
+ * <p>A BoxComponent may be created as a custom Component which encapsulates any HTML element, either a pre-existing
+ * element, or one that is created to your specifications at render time. Usually, to participate in layouts,
+ * a Component will need to be a <b>Box</b>Component in order to have its width and height managed.</p>
+ * <p>To use a pre-existing element as a BoxComponent, configure it so that you preset the <b>el</b> property to the
+ * element to reference:<pre><code>
+var pageHeader = new Ext.BoxComponent({
+ el: 'my-header-div'
+});</code></pre>
+ * This may then be {@link Ext.Container#add added} to a {@link Ext.Container Container} as a child item.</p>
+ * <p>To create a BoxComponent based around a HTML element to be created at render time, use the
+ * {@link Ext.Component#autoEl autoEl} config option which takes the form of a
+ * {@link Ext.DomHelper DomHelper} specification:<pre><code>
+var myImage = new Ext.BoxComponent({
+ autoEl: {
+ tag: 'img',
+ src: '/images/my-image.jpg'
+ }
+});</code></pre></p>
+ * @constructor
+ * @param {Ext.Element/String/Object} config The configuration options.
+ * @xtype box
+ */
+Ext.BoxComponent = Ext.extend(Ext.Component, {
+
+ // Configs below are used for all Components when rendered by BoxLayout.
+ /**
+ * @cfg {Number} flex
+ * <p><b>Note</b>: this config is only used when this Component is rendered
+ * by a Container which has been configured to use a <b>{@link Ext.layout.BoxLayout BoxLayout}.</b>
+ * Each child Component with a <code>flex</code> property will be flexed either vertically (by a VBoxLayout)
+ * or horizontally (by an HBoxLayout) according to the item's <b>relative</b> <code>flex</code> value
+ * compared to the sum of all Components with <code>flex</flex> value specified. Any child items that have
+ * either a <code>flex = 0</code> or <code>flex = undefined</code> 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 <p><b>Note</b>: this config is only used when this Component is rendered
+ * by a Container which has been configured to use an <b>{@link Ext.layout.AnchorLayout AnchorLayout} (or subclass thereof).</b>
+ * based layout manager, for example:<div class="mdetail-params"><ul>
+ * <li>{@link Ext.form.FormPanel}</li>
+ * <li>specifying <code>layout: 'anchor' // or 'form', or 'absolute'</code></li>
+ * </ul></div></p>
+ * <p>See {@link Ext.layout.AnchorLayout}.{@link Ext.layout.AnchorLayout#anchor anchor} also.</p>
+ */
+ // tabTip config is used when a BoxComponent is a child of a TabPanel
+ /**
+ * @cfg {String} tabTip
+ * <p><b>Note</b>: this config is only used when this BoxComponent is a child item of a TabPanel.</p>
+ * A string to be used as innerHTML (html tags are accepted) to show in a tooltip when mousing over
+ * the associated tab selector element. {@link Ext.QuickTips}.init()
+ * must be called in order for the tips to render.
+ */
+ // Configs below are used for all Components when rendered by BorderLayout.
+ /**
+ * @cfg {String} region <p><b>Note</b>: this config is only used when this BoxComponent is rendered
+ * by a Container which has been configured to use the <b>{@link Ext.layout.BorderLayout BorderLayout}</b>
+ * layout manager (e.g. specifying <tt>layout:'border'</tt>).</p><br>
+ * <p>See {@link Ext.layout.BorderLayout} also.</p>
+ */
+ // margins config is used when a BoxComponent is rendered by BorderLayout or BoxLayout.
+ /**
+ * @cfg {Object} margins <p><b>Note</b>: this config is only used when this BoxComponent is rendered
+ * by a Container which has been configured to use the <b>{@link Ext.layout.BorderLayout BorderLayout}</b>
+ * or one of the two <b>{@link Ext.layout.BoxLayout BoxLayout} subclasses.</b></p>
+ * <p>An object containing margins to apply to this BoxComponent in the
+ * format:</p><pre><code>
+{
+ top: (top margin),
+ right: (right margin),
+ bottom: (bottom margin),
+ left: (left margin)
+}</code></pre>
+ * <p>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:</p>
+ * <p><div class="mdetail-params"><ul>
+ * <li>If there is only one value, it applies to all sides.</li>
+ * <li>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.</li>
+ * <li>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.</li>
+ * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
+ * </ul></div></p>
+ * <p>Defaults to:</p><pre><code>
+ * {top:0, right:0, bottom:0, left:0}
+ * </code></pre>
+ */
+ /**
+ * @cfg {Number} x
+ * The local x (left) coordinate for this component if contained within a positioning container.
+ */
+ /**
+ * @cfg {Number} y
+ * The local y (top) coordinate for this component if contained within a positioning container.
+ */
+ /**
+ * @cfg {Number} pageX
+ * The page level x coordinate for this component if contained within a positioning container.
+ */
+ /**
+ * @cfg {Number} pageY
+ * The page level y coordinate for this component if contained within a positioning container.
+ */
+ /**
+ * @cfg {Number} height
+ * The height of this component in pixels (defaults to auto).
+ * <b>Note</b> to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
+ */
+ /**
+ * @cfg {Number} width
+ * The width of this component in pixels (defaults to auto).
+ * <b>Note</b> to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
+ */
+ /**
+ * @cfg {Number} boxMinHeight
+ * <p>The minimum value in pixels which this BoxComponent will set its height to.</p>
+ * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
+ */
+ /**
+ * @cfg {Number} boxMinWidth
+ * <p>The minimum value in pixels which this BoxComponent will set its width to.</p>
+ * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
+ */
+ /**
+ * @cfg {Number} boxMaxHeight
+ * <p>The maximum value in pixels which this BoxComponent will set its height to.</p>
+ * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
+ */
+ /**
+ * @cfg {Number} boxMaxWidth
+ * <p>The maximum value in pixels which this BoxComponent will set its width to.</p>
+ * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
+ */
+ /**
+ * @cfg {Boolean} autoHeight
+ * <p>True to use height:'auto', false to use fixed height (or allow it to be managed by its parent
+ * Container's {@link Ext.Container#layout layout manager}. Defaults to false.</p>
+ * <p><b>Note</b>: Although many components inherit this config option, not all will
+ * function as expected with a height of 'auto'. Setting autoHeight:true means that the
+ * browser will manage height based on the element's contents, and that Ext will not manage it at all.</p>
+ * <p>If the <i>browser</i> is managing the height, be aware that resizes performed by the browser in response
+ * to changes within the structure of the Component cannot be detected. Therefore changes to the height might
+ * result in elements needing to be synchronized with the new height. Example:</p><pre><code>
+var w = new Ext.Window({
+ title: 'Window',
+ width: 600,
+ autoHeight: true,
+ items: {
+ title: 'Collapse Me',
+ height: 400,
+ collapsible: true,
+ border: false,
+ listeners: {
+ beforecollapse: function() {
+ w.el.shadow.hide();
+ },
+ beforeexpand: function() {
+ w.el.shadow.hide();
+ },
+ collapse: function() {
+ w.syncShadow();
+ },
+ expand: function() {
+ w.syncShadow();
+ }
+ }
+ }
+}).show();
+</code></pre>
+ */
+ /**
+ * @cfg {Boolean} autoWidth
+ * <p>True to use width:'auto', false to use fixed width (or allow it to be managed by its parent
+ * Container's {@link Ext.Container#layout layout manager}. Defaults to false.</p>
+ * <p><b>Note</b>: Although many components inherit this config option, not all will
+ * function as expected with a width of 'auto'. Setting autoWidth:true means that the
+ * browser will manage width based on the element's contents, and that Ext will not manage it at all.</p>
+ * <p>If the <i>browser</i> is managing the width, be aware that resizes performed by the browser in response
+ * to changes within the structure of the Component cannot be detected. Therefore changes to the width might
+ * result in elements needing to be synchronized with the new width. For example, where the target element is:</p><pre><code>
+<div id='grid-container' style='margin-left:25%;width:50%'></div>
+</code></pre>
+ * A Panel rendered into that target element must listen for browser window resize in order to relay its
+ * child items when the browser changes its width:<pre><code>
+var myPanel = new Ext.Panel({
+ renderTo: 'grid-container',
+ monitorResize: true, // relay on browser resize
+ title: 'Panel',
+ height: 400,
+ autoWidth: true,
+ layout: 'hbox',
+ layoutConfig: {
+ align: 'stretch'
+ },
+ defaults: {
+ flex: 1
+ },
+ items: [{
+ title: 'Box 1',
+ }, {
+ title: 'Box 2'
+ }, {
+ title: 'Box 3'
+ }],
+});
+</code></pre>
+ */
+ /**
+ * @cfg {Boolean} autoScroll
+ * <code>true</code> to use overflow:'auto' on the components layout element and show scroll bars automatically when
+ * necessary, <code>false</code> to clip any overflowing content (defaults to <code>false</code>).
+ */
+
+ /* // private internal config
+ * {Boolean} deferHeight
+ * True to defer height calculations to an external component, false to allow this component to set its own
+ * height (defaults to false).
+ */
+
+ // private
+ initComponent : function(){
+ Ext.BoxComponent.superclass.initComponent.call(this);
+ this.addEvents(
+ /**
+ * @event resize
+ * Fires after the component is resized.
+ * @param {Ext.Component} this
+ * @param {Number} adjWidth The box-adjusted width that was set
+ * @param {Number} adjHeight The box-adjusted height that was set
+ * @param {Number} rawWidth The width that was originally specified
+ * @param {Number} rawHeight The height that was originally specified
+ */
+ 'resize',
+ /**
+ * @event move
+ * Fires after the component is moved.
+ * @param {Ext.Component} this
+ * @param {Number} x The new x position
+ * @param {Number} y The new y position
+ */
+ 'move'
+ );
+ },
+
+ // private, set in afterRender to signify that the component has been rendered
+ boxReady : false,
+ // private, used to defer height settings to subclasses
+ deferHeight: false,
+
+ /**
+ * Sets the width and height of this BoxComponent. This method fires the {@link #resize} event. This method can accept
+ * either width and height as separate arguments, or you can pass a size object like <code>{width:10, height:20}</code>.
+ * @param {Mixed} width The new width to set. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS width style.</li>
+ * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
+ * <li><code>undefined</code> to leave the width unchanged.</li>
+ * </ul></div>
+ * @param {Mixed} height The new height to set (not required if a size object is passed as the first arg).
+ * This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
+ * <li><code>undefined</code> to leave the height unchanged.</li>
+ * </ul></div>
+ * @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.height = h;
+ return this;
+ }
+
+ // prevent recalcs when not needed
+ if(this.cacheSizes !== false && this.lastSize && this.lastSize.width == w && this.lastSize.height == h){
+ return this;
+ }
+ this.lastSize = {width: w, height: h};
+ 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
+ rz = this.getResizeEl();
+ if(!this.deferHeight && aw !== undefined && ah !== undefined){
+ rz.setSize(aw, ah);
+ }else if(!this.deferHeight && ah !== undefined){
+ rz.setHeight(ah);
+ }else if(aw !== undefined){
+ rz.setWidth(aw);
+ }
+ this.onResize(aw, ah, w, h);
+ this.fireEvent('resize', this, aw, ah, w, h);
+ }
+ return this;
+ },
+
+ /**
+ * Sets the width of the component. This method fires the {@link #resize} event.
+ * @param {Mixed} width The new width to set. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS width style.</li>
+ * </ul></div>
+ * @return {Ext.BoxComponent} this
+ */
+ setWidth : function(width){
+ return this.setSize(width);
+ },
+
+ /**
+ * Sets the height of the component. This method fires the {@link #resize} event.
+ * @param {Mixed} height The new height to set. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS height style.</li>
+ * <li><i>undefined</i> to leave the height unchanged.</li>
+ * </ul></div>
+ * @return {Ext.BoxComponent} this
+ */
+ setHeight : function(height){
+ return this.setSize(undefined, height);
+ },
+
+ /**
+ * Gets the current size of the component's underlying element.
+ * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
+ */
+ getSize : function(){
+ return this.getResizeEl().getSize();
+ },
+
+ /**
+ * Gets the current width of the component's underlying element.
+ * @return {Number}
+ */
+ getWidth : function(){
+ return this.getResizeEl().getWidth();
+ },
+
+ /**
+ * Gets the current height of the component's underlying element.
+ * @return {Number}
+ */
+ getHeight : function(){
+ return this.getResizeEl().getHeight();
+ },
+
+ /**
+ * Gets the current size of the component's underlying element, including space taken by its margins.
+ * @return {Object} An object containing the element's size {width: (element width + left/right margins), height: (element height + top/bottom margins)}
+ */
+ getOuterSize : function(){
+ var el = this.getResizeEl();
+ return {width: el.getWidth() + el.getMargins('lr'),
+ height: el.getHeight() + el.getMargins('tb')};
+ },
+
+ /**
+ * Gets the current XY position of the component's underlying element.
+ * @param {Boolean} local (optional) If true the element's left and top are returned instead of page XY (defaults to false)
+ * @return {Array} The XY position of the element (e.g., [100, 200])
+ */
+ getPosition : function(local){
+ var el = this.getPositionEl();
+ if(local === true){
+ return [el.getLeft(true), el.getTop(true)];
+ }
+ return this.xy || el.getXY();
+ },
+
+ /**
+ * Gets the current box measurements of the component's underlying element.
+ * @param {Boolean} local (optional) If true the element's left and top are returned instead of page XY (defaults to false)
+ * @return {Object} box An object in the format {x, y, width, height}
+ */
+ getBox : function(local){
+ var pos = this.getPosition(local);
+ var s = this.getSize();
+ s.x = pos[0];
+ s.y = pos[1];
+ return s;
+ },
+
+ /**
+ * Sets the current box measurements of the component's underlying element.
+ * @param {Object} box An object in the format {x, y, width, height}
+ * @return {Ext.BoxComponent} this
+ */
+ updateBox : function(box){
+ this.setSize(box.width, box.height);
+ this.setPagePosition(box.x, box.y);
+ return this;
+ },
+
+ /**
+ * <p>Returns the outermost Element of this Component which defines the Components overall size.</p>
+ * <p><i>Usually</i> this will return the same Element as <code>{@link #getEl}</code>,
+ * but in some cases, a Component may have some more wrapping Elements around its main
+ * active Element.</p>
+ * <p>An example is a ComboBox. It is encased in a <i>wrapping</i> Element which
+ * contains both the <code><input></code> Element (which is what would be returned
+ * by its <code>{@link #getEl}</code> method, <i>and</i> the trigger button Element.
+ * This Element is returned as the <code>resizeEl</code>.
+ * @return {Ext.Element} The Element which is to be resized by size managing layouts.
+ */
+ getResizeEl : function(){
+ return this.resizeEl || 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;
+ },
+
+ /**
+ * Sets the left and top of the component. To set the page XY position instead, use {@link #setPagePosition}.
+ * This method fires the {@link #move} event.
+ * @param {Number} left The new left
+ * @param {Number} top The new top
+ * @return {Ext.BoxComponent} this
+ */
+ setPosition : function(x, y){
+ if(x && typeof x[1] == 'number'){
+ y = x[1];
+ x = x[0];
+ }
+ this.x = x;
+ this.y = y;
+ if(!this.boxReady){
+ return this;
+ }
+ var adj = this.adjustPosition(x, y);
+ var ax = adj.x, ay = adj.y;
+
+ var el = this.getPositionEl();
+ if(ax !== undefined || ay !== undefined){
+ if(ax !== undefined && ay !== undefined){
+ el.setLeftTop(ax, ay);
+ }else if(ax !== undefined){
+ el.setLeft(ax);
+ }else if(ay !== undefined){
+ el.setTop(ay);
+ }
+ this.onPosition(ax, ay);
+ this.fireEvent('move', this, ax, ay);
+ }
+ return this;
+ },
+
+ /**
+ * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
+ * This method fires the {@link #move} event.
+ * @param {Number} x The new x position
+ * @param {Number} y The new y position
+ * @return {Ext.BoxComponent} this
+ */
+ setPagePosition : function(x, y){
+ if(x && typeof x[1] == 'number'){
+ y = x[1];
+ x = x[0];
+ }
+ this.pageX = x;
+ this.pageY = y;
+ if(!this.boxReady){
+ return;
+ }
+ if(x === undefined || y === undefined){ // cannot translate undefined points
+ return;
+ }
+ var p = this.getPositionEl().translatePoints(x, y);
+ this.setPosition(p.left, p.top);
+ return this;
+ },
+
+ // private
+ afterRender : function(){
+ Ext.BoxComponent.superclass.afterRender.call(this);
+ if(this.resizeEl){
+ this.resizeEl = Ext.get(this.resizeEl);
+ }
+ if(this.positionEl){
+ 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);
+ }else if(this.pageX || this.pageY){
+ this.setPagePosition(this.pageX, this.pageY);
+ }
+ },
+
+ /**
+ * Force the component's size to recalculate based on the underlying element's current height and width.
+ * @return {Ext.BoxComponent} this
+ */
+ syncSize : function(){
+ delete this.lastSize;
+ this.setSize(this.autoWidth ? undefined : this.getResizeEl().getWidth(), this.autoHeight ? undefined : this.getResizeEl().getHeight());
+ return this;
+ },
+
+ /* // protected
+ * Called after the component is resized, this method is empty by default but can be implemented by any
+ * subclass that needs to perform custom logic after a resize occurs.
+ * @param {Number} adjWidth The box-adjusted width that was set
+ * @param {Number} adjHeight The box-adjusted height that was set
+ * @param {Number} rawWidth The width that was originally specified
+ * @param {Number} rawHeight The height that was originally specified
+ */
+ onResize : function(adjWidth, adjHeight, rawWidth, rawHeight){
+ },
+
+ /* // protected
+ * Called after the component is moved, this method is empty by default but can be implemented by any
+ * subclass that needs to perform custom logic after a move occurs.
+ * @param {Number} x The new x position
+ * @param {Number} y The new y position
+ */
+ onPosition : function(x, y){
+
+ },
+
+ // private
+ adjustSize : function(w, h){
+ if(this.autoWidth){
+ w = 'auto';
+ }
+ if(this.autoHeight){
+ h = 'auto';
+ }
+ return {width : w, height: h};
+ },
+
+ // private
+ adjustPosition : function(x, y){
+ return {x : x, y: y};
+ }
+});
+Ext.reg('box', Ext.BoxComponent);
+
+
+/**
+ * @class Ext.Spacer
+ * @extends Ext.BoxComponent
+ * <p>Used to provide a sizable space in a layout.</p>
+ * @constructor
+ * @param {Object} config
+ */
+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).
+ * <br><br>
+ * Usage:
+ * <pre><code>
+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);
+</code></pre>
+ * @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 <i>undefined</i>, 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"));
+ document.body.appendChild(proxy.dom);
+ proxy.unselectable();
+ var cls = 'x-splitbar-proxy';
+ proxy.addClass(cls + ' ' + (dir == Ext.SplitBar.HORIZONTAL ? cls +'-h' : cls + '-v'));
+ 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
+ * <p>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.</p>
+ *
+ * <p>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
+ * <code><b>{@link Ext.Component#autoEl autoEl}</b></code> config option. This is a useful technique when creating
+ * embedded {@link Ext.layout.ColumnLayout column} layouts inside {@link Ext.form.FormPanel FormPanels}
+ * for example.</p>
+ *
+ * <p>The code below illustrates both how to explicitly create a Container, and how to implicitly
+ * create one using the <b><code>'container'</code></b> xtype:<pre><code>
+// 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'
+ }
+ }]
+});</code></pre></p>
+ *
+ * <p><u><b>Layout</b></u></p>
+ * <p>Container classes delegate the rendering of child Components to a layout
+ * manager class which must be configured into the Container using the
+ * <code><b>{@link #layout}</b></code> configuration property.</p>
+ * <p>When either specifying child <code>{@link #items}</code> 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
+ * <b><code>{@link #layout}</code></b> 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 <b>does not apply any sizing</b> at all.</p>
+ * <p>A common mistake is when a developer neglects to specify a
+ * <b><code>{@link #layout}</code></b> (e.g. widgets like GridPanels or
+ * TreePanels are added to Containers for which no <code><b>{@link #layout}</b></code>
+ * 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.</p>
+ * <p>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:<pre><code>
+// 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);
+ * </code></pre></p>
+ * <p>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.
+ * <p><b><u>Overnesting is a common problem</u></b>.
+ * An example of overnesting occurs when a GridPanel is added to a TabPanel
+ * by wrapping the GridPanel <i>inside</i> a wrapping Panel (that has no
+ * <code><b>{@link #layout}</b></code> specified) and then add that wrapping Panel
+ * to the TabPanel. The point to realize is that a GridPanel <b>is</b> a
+ * Component which can be added directly to a Container. If the wrapping Panel
+ * has no <code><b>{@link #layout}</b></code> configuration, then the overnested
+ * GridPanel will not be sized as expected.<p>
+ *
+ * <p><u><b>Adding via remote configuration</b></u></p>
+ *
+ * <p>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:
+ * </p><pre><code>
+// 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");
+ }
+});
+</code></pre>
+ * <p>The server script needs to return an executable Javascript statement which, when processed
+ * using <code>eval()</code>, will return either a config object with an {@link Ext.Component#xtype xtype},
+ * or an instantiated Component. The server might return this for example:</p><pre><code>
+(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
+})();
+</code></pre>
+ * <p>When the above code fragment is passed through the <code>eval</code> 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.</p>
+ * <p>Note: since the code above is <i>generated</i> by a server script, the <code>baseParams</code> 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.</p>
+ *
+ * @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 <code>{@link #layout}</code> and should not need
+ * to be set manually.
+ */
+ /**
+ * @cfg {String/Object} layout
+ * <p><b>*Important</b>: In order for child items to be correctly sized and
+ * positioned, typically a layout manager <b>must</b> be specified through
+ * the <code>layout</code> configuration option.</p>
+ * <br><p>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:</p><pre><code>
+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();
+ * </code></pre>
+ * <p>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 <code>layout:'form'</code>). Other specific
+ * purpose classes internally specify/manage their internal layout (e.g.
+ * GridPanel, TabPanel, TreePanel, Toolbar, Menu, etc.).</p>
+ * <br><p><b><code>layout</code></b> may be specified as either as an Object or
+ * as a String:</p><div><ul class="mdetail-params">
+ *
+ * <li><u>Specify as an Object</u></li>
+ * <div><ul class="mdetail-params">
+ * <li>Example usage:</li>
+<pre><code>
+layout: {
+ type: 'vbox',
+ padding: '5',
+ align: 'left'
+}
+</code></pre>
+ *
+ * <li><code><b>type</b></code></li>
+ * <br/><p>The layout type to be used for this container. If not specified,
+ * a default {@link Ext.layout.ContainerLayout} will be created and used.</p>
+ * <br/><p>Valid layout <code>type</code> values are:</p>
+ * <div class="sub-desc"><ul class="mdetail-params">
+ * <li><code><b>{@link Ext.layout.AbsoluteLayout absolute}</b></code></li>
+ * <li><code><b>{@link Ext.layout.AccordionLayout accordion}</b></code></li>
+ * <li><code><b>{@link Ext.layout.AnchorLayout anchor}</b></code></li>
+ * <li><code><b>{@link Ext.layout.ContainerLayout auto}</b></code> <b>Default</b></li>
+ * <li><code><b>{@link Ext.layout.BorderLayout border}</b></code></li>
+ * <li><code><b>{@link Ext.layout.CardLayout card}</b></code></li>
+ * <li><code><b>{@link Ext.layout.ColumnLayout column}</b></code></li>
+ * <li><code><b>{@link Ext.layout.FitLayout fit}</b></code></li>
+ * <li><code><b>{@link Ext.layout.FormLayout form}</b></code></li>
+ * <li><code><b>{@link Ext.layout.HBoxLayout hbox}</b></code></li>
+ * <li><code><b>{@link Ext.layout.MenuLayout menu}</b></code></li>
+ * <li><code><b>{@link Ext.layout.TableLayout table}</b></code></li>
+ * <li><code><b>{@link Ext.layout.ToolbarLayout toolbar}</b></code></li>
+ * <li><code><b>{@link Ext.layout.VBoxLayout vbox}</b></code></li>
+ * </ul></div>
+ *
+ * <li>Layout specific configuration properties</li>
+ * <br/><p>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 <code>type</code>
+ * specified.</p>
+ *
+ * </ul></div>
+ *
+ * <li><u>Specify as a String</u></li>
+ * <div><ul class="mdetail-params">
+ * <li>Example usage:</li>
+<pre><code>
+layout: 'vbox',
+layoutConfig: {
+ padding: '5',
+ align: 'left'
+}
+</code></pre>
+ * <li><code><b>layout</b></code></li>
+ * <br/><p>The layout <code>type</code> to be used for this container (see list
+ * of valid layout type values above).</p><br/>
+ * <li><code><b>{@link #layoutConfig}</b></code></li>
+ * <br/><p>Additional layout specific configuration properties. For complete
+ * details regarding the valid config options for each layout type, see the
+ * layout class corresponding to the <code>layout</code> specified.</p>
+ * </ul></div></ul></div>
+ */
+ /**
+ * @cfg {Object} layoutConfig
+ * This is a config object containing properties specific to the chosen
+ * <b><code>{@link #layout}</code></b> if <b><code>{@link #layout}</code></b>
+ * has been specified as a <i>string</i>.</p>
+ */
+ /**
+ * @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 <code>50</code>.
+ */
+ 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
+ * <pre><b>** IMPORTANT</b>: be sure to <b>{@link #layout specify a <code>layout</code>} if needed ! **</b></pre>
+ * <p>A single item, or an array of child Components to be added to this container,
+ * for example:</p>
+ * <pre><code>
+// specifying a single item
+items: {...},
+layout: 'fit', // specify a layout!
+
+// specifying multiple items
+items: [{...}, {...}],
+layout: 'anchor', // specify a layout!
+ * </code></pre>
+ * <p>Each item may be:</p>
+ * <div><ul class="mdetail-params">
+ * <li>any type of object based on {@link Ext.Component}</li>
+ * <li>a fully instanciated object or</li>
+ * <li>an object literal that:</li>
+ * <div><ul class="mdetail-params">
+ * <li>has a specified <code>{@link Ext.Component#xtype xtype}</code></li>
+ * <li>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}.</li>
+ * <li>If an <code>{@link Ext.Component#xtype xtype}</code> is not explicitly
+ * specified, the {@link #defaultType} for that Container is used.</li>
+ * <li>will be "lazily instanciated", avoiding the overhead of constructing a fully
+ * instanciated Component object</li>
+ * </ul></div></ul></div>
+ * <p><b>Notes</b>:</p>
+ * <div><ul class="mdetail-params">
+ * <li>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.</li>
+ * <li>Do not specify <code>{@link Ext.Panel#contentEl contentEl}</code>/
+ * <code>{@link Ext.Panel#html html}</code> with <code>items</code>.</li>
+ * </ul></div>
+ */
+ /**
+ * @cfg {Object|Function} defaults
+ * <p>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.</p>
+ * <p>If an added item is a config object, and <b>not</b> an instantiated Component, then the default properties are
+ * unconditionally applied. If the added item <b>is</b> an instantiated Component, then the default properties are
+ * applied conditionally so as not to override existing properties in the item.</p>
+ * <p>If the defaults option is specified as a function, then the function will be called using this Container as the
+ * scope (<code>this</code> 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.</p>
+ * <p>For example, to automatically apply padding to the body of each of a set of
+ * contained {@link Ext.Panel} items, you could pass: <code>defaults: {bodyStyle:'padding:15px'}</code>.</p>
+ * <p>Usage:</p><pre><code>
+defaults: { // defaults are applied to items, not the container
+ autoScroll:true
+},
+items: [
+ {
+ xtype: 'panel', // defaults <b>do not</b> 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 <b>do</b> have precedence over options
+ id: 'panel2', // options in components, so the defaults
+ autoScroll: false // will be applied here, panel2 will be autoScroll:true.
+ })
+]
+ * </code></pre>
+ */
+
+
+ /** @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
+ * <p>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.</p>
+ * <p>Defaults to <code>'panel'</code>, except {@link Ext.menu.Menu} which defaults to <code>'menuitem'</code>,
+ * and {@link Ext.Toolbar} and {@link Ext.ButtonGroup} which default to <code>'button'</code>.</p>
+ */
+ defaultType : 'panel',
+
+ /** @cfg {String} resizeEvent
+ * The event to listen to for resizing in layouts. Defaults to <code>'resize'</code>.
+ */
+ resizeEvent: 'resize',
+
+ /**
+ * @cfg {Array} bubbleEvents
+ * <p>An array of events that, when fired, should be bubbled to any parent container.
+ * See {@link Ext.util.Observable#enableBubble}.
+ * Defaults to <code>['add', 'remove']</code>.
+ */
+ 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'
+ );
+
+ /**
+ * 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(){
+ // Render this Container, this should be done before setLayout is called which
+ // will hook onResize
+ Ext.Container.superclass.afterRender.call(this);
+ if(!this.layout){
+ this.layout = 'auto';
+ }
+ if(Ext.isObject(this.layout) && !this.layout.layout){
+ this.layoutConfig = this.layout;
+ this.layout = this.layoutConfig.type;
+ }
+ if(Ext.isString(this.layout)){
+ this.layout = new Ext.Container.LAYOUTS[this.layout.toLowerCase()](this.layoutConfig);
+ }
+ this.setLayout(this.layout);
+
+ // If a CardLayout, the active item set
+ if(this.activeItem !== undefined){
+ var item = this.activeItem;
+ delete this.activeItem;
+ this.layout.setActiveItem(item);
+ }
+
+ // If we have no ownerCt, render and size all children
+ if(!this.ownerCt){
+ 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]);
+ }
+ },
+
+ /**
+ * <p>Returns the Element to be used to contain the child Components of this Container.</p>
+ * <p>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.</p>
+ * @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();
+ },
+
+ /**
+ * <p>Adds {@link Ext.Component Component}(s) to this Container.</p>
+ * <br><p><b>Description</b></u> :
+ * <div><ul class="mdetail-params">
+ * <li>Fires the {@link #beforeadd} event before adding</li>
+ * <li>The Container's {@link #defaults default config values} will be applied
+ * accordingly (see <code>{@link #defaults}</code> for details).</li>
+ * <li>Fires the {@link #add} event after the component has been added.</li>
+ * </ul></div>
+ * <br><p><b>Notes</b></u> :
+ * <div><ul class="mdetail-params">
+ * <li>If the Container is <i>already rendered</i> when <code>add</code>
+ * 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
+ * <code>add</code> multiple child components if needed while only refreshing the layout
+ * once. For example:<pre><code>
+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
+ * </code></pre></li>
+ * <li><i>Warning:</i> 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.</li>
+ * </ul></div>
+ * @param {...Object/Array} component
+ * <p>Either one or more Components to add or an Array of Components to add. See
+ * <code>{@link #items}</code> for additional information.</p>
+ * @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;
+ }
+ 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
+ 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.<br><br>
+ * Ext uses lazy rendering, and will only render the inserted Component should
+ * it become necessary.<br><br>
+ * 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.<br><br>
+ * 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){
+ this.initItems();
+ var a = arguments, len = a.length;
+ if(len > 2){
+ var result = [];
+ for(var i = len-1; i >= 1; --i) {
+ result.push(this.insert(index, a[i]));
+ }
+ return result;
+ }
+ var 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
+ 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, d);
+ }else{
+ Ext.apply(c, d);
+ }
+ }
+ return c;
+ },
+
+ // private
+ onBeforeAdd : function(item){
+ if(item.ownerCt){
+ item.ownerCt.remove(item, false);
+ }
+ if(this.hideBorders === true){
+ item.border = (item.border === true);
+ }
+ },
+
+ /**
+ * 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
+ doRemove: function(c, autoDestroy){
+ var l = this.layout,
+ hasLayout = l && this.rendered;
+
+ if(hasLayout){
+ l.onRemove(c);
+ }
+ this.items.remove(c);
+ c.onRemoved();
+ this.onRemove(c);
+ if(autoDestroy === true || (autoDestroy !== false && this.autoDestroy)){
+ c.destroy();
+ }
+ if(hasLayout){
+ l.afterRemove(c);
+ }
+ },
+
+ /**
+ * Removes all components from this container.
+ * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
+ * Defaults to the value of this Container's {@link #autoDestroy} config.
+ * @return {Array} Array of the destroyed components
+ */
+ 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;
+ },
+
+ /**
+ * Examines this container's <code>{@link #items}</code> <b>property</b>
+ * and gets a direct child component of this container.
+ * @param {String/Number} comp This parameter may be any of the following:
+ * <div><ul class="mdetail-params">
+ * <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
+ * or <code>{@link Ext.Component#id id}</code> of the child component </li>
+ * <li>a <b><code>Number</code></b> : representing the position of the child component
+ * within the <code>{@link #items}</code> <b>property</b></li>
+ * </ul></div>
+ * <p>For 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, 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;
+ },
+
+ /**
+ * 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.
+ */
+
+ // private
+ canLayout : function() {
+ var el = this.getVisibilityEl();
+ return el && el.dom && !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.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
+ shouldBufferLayout: function(){
+ /*
+ * Returns true if the container should buffer a layout.
+ * This is true only if the container has previously been laid out
+ * and has a parent container that is pending a layout.
+ */
+ var hl = this.hasLayout;
+ if(this.ownerCt){
+ // Only ever buffer if we've laid out the first time and we have one pending.
+ return hl ? !this.hasLayoutPending() : false;
+ }
+ // Never buffer initial layout
+ return hl;
+ },
+
+ // private
+ hasLayoutPending: function(){
+ // Traverse hierarchy to see if any parent container has a pending layout.
+ var pending = false;
+ this.ownerCt.bubble(function(c){
+ if(c.layoutPending){
+ pending = true;
+ return false;
+ }
+ });
+ return pending;
+ },
+
+ onShow : function(){
+ // removes css classes that were added to hide
+ Ext.Container.superclass.onShow.call(this);
+ // If we were sized during the time we were hidden, layout.
+ if(Ext.isDefined(this.deferLayout)){
+ delete this.deferLayout;
+ 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.AutoLayout(this.layoutConfig);
+ this.setLayout(layout);
+ }
+ return this.layout;
+ },
+
+ // private
+ 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);
+ },
+
+ /**
+ * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope (<i>this</i>) 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 (<i>this</i>) 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
+ * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Container#layout layout}</b></tt>
+ * configuration property. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
+ */
+Ext.layout.ContainerLayout = Ext.extend(Object, {
+ /**
+ * @cfg {String} extraCls
+ * <p>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.</p>
+ * <p><b>Note</b>: <tt>extraCls</tt> defaults to <tt>''</tt> except for the following classes
+ * which assign a value by default:
+ * <div class="mdetail-params"><ul>
+ * <li>{@link Ext.layout.AbsoluteLayout Absolute Layout} : <tt>'x-abs-layout-item'</tt></li>
+ * <li>{@link Ext.layout.Box Box Layout} : <tt>'x-box-item'</tt></li>
+ * <li>{@link Ext.layout.ColumnLayout Column Layout} : <tt>'x-column'</tt></li>
+ * </ul></div>
+ * To configure the above Classes with an extra CSS class append to the default. For example,
+ * for ColumnLayout:<pre><code>
+ * extraCls: 'x-column custom-class'
+ * </code></pre>
+ * </p>
+ */
+ /**
+ * @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, <pre><code>
+ * if(myPanel.layout.activeItem.id == 'item-1') { ... }
+ * </code></pre>
+ * <tt>activeItem</tt> 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'});
+ }
+ }
+ ret = target ? target.getViewSize(viewFlag) : {};
+ for (i = 0 ; i < tLen ; i++) {
+ c = tChildren[i];
+ e = Ext.get(c);
+ if (e) {
+ e.setStyle({display: d[i]});
+ }
+ }
+ return ret;
+ },
+
+ // Placeholder for the derived layouts
+ getLayoutTargetSize : Ext.EmptyFn,
+
+ // private
+ layout : function(){
+ var 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
+ onLayout : function(ct, target){
+ this.renderAll(ct, target);
+ },
+
+ // private
+ isValidParent : function(c, target){
+ return target && c.getPositionEl().dom.parentNode == (target.dom || target);
+ },
+
+ // private
+ 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
+ * 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, position);
+ } else if (!this.isValidParent(c, target)) {
+ if (Ext.isNumber(position)) {
+ position = target.dom.childNodes[position];
+ }
+
+ target.dom.insertBefore(c.getPositionEl().dom, position || null);
+ c.container = target;
+ this.configureItem(c, position);
+ }
+ }
+ },
+
+ // private.
+ // Get all rendered items to lay out.
+ getRenderedItems: function(ct){
+ var t = ct.getLayoutTarget(), cti = ct.items.items, len = cti.length, i, c, items = [];
+ for (i = 0; i < len; i++) {
+ if((c = cti[i]).rendered && this.isValidParent(c, t)){
+ items.push(c);
+ }
+ };
+ return items;
+ },
+
+ /**
+ * @private
+ * Applies extraCls and hides the item if renderHidden is true
+ */
+ configureItem: function(c, position){
+ if (this.extraCls) {
+ 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
+ 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
+ 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;
+ },
+
+ /**
+ * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
+ * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
+ * @param {Number|String} v The encoded margins
+ * @return {Object} An object with margin sizes for top, right, bottom and left
+ */
+ parseMargins : function(v){
+ if (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
+ };
+ },
+
+ /**
+ * 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
+ */
+ fieldTpl: (function() {
+ var t = 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>'
+ );
+ t.disableFormats = true;
+ return t.compile();
+ })(),
+
+ /*
+ * Destroys this layout. This is a template method that is empty by default, but should be implemented
+ * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
+ * @protected
+ */
+ destroy : function(){
+ // Stop any buffered layout tasks
+ if(this.resizeTask && this.resizeTask.cancel){
+ this.resizeTask.cancel();
+ }
+ if(!Ext.isEmpty(this.targetCls)){
+ var target = this.container.getLayoutTarget();
+ if(target){
+ target.removeClass(this.targetCls);
+ }
+ }
+ }
+});/**
+ * @class Ext.layout.AutoLayout
+ * <p>The AutoLayout is the default layout manager delegated by {@link Ext.Container} to
+ * render any child Components when no <tt>{@link Ext.Container#layout layout}</tt> is configured into
+ * a {@link Ext.Container Container}.</tt>. AutoLayout provides only a passthrough of any layout calls
+ * to any child containers.</p>
+ */
+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
+ * <p>This is a base class for layouts that contain <b>a single item</b> that automatically expands to fill the layout's
+ * container. This class is intended to be extended or created via the <tt>layout:'fit'</tt> {@link Ext.Container#layout}
+ * config, and should generally not need to be created directly via the new keyword.</p>
+ * <p>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:</p>
+ * <pre><code>
+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
+ }
+});
+</code></pre>
+ */
+Ext.layout.FitLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ // private
+ monitorResize:true,
+
+ type: 'fit',
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget();
+ if (!target) {
+ return {};
+ }
+ // Style Sized (scrollbars not included)
+ return target.getStyleSize();
+ },
+
+ // private
+ onLayout : function(ct, target){
+ Ext.layout.FitLayout.superclass.onLayout.call(this, ct, target);
+ if(!ct.collapsed){
+ this.setItemSize(this.activeItem || ct.items.itemAt(0), this.getLayoutTargetSize());
+ }
+ },
+
+ // 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
+ * <p>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.</p>
+ * <p>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.</p>
+ * <p>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:</p>
+ * <pre><code>
+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>'
+ }]
+});
+</code></pre>
+ */
+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,
+
+ type: 'card',
+
+ /**
+ * Sets the active (visible) item in the layout.
+ * @param {String/Number} item The string component id or numeric index of the item to activate
+ */
+ setActiveItem : function(item){
+ var ai = this.activeItem,
+ ct = this.container;
+ item = ct.getComponent(item);
+
+ // Is this a valid, different card?
+ if(item && ai != item){
+
+ // Changing cards, hide the current one
+ if(ai){
+ ai.hide();
+ if (ai.hidden !== true) {
+ return false;
+ }
+ ai.fireEvent('deactivate', ai);
+ }
+
+ var layout = item.doLayout && (this.layoutOnCardChange || !item.rendered);
+
+ // Change activeItem reference
+ this.activeItem = item;
+
+ // The container is about to get a recursive layout, remove any deferLayout reference
+ // because it will trigger a redundant layout.
+ delete item.deferLayout;
+
+ // Show the new component
+ item.show();
+
+ this.layout();
+
+ if(layout){
+ item.doLayout();
+ }
+ item.fireEvent('activate', item);
+ }
+ },
+
+ // 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
+ * <p>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
+ * <b><tt>{@link #anchor}</tt></b> rules.</p>
+ * <p>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.</p>
+ * <p>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 <b>anchorSize</b>.
+ * 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:</p>
+ * <pre><code>
+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%'
+ }]
+});
+ * </code></pre>
+ */
+Ext.layout.AnchorLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ /**
+ * @cfg {String} anchor
+ * <p>This configuation option is to be applied to <b>child <tt>items</tt></b> of a container managed by
+ * this layout (ie. configured with <tt>layout:'anchor'</tt>).</p><br/>
+ *
+ * <p>This value is what tells the layout how an item should be anchored to the container. <tt>items</tt>
+ * added to an AnchorLayout accept an anchoring-specific config property of <b>anchor</b> 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:<div class="mdetail-params"><ul>
+ *
+ * <li><b>Percentage</b> : Any value between 1 and 100, expressed as a percentage.<div class="sub-desc">
+ * 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:<pre><code>
+// 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
+ * </code></pre></div></li>
+ *
+ * <li><b>Offsets</b> : Any positive or negative integer value.<div class="sub-desc">
+ * 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:<pre><code>
+// 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
+ * </code></pre></div></li>
+ *
+ * <li><b>Sides</b> : Valid values are <tt>'right'</tt> (or <tt>'r'</tt>) and <tt>'bottom'</tt>
+ * (or <tt>'b'</tt>).<div class="sub-desc">
+ * 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.</div></li>
+ *
+ * <li><b>Mixed</b> : <div class="sub-desc">
+ * 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:
+ * <pre><code>
+anchor: '-50 75%'
+ * </code></pre></div></li>
+ *
+ *
+ * </ul></div>
+ */
+
+ // private
+ monitorResize : true,
+
+ type : 'anchor',
+
+ /**
+ * @cfg {String} defaultAnchor
+ *
+ * default anchor for all child container items applied if no anchor or specific width is set on the child item. Defaults to '100%'.
+ *
+ */
+ defaultAnchor : '100%',
+
+ parseAnchorRE : /^(r|right|b|bottom)$/i,
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget();
+ if (!target) {
+ return {};
+ }
+ // Style Sized (scrollbars not included)
+ return target.getStyleSize();
+ },
+
+ // private
+ onLayout : function(ct, target){
+ Ext.layout.AnchorLayout.superclass.onLayout.call(this, ct, target);
+ var size = this.getLayoutTargetSize();
+
+ var w = size.width, h = size.height;
+
+ if(w < 20 && h < 20){
+ return;
+ }
+
+ // find the container anchoring size
+ var aw, ah;
+ if(ct.anchorSize){
+ if(typeof ct.anchorSize == 'number'){
+ aw = ct.anchorSize;
+ }else{
+ aw = ct.anchorSize.width;
+ ah = ct.anchorSize.height;
+ }
+ }else{
+ aw = ct.initialConfig.width;
+ ah = ct.initialConfig.height;
+ }
+
+ var cs = this.getRenderedItems(ct), len = cs.length, i, c, a, cw, ch, el, vs, boxes = [];
+ for(i = 0; i < len; i++){
+ c = cs[i];
+ el = c.getPositionEl();
+
+ // If a child container item has no anchor and no specific width, set the child to the default anchor size
+ if (!c.anchor && c.items && !Ext.isNumber(c.width) && !(Ext.isIE6 && Ext.isStrict)){
+ c.anchor = this.defaultAnchor;
+ }
+
+ if(c.anchor){
+ a = c.anchorSpec;
+ if(!a){ // cache all anchor values
+ vs = c.anchor.split(' ');
+ c.anchorSpec = a = {
+ right: this.parseAnchor(vs[0], c.initialConfig.width, aw),
+ bottom: this.parseAnchor(vs[1], c.initialConfig.height, ah)
+ };
+ }
+ cw = a.right ? this.adjustWidthAnchor(a.right(w) - el.getMargins('lr'), c) : undefined;
+ ch = a.bottom ? this.adjustHeightAnchor(a.bottom(h) - el.getMargins('tb'), c) : undefined;
+
+ if(cw || ch){
+ boxes.push({
+ comp: c,
+ width: cw || undefined,
+ height: ch || undefined
+ });
+ }
+ }
+ }
+ for (i = 0, len = boxes.length; i < len; i++) {
+ c = boxes[i];
+ c.comp.setSize(c.width, c.height);
+ }
+ },
+
+ // private
+ parseAnchor : function(a, start, cstart){
+ if(a && a != 'none'){
+ var last;
+ // standard anchor
+ if(this.parseAnchorRE.test(a)){
+ var diff = cstart - start;
+ return function(v){
+ if(v !== last){
+ last = v;
+ return v - diff;
+ }
+ }
+ // percentage
+ }else if(a.indexOf('%') != -1){
+ var ratio = parseFloat(a.replace('%', ''))*.01;
+ return function(v){
+ if(v !== last){
+ last = v;
+ return Math.floor(v*ratio);
+ }
+ }
+ // simple offset adjustment
+ }else{
+ a = parseInt(a, 10);
+ if(!isNaN(a)){
+ return function(v){
+ if(v !== last){
+ last = v;
+ return v + a;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ },
+
+ // private
+ adjustWidthAnchor : function(value, comp){
+ return value;
+ },
+
+ // private
+ adjustHeightAnchor : function(value, comp){
+ return value;
+ }
+
+ /**
+ * @property activeItem
+ * @hide
+ */
+});
+Ext.Container.LAYOUTS['anchor'] = Ext.layout.AnchorLayout;
+/**
+ * @class Ext.layout.ColumnLayout
+ * @extends Ext.layout.ContainerLayout
+ * <p>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.</p>
+ * <p>ColumnLayout does not have any direct config options (other than inherited ones), but it does support a
+ * specific config property of <b><tt>columnWidth</tt></b> 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).</p>
+ * <p>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).</p>
+ * <p>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 <b>remaining</b> 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:</p>
+ * <pre><code>
+// 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
+ }]
+});
+</code></pre>
+ */
+Ext.layout.ColumnLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ // private
+ monitorResize:true,
+
+ type: 'column',
+
+ extraCls: 'x-column',
+
+ scrollOffset : 0,
+
+ // private
+
+ targetCls: 'x-column-layout-ct',
+
+ isValidParent : function(c, target){
+ return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
+ },
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget(), ret;
+ if (target) {
+ ret = target.getViewSize();
+
+ // IE in strict mode will return a width of 0 on the 1st pass of getViewSize.
+ // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
+ // with getViewSize
+ if (Ext.isIE && Ext.isStrict && ret.width == 0){
+ ret = target.getStyleSize();
+ }
+
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
+ }
+ return ret;
+ },
+
+ renderAll : function(ct, target) {
+ if(!this.innerCt){
+ // the innerCt prevents wrapping and shuffling while
+ // the container is resizing
+ this.innerCt = target.createChild({cls:'x-column-inner'});
+ this.innerCt.createChild({cls:'x-clear'});
+ }
+ Ext.layout.ColumnLayout.superclass.renderAll.call(this, ct, this.innerCt);
+ },
+
+ // private
+ onLayout : function(ct, target){
+ var cs = ct.items.items,
+ len = cs.length,
+ c,
+ i,
+ m,
+ margins = [];
+
+ this.renderAll(ct, target);
+
+ var size = this.getLayoutTargetSize();
+
+ if(size.width < 1 && size.height < 1){ // display none?
+ return;
+ }
+
+ var w = size.width - this.scrollOffset,
+ h = size.height,
+ pw = w;
+
+ this.innerCt.setWidth(w);
+
+ // some columns can be percentages while others are fixed
+ // so we need to make 2 passes
+
+ for(i = 0; i < len; i++){
+ c = cs[i];
+ m = c.getPositionEl().getMargins('lr');
+ margins[i] = m;
+ if(!c.columnWidth){
+ pw -= (c.getWidth() + m);
+ }
+ }
+
+ pw = pw < 0 ? 0 : pw;
+
+ for(i = 0; i < len; i++){
+ c = cs[i];
+ m = margins[i];
+ if(c.columnWidth){
+ c.setSize(Math.floor(c.columnWidth * pw) - m);
+ }
+ }
+
+ // Browsers differ as to when they account for scrollbars. We need to re-measure to see if the scrollbar
+ // spaces were accounted for properly. If not, re-layout.
+ if (Ext.isIE) {
+ if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) {
+ var ts = this.getLayoutTargetSize();
+ if (ts.width != size.width){
+ this.adjustmentPass = true;
+ this.onLayout(ct, target);
+ }
+ }
+ }
+ delete this.adjustmentPass;
+ }
+
+ /**
+ * @property activeItem
+ * @hide
+ */
+});
+
+Ext.Container.LAYOUTS['column'] = Ext.layout.ColumnLayout;
+/**
+ * @class Ext.layout.BorderLayout
+ * @extends Ext.layout.ContainerLayout
+ * <p>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.</p>
+ * <p>This class is intended to be extended or created via the <tt>layout:'border'</tt>
+ * {@link Ext.Container#layout} config, and should generally not need to be created directly
+ * via the new keyword.</p>
+ * <p>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.</p>
+ * <p>Example usage:</p>
+ * <pre><code>
+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'
+ }]
+});
+</code></pre>
+ * <p><b><u>Notes</u></b>:</p><div class="mdetail-params"><ul>
+ * <li>Any container using the BorderLayout <b>must</b> have a child item with <tt>region:'center'</tt>.
+ * 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.</li>
+ * <li>Any child items with a region of <tt>west</tt> or <tt>east</tt> must have <tt>width</tt> defined
+ * (an integer representing the number of pixels that the region should take up).</li>
+ * <li>Any child items with a region of <tt>north</tt> or <tt>south</tt> must have <tt>height</tt> defined.</li>
+ * <li>The regions of a BorderLayout are <b>fixed at render time</b> and thereafter, its child Components may not be removed or added</b>. 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:
+ * <div style="margin-left:16px"><pre><code>
+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}();
+ * </code></pre></div>
+ * </li>
+ * <li> To reference a {@link Ext.layout.BorderLayout.Region Region}:
+ * <div style="margin-left:16px"><pre><code>
+wr = myBorderPanel.layout.west;
+ * </code></pre></div>
+ * </li>
+ * </ul></div>
+ */
+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() : {};
+ },
+
+ // 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;
+ },
+
+ 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
+ */
+});
+
+/**
+ * @class Ext.layout.BorderLayout.Region
+ * <p>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}.</p>
+ * <p>Region size is managed automatically and cannot be changed by the user -- for
+ * {@link #split resizable regions}, see {@link Ext.layout.BorderLayout.SplitRegion}.</p>
+ * @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: <tt>north</tt>, <tt>south</tt>,
+ * <tt>east</tt>, <tt>west</tt> and <tt>center</tt>. Every {@link Ext.layout.BorderLayout BorderLayout}
+ * <b>must have a center region</b> 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
+ * <tt>{@link #autoHide} = false</tt>). Setting <tt>{@link #animFloat} = false</tt> will
+ * prevent the open and close of these floated panels from being animated (defaults to <tt>true</tt>).
+ */
+ /**
+ * @cfg {Boolean} autoHide
+ * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
+ * panel. If <tt>autoHide = true</tt>, the panel will automatically hide after the user mouses
+ * out of the panel. If <tt>autoHide = false</tt>, the panel will continue to display until the
+ * user clicks outside of the panel (defaults to <tt>true</tt>).
+ */
+ /**
+ * @cfg {String} collapseMode
+ * <tt>collapseMode</tt> supports two configuration values:<div class="mdetail-params"><ul>
+ * <li><b><tt>undefined</tt></b> (default)<div class="sub-desc">By default, {@link #collapsible}
+ * regions are collapsed by clicking the expand/collapse tool button that renders into the region's
+ * title bar.</div></li>
+ * <li><b><tt>'mini'</tt></b><div class="sub-desc">Optionally, when <tt>collapseMode</tt> is set to
+ * <tt>'mini'</tt> the region's split bar will also display a small collapse button in the center of
+ * the bar. In <tt>'mini'</tt> mode the region will collapse to a thinner bar than in normal mode.
+ * </div></li>
+ * </ul></div></p>
+ * <p><b>Note</b>: if a collapsible region does not have a title bar, then set <tt>collapseMode =
+ * 'mini'</tt> and <tt>{@link #split} = true</tt> 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.</p>
+ * <p>See also <tt>{@link #cmargins}</tt>.</p>
+ */
+ /**
+ * @cfg {Object} margins
+ * An object containing margins to apply to the region when in the expanded state in the
+ * format:<pre><code>
+{
+ top: (top margin),
+ right: (right margin),
+ bottom: (bottom margin),
+ left: (left margin)
+}</code></pre>
+ * <p>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:</p>
+ * <p><div class="mdetail-params"><ul>
+ * <li>If there is only one value, it applies to all sides.</li>
+ * <li>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.</li>
+ * <li>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.</li>
+ * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
+ * </ul></div></p>
+ * <p>Defaults to:</p><pre><code>
+ * {top:0, right:0, bottom:0, left:0}
+ * </code></pre>
+ */
+ /**
+ * @cfg {Object} cmargins
+ * An object containing margins to apply to the region when in the collapsed state in the
+ * format:<pre><code>
+{
+ top: (top margin),
+ right: (right margin),
+ bottom: (bottom margin),
+ left: (left margin)
+}</code></pre>
+ * <p>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.</p>
+ * <p><ul>
+ * <li>If there is only one value, it applies to all sides.</li>
+ * <li>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.</li>
+ * <li>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.</li>
+ * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
+ * </ul></p>
+ */
+ /**
+ * @cfg {Boolean} collapsible
+ * <p><tt>true</tt> to allow the user to collapse this region (defaults to <tt>false</tt>). If
+ * <tt>true</tt>, an expand/collapse tool button will automatically be rendered into the title
+ * bar of the region, otherwise the button will not be shown.</p>
+ * <p><b>Note</b>: that a title bar is required to display the collapse/expand toggle button -- if
+ * no <tt>title</tt> is specified for the region's panel, the region will only be collapsible if
+ * <tt>{@link #collapseMode} = 'mini'</tt> and <tt>{@link #split} = true</tt>.
+ */
+ collapsible : false,
+ /**
+ * @cfg {Boolean} split
+ * <p><tt>true</tt> 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 <tt>false</tt> creating a
+ * {@link Ext.layout.BorderLayout.Region Region}.</p><br>
+ * <p><b>Notes</b>:</p><div class="mdetail-params"><ul>
+ * <li>this configuration option is ignored if <tt>region='center'</tt></li>
+ * <li>when <tt>split == true</tt>, it is common to specify a
+ * <tt>{@link Ext.SplitBar#minSize minSize}</tt> and <tt>{@link Ext.SplitBar#maxSize maxSize}</tt>
+ * 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.</li>
+ * <li>if <tt>{@link #collapseMode} = 'mini'</tt> requires <tt>split = true</tt> to reserve space
+ * for the collapse tool</tt></li>
+ * </ul></div>
+ */
+ split:false,
+ /**
+ * @cfg {Boolean} floatable
+ * <tt>true</tt> to allow clicking a collapsed region's bar to display the region's panel floated
+ * above the layout, <tt>false</tt> to force the user to fully expand a collapsed region by
+ * clicking the expand button to see it again (defaults to <tt>true</tt>).
+ */
+ floatable: true,
+ /**
+ * @cfg {Number} minWidth
+ * <p>The minimum allowable width in pixels for this region (defaults to <tt>50</tt>).
+ * <tt>maxWidth</tt> may also be specified.</p><br>
+ * <p><b>Note</b>: setting the <tt>{@link Ext.SplitBar#minSize minSize}</tt> /
+ * <tt>{@link Ext.SplitBar#maxSize maxSize}</tt> supersedes any specified
+ * <tt>minWidth</tt> / <tt>maxWidth</tt>.</p>
+ */
+ minWidth:50,
+ /**
+ * @cfg {Number} minHeight
+ * The minimum allowable height in pixels for this region (defaults to <tt>50</tt>)
+ * <tt>maxHeight</tt> may also be specified.</p><br>
+ * <p><b>Note</b>: setting the <tt>{@link Ext.SplitBar#minSize minSize}</tt> /
+ * <tt>{@link Ext.SplitBar#maxSize maxSize}</tt> supersedes any specified
+ * <tt>minHeight</tt> / <tt>maxHeight</tt>.</p>
+ */
+ 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(
+ '<div class="x-tool x-tool-{id}"> </div>'
+ );
+ 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);
+ }
+ }
+ }
+ 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: <tt>{left: (left
+ * margin), top: (top margin), right: (right margin), bottom: (bottom margin)}</tt>
+ */
+ 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: <tt>{width: (element width),
+ * height: (element height)}</tt>
+ */
+ 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 <i>over the top
+ * of the center Region</i> 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.destroy(this.miniCollapsedEl, this.collapsedEl);
+ }
+};
+
+/**
+ * @class Ext.layout.BorderLayout.SplitRegion
+ * @extends Ext.layout.BorderLayout.Region
+ * <p>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}.</p>
+ * @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 <tt>"Drag to resize."</tt>). Only applies if
+ * <tt>{@link #useSplitTips} = true</tt>.
+ */
+ 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
+ * <tt>{@link #useSplitTips} = true</tt>.
+ */
+ collapsibleSplitTip : "Drag to resize. Double click to hide.",
+ /**
+ * @cfg {Boolean} useSplitTips
+ * <tt>true</tt> to display a tooltip when the user hovers over a region's split bar
+ * (defaults to <tt>false</tt>). The tooltip text will be the value of either
+ * <tt>{@link #splitTip}</tt> or <tt>{@link #collapsibleSplitTip}</tt> 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
+ * <p>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.</p>
+ *
+ * <p>This layout manager is used when a Container is configured with the <tt>layout:'form'</tt>
+ * {@link Ext.Container#layout layout} config option, and should generally not need to be created directly
+ * via the new keyword. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>A {@link Ext.Container Container} <i>using</i> the FormLayout layout manager (e.g.
+ * {@link Ext.form.FormPanel} or specifying <tt>layout:'form'</tt>) can also accept the following
+ * layout-specific config properties:<div class="mdetail-params"><ul>
+ * <li><b><tt>{@link Ext.form.FormPanel#hideLabels hideLabels}</tt></b></li>
+ * <li><b><tt>{@link Ext.form.FormPanel#labelAlign labelAlign}</tt></b></li>
+ * <li><b><tt>{@link Ext.form.FormPanel#labelPad labelPad}</tt></b></li>
+ * <li><b><tt>{@link Ext.form.FormPanel#labelSeparator labelSeparator}</tt></b></li>
+ * <li><b><tt>{@link Ext.form.FormPanel#labelWidth labelWidth}</tt></b></li>
+ * </ul></div></p>
+ *
+ * <p>Any Component (including Fields) managed by FormLayout accepts the following as a config option:
+ * <div class="mdetail-params"><ul>
+ * <li><b><tt>{@link Ext.Component#anchor anchor}</tt></b></li>
+ * </ul></div></p>
+ *
+ * <p>Any Component managed by FormLayout may be rendered as a form field (with an associated label) by
+ * configuring it with a non-null <b><tt>{@link Ext.Component#fieldLabel fieldLabel}</tt></b>. Components configured
+ * in this way may be configured with the following options which affect the way the FormLayout renders them:
+ * <div class="mdetail-params"><ul>
+ * <li><b><tt>{@link Ext.Component#clearCls clearCls}</tt></b></li>
+ * <li><b><tt>{@link Ext.Component#fieldLabel fieldLabel}</tt></b></li>
+ * <li><b><tt>{@link Ext.Component#hideLabel hideLabel}</tt></b></li>
+ * <li><b><tt>{@link Ext.Component#itemCls itemCls}</tt></b></li>
+ * <li><b><tt>{@link Ext.Component#labelSeparator labelSeparator}</tt></b></li>
+ * <li><b><tt>{@link Ext.Component#labelStyle labelStyle}</tt></b></li>
+ * </ul></div></p>
+ *
+ * <p>Example usage:</p>
+ * <pre><code>
+// 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
+});
+</code></pre>
+ */
+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 <b>container</b> 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 <tt>false</tt>.
+ */
+ trackLabels: false,
+
+ type: 'form',
+
+ onRemove: function(c){
+ Ext.layout.FormLayout.superclass.onRemove.call(this, c);
+ if(this.trackLabels){
+ c.un('show', this.onFieldShow, this);
+ c.un('hide', this.onFieldHide, this);
+ }
+ // check for itemCt, since we may be removing a fieldset or something similar
+ var el = c.getPositionEl(),
+ ct = c.getItemCt && c.getItemCt();
+ if (c.rendered && ct) {
+ if (el && el.dom) {
+ el.insertAfter(ct);
+ }
+ Ext.destroy(ct);
+ Ext.destroyMembers(c, 'label', 'itemCt');
+ if (c.customItemCt) {
+ Ext.destroyMembers(c, 'getItemCt', 'customItemCt');
+ }
+ }
+ },
+
+ // private
+ setContainer : function(ct){
+ Ext.layout.FormLayout.superclass.setContainer.call(this, ct);
+ 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 = 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);
+ },
+
+ 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:</p><pre><code>
+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>'
+);
+</code></pre>
+ * <p>This may be specified to produce a different DOM structure when rendering form Fields.</p>
+ * <p>A description of the properties within the template follows:</p><div class="mdetail-params"><ul>
+ * <li><b><tt>itemCls</tt></b> : String<div class="sub-desc">The CSS class applied to the outermost div wrapper
+ * that contains this field label and field element (the default class is <tt>'x-form-item'</tt> and <tt>itemCls</tt>
+ * will be added to that). If supplied, <tt>itemCls</tt> at the field level will override the default <tt>itemCls</tt>
+ * supplied at the container level.</div></li>
+ * <li><b><tt>id</tt></b> : String<div class="sub-desc">The id of the Field</div></li>
+ * <li><b><tt>{@link #labelStyle}</tt></b> : String<div class="sub-desc">
+ * A CSS style specification string to add to the field label for this field (defaults to <tt>''</tt> or the
+ * {@link #labelStyle layout's value for <tt>labelStyle</tt>}).</div></li>
+ * <li><b><tt>label</tt></b> : String<div class="sub-desc">The text to display as the label for this
+ * field (defaults to <tt>''</tt>)</div></li>
+ * <li><b><tt>{@link #labelSeparator}</tt></b> : String<div class="sub-desc">The separator to display after
+ * the text of the label for this field (defaults to a colon <tt>':'</tt> or the
+ * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.</div></li>
+ * <li><b><tt>elementStyle</tt></b> : String<div class="sub-desc">The styles text for the input element's wrapper.</div></li>
+ * <li><b><tt>clearCls</tt></b> : String<div class="sub-desc">The CSS class to apply to the special clearing div
+ * rendered directly after each form field wrapper (defaults to <tt>'x-form-clear-left'</tt>)</div></li>
+ * </ul></div>
+ * <p>Also see <tt>{@link #getTemplateArgs}</tt></p>
+ */
+
+ /**
+ * @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);
+ }
+ },
+
+ /**
+ * <p>Provides template arguments for rendering the fully wrapped, labeled and styled form Field.</p>
+ * <p>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:</p><div class="mdetail-params"><ul>
+ * <li><b><tt>itemCls</tt></b> : String<div class="sub-desc">The CSS class applied to the outermost div wrapper
+ * that contains this field label and field element (the default class is <tt>'x-form-item'</tt> and <tt>itemCls</tt>
+ * will be added to that). If supplied, <tt>itemCls</tt> at the field level will override the default <tt>itemCls</tt>
+ * supplied at the container level.</div></li>
+ * <li><b><tt>id</tt></b> : String<div class="sub-desc">The id of the Field</div></li>
+ * <li><b><tt>{@link #labelStyle}</tt></b> : String<div class="sub-desc">
+ * A CSS style specification string to add to the field label for this field (defaults to <tt>''</tt> or the
+ * {@link #labelStyle layout's value for <tt>labelStyle</tt>}).</div></li>
+ * <li><b><tt>label</tt></b> : String<div class="sub-desc">The text to display as the label for this
+ * field (defaults to the field's configured fieldLabel property)</div></li>
+ * <li><b><tt>{@link #labelSeparator}</tt></b> : String<div class="sub-desc">The separator to display after
+ * the text of the label for this field (defaults to a colon <tt>':'</tt> or the
+ * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.</div></li>
+ * <li><b><tt>elementStyle</tt></b> : String<div class="sub-desc">The styles text for the input element's wrapper.</div></li>
+ * <li><b><tt>clearCls</tt></b> : String<div class="sub-desc">The CSS class to apply to the special clearing div
+ * rendered directly after each form field wrapper (defaults to <tt>'x-form-clear-left'</tt>)</div></li>
+ * </ul></div>
+ * @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
+ * <p>This is a layout that manages multiple Panels in an expandable accordion style such that only
+ * <b>one Panel can be expanded at any given time</b>. Each Panel has built-in support for expanding and collapsing.</p>
+ * <p>Note: Only Ext.Panels <b>and all subclasses of Ext.Panel</b> may be used in an accordion layout Container.</p>
+ * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Container#layout layout}</b></tt>
+ * configuration property. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
+ * <p>Example usage:</p>
+ * <pre><code>
+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>'
+ }]
+});
+</code></pre>
+ */
+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
+ * <b>Experimental</b>. 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. <b>This is NOT compatible with "animate:true"</b> (defaults to false).
+ */
+ activeOnTop : false,
+
+ type: 'accordion',
+
+ renderItem : function(c){
+ if(this.animate === false){
+ c.animCollapse = false;
+ }
+ c.collapsible = true;
+ if(this.autoWidth){
+ c.autoWidth = true;
+ }
+ if(this.titleCollapse){
+ c.titleCollapse = true;
+ }
+ if(this.hideCollapseTool){
+ c.hideCollapseTool = true;
+ }
+ if(this.collapseFirst !== undefined){
+ c.collapseFirst = this.collapseFirst;
+ }
+ if(!this.activeItem && !c.collapsed){
+ this.setActiveItem(c, true);
+ }else if(this.activeItem && this.activeItem != c){
+ c.collapsed = true;
+ }
+ Ext.layout.AccordionLayout.superclass.renderItem.apply(this, arguments);
+ c.header.addClass('x-accordion-hd');
+ c.on('beforeexpand', this.beforeExpand, this);
+ },
+
+ onRemove: function(c){
+ Ext.layout.AccordionLayout.superclass.onRemove.call(this, c);
+ if(c.rendered){
+ c.header.removeClass('x-accordion-hd');
+ }
+ c.un('beforeexpand', this.beforeExpand, this);
+ },
+
+ // private
+ beforeExpand : function(p, anim){
+ var ai = this.activeItem;
+ if(ai){
+ if(this.sequence){
+ delete this.activeItem;
+ if (!ai.collapsed){
+ ai.collapse({callback:function(){
+ p.expand(anim || true);
+ }, scope: this});
+ return false;
+ }
+ }else{
+ ai.collapse(this.animate);
+ }
+ }
+ this.setActive(p);
+ if(this.activeOnTop){
+ p.el.dom.parentNode.insertBefore(p.el.dom, p.el.dom.parentNode.firstChild);
+ }
+ // Items have been hidden an possibly rearranged, we need to get the container size again.
+ this.layout();
+ },
+
+ // private
+ setItemSize : function(item, size){
+ if(this.fill && item){
+ var hh = 0, i, ct = this.getRenderedItems(this.container), len = ct.length, p;
+ // Add up all the header heights
+ for (i = 0; i < len; i++) {
+ if((p = ct[i]) != item){
+ hh += p.header.getHeight();
+ }
+ };
+ // Subtract the header heights from the container size
+ size.height -= hh;
+ // Call setSize on the container to set the correct height. For Panels, deferedHeight
+ // will simply store this size for when the expansion is done.
+ item.setSize(size);
+ }
+ },
+
+ /**
+ * Sets the active (expanded) item in the layout.
+ * @param {String/Number} item The string component id or numeric index of the item to activate
+ */
+ setActiveItem : function(item){
+ this.setActive(item, true);
+ },
+
+ // private
+ setActive : function(item, expand){
+ var ai = this.activeItem;
+ item = this.container.getComponent(item);
+ if(ai != item){
+ if(item.rendered && item.collapsed && expand){
+ item.expand();
+ }else{
+ if(ai){
+ ai.fireEvent('deactivate', ai);
+ }
+ this.activeItem = item;
+ item.fireEvent('activate', item);
+ }
+ }
+ }
+});
+Ext.Container.LAYOUTS.accordion = Ext.layout.AccordionLayout;
+
+//backwards compat
+Ext.layout.Accordion = Ext.layout.AccordionLayout;/**
+ * @class Ext.layout.TableLayout
+ * @extends Ext.layout.ContainerLayout
+ * <p>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.</p>
+ * <p>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:</p>
+ * <ul>
+ * <li><b>rowspan</b> Applied to the table cell containing the item.</li>
+ * <li><b>colspan</b> Applied to the table cell containing the item.</li>
+ * <li><b>cellId</b> An id applied to the table cell containing the item.</li>
+ * <li><b>cellCls</b> A CSS class name added to the table cell containing the item.</li>
+ * </ul>
+ * <p>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:</p>
+ * <pre><code>
+// 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>'
+ }]
+});
+</code></pre>
+ */
+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
+ * <p>An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification
+ * used to create the layout's <tt><table></tt> element. Example:</p><pre><code>
+{
+ xtype: 'panel',
+ layout: 'table',
+ layoutConfig: {
+ tableAttrs: {
+ style: {
+ width: '100%'
+ }
+ },
+ columns: 3
+ }
+}</code></pre>
+ */
+ tableAttrs:null,
+
+ // private
+ setContainer : function(ct){
+ Ext.layout.TableLayout.superclass.setContainer.call(this, ct);
+
+ this.currentRow = 0;
+ this.currentColumn = 0;
+ this.cells = [];
+ },
+
+ // private
+ onLayout : function(ct, target){
+ var cs = ct.items.items, len = cs.length, c, i;
+
+ if(!this.table){
+ target.addClass('x-table-layout-ct');
+
+ this.table = target.createChild(
+ Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true);
+ }
+ this.renderAll(ct, target);
+ },
+
+ // private
+ getRow : function(index){
+ var row = this.table.tBodies[0].childNodes[index];
+ if(!row){
+ row = document.createElement('tr');
+ this.table.tBodies[0].appendChild(row);
+ }
+ return row;
+ },
+
+ // private
+ getNextCell : function(c){
+ var cell = this.getNextNonSpan(this.currentColumn, this.currentRow);
+ var curCol = this.currentColumn = cell[0], curRow = this.currentRow = cell[1];
+ for(var rowIndex = curRow; rowIndex < curRow + (c.rowspan || 1); rowIndex++){
+ if(!this.cells[rowIndex]){
+ this.cells[rowIndex] = [];
+ }
+ for(var colIndex = curCol; colIndex < curCol + (c.colspan || 1); colIndex++){
+ this.cells[rowIndex][colIndex] = true;
+ }
+ }
+ var td = document.createElement('td');
+ if(c.cellId){
+ td.id = c.cellId;
+ }
+ var cls = 'x-table-layout-cell';
+ if(c.cellCls){
+ cls += ' ' + c.cellCls;
+ }
+ td.className = cls;
+ if(c.colspan){
+ td.colSpan = c.colspan;
+ }
+ if(c.rowspan){
+ td.rowSpan = c.rowspan;
+ }
+ this.getRow(curRow).appendChild(td);
+ return td;
+ },
+
+ // private
+ getNextNonSpan: function(colIndex, rowIndex){
+ var cols = this.columns;
+ while((cols && colIndex >= cols) || (this.cells[rowIndex] && this.cells[rowIndex][colIndex])) {
+ if(cols && colIndex >= cols){
+ rowIndex++;
+ colIndex = 0;
+ }else{
+ colIndex++;
+ }
+ }
+ return [colIndex, rowIndex];
+ },
+
+ // private
+ renderItem : function(c, position, target){
+ // Ensure we have our inner table to get cells to render into.
+ if(!this.table){
+ this.table = target.createChild(
+ Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true);
+ }
+ if(c && !c.rendered){
+ c.render(this.getNextCell(c));
+ this.configureItem(c, position);
+ }else if(c && !this.isValidParent(c, target)){
+ var container = this.getNextCell(c);
+ container.insertBefore(c.getPositionEl().dom, null);
+ c.container = Ext.get(container);
+ this.configureItem(c, position);
+ }
+ },
+
+ // private
+ isValidParent : function(c, target){
+ return c.getPositionEl().up('table', 5).dom.parentNode === (target.dom || target);
+ }
+
+ /**
+ * @property activeItem
+ * @hide
+ */
+});
+
+Ext.Container.LAYOUTS['table'] = Ext.layout.TableLayout;/**
+ * @class Ext.layout.AbsoluteLayout
+ * @extends Ext.layout.AnchorLayout
+ * <p>This is a layout that inherits the anchoring of <b>{@link Ext.layout.AnchorLayout}</b> and adds the
+ * ability for x/y positioning using the standard x and y component config options.</p>
+ * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Container#layout layout}</b></tt>
+ * configuration property. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
+ * <p>Example usage:</p>
+ * <pre><code>
+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
+ }]
+});
+</code></pre>
+ */
+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
+ * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
+ */
+Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ /**
+ * @cfg {Object} defaultMargins
+ * <p>If the individual contained items do not have a <tt>margins</tt>
+ * property specified, the default margins from this property will be
+ * applied to each item.</p>
+ * <br><p>This property may be specified as an object containing margins
+ * to apply in the format:</p><pre><code>
+{
+ top: (top margin),
+ right: (right margin),
+ bottom: (bottom margin),
+ left: (left margin)
+}</code></pre>
+ * <p>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:</p>
+ * <div class="mdetail-params"><ul>
+ * <li>If there is only one value, it applies to all sides.</li>
+ * <li>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.</li>
+ * <li>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.</li>
+ * <li>If there are four values, they apply to the top, right, bottom, and
+ * left, respectively.</li>
+ * </ul></div>
+ * <p>Defaults to:</p><pre><code>
+ * {top:0, right:0, bottom:0, left:0}
+ * </code></pre>
+ */
+ defaultMargins : {left:0,top:0,right:0,bottom:0},
+ /**
+ * @cfg {String} padding
+ * <p>Sets the padding to be applied to all child items managed by this layout.</p>
+ * <p>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:</p>
+ * <div class="mdetail-params"><ul>
+ * <li>If there is only one value, it applies to all sides.</li>
+ * <li>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.</li>
+ * <li>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.</li>
+ * <li>If there are four values, they apply to the top, right, bottom, and
+ * left, respectively.</li>
+ * </ul></div>
+ * <p>Defaults to: <code>"0"</code></p>
+ */
+ 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);
+ }
+ },
+
+ /**
+ * @private
+ * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
+ * when laying out
+ */
+ onLayout: function(container, target) {
+ Ext.layout.BoxLayout.superclass.onLayout.call(this, container, target);
+
+ var items = this.getVisibleItems(container),
+ tSize = this.getLayoutTargetSize();
+
+ /**
+ * @private
+ * @property layoutTargetLastSize
+ * @type Object
+ * Private cache of the last measured size of the layout target. This should never be used except by
+ * BoxLayout subclasses during their onLayout run.
+ */
+ this.layoutTargetLastSize = tSize;
+
+ /**
+ * @private
+ * @property childBoxCache
+ * @type Array
+ * Array of the last calculated height, width, top and left positions of each visible rendered component
+ * within the Box layout.
+ */
+ this.childBoxCache = this.calculateChildBoxes(items, tSize);
+
+ this.updateInnerCtSize(tSize, this.childBoxCache);
+ this.updateChildBoxes(this.childBoxCache.boxes);
+
+ // Putting a box layout into an overflowed container is NOT correct and will make a second layout pass necessary.
+ this.handleTargetOverflow(tSize, container, target);
+ },
+
+ /**
+ * 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: Ext.emptyFn,
+
+ /**
+ * @private
+ * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
+ * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
+ * target. Having a Box layout inside such a target is therefore not recommended.
+ * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
+ * @param {Ext.Container} container The container
+ * @param {Ext.Element} target The target element
+ */
+ handleTargetOverflow: function(previousTargetSize, container, target) {
+ var overflow = target.getStyle('overflow');
+
+ if (overflow && overflow != 'hidden' &&!this.adjustmentPass) {
+ var newTargetSize = this.getLayoutTargetSize();
+ if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height){
+ this.adjustmentPass = true;
+ this.onLayout(container, target);
+ }
+ }
+
+ delete this.adjustmentPass;
+ },
+
+ // private
+ isValidParent : function(c, target){
+ return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
+ },
+
+ /**
+ * @private
+ * Returns all items that are both rendered and visible
+ * @return {Array} All matching items
+ */
+ getVisibleItems: function(ct) {
+ var ct = ct || this.container,
+ t = ct.getLayoutTarget(),
+ cti = ct.items.items,
+ len = cti.length,
+
+ i, c, items = [];
+
+ for (i = 0; i < len; i++) {
+ if((c = cti[i]).rendered && this.isValidParent(c, t) && c.hidden !== true && c.collapsed !== true){
+ items.push(c);
+ }
+ }
+
+ return items;
+ },
+
+ // private
+ renderAll : function(ct, target){
+ if(!this.innerCt){
+ // the innerCt prevents wrapping and shuffling while
+ // the container is resizing
+ this.innerCt = target.createChild({cls:this.innerCls});
+ this.padding = this.parseMargins(this.padding);
+ }
+ Ext.layout.BoxLayout.superclass.renderAll.call(this, ct, this.innerCt);
+ },
+
+ getLayoutTargetSize : function(){
+ var target = this.container.getLayoutTarget(), ret;
+ if (target) {
+ ret = target.getViewSize();
+
+ // IE in strict mode will return a width of 0 on the 1st pass of getViewSize.
+ // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
+ // with getViewSize
+ if (Ext.isIE && Ext.isStrict && ret.width == 0){
+ ret = target.getStyleSize();
+ }
+
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
+ }
+ return ret;
+ },
+
+ // private
+ renderItem : function(c){
+ if(Ext.isString(c.margins)){
+ c.margins = this.parseMargins(c.margins);
+ }else if(!c.margins){
+ c.margins = this.defaultMargins;
+ }
+ Ext.layout.BoxLayout.superclass.renderItem.apply(this, arguments);
+ }
+});
+
+/**
+ * @class Ext.layout.VBoxLayout
+ * @extends Ext.layout.BoxLayout
+ * <p>A layout that arranges items vertically down a Container. This layout optionally divides available vertical
+ * space between child items containing a numeric <code>flex</code> configuration.</p>
+ * 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:
+ * <div class="mdetail-params"><ul>
+ * <li><b><tt>left</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned horizontally
+ * at the <b>left</b> side of the container</div></li>
+ * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are aligned horizontally at the
+ * <b>mid-width</b> of the container</div></li>
+ * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched horizontally to fill
+ * the width of the container</div></li>
+ * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched horizontally to
+ * the size of the largest item.</div></li>
+ * </ul></div>
+ */
+ 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:
+ * <div class="mdetail-params"><ul>
+ * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
+ * <b>top</b> side of container</div></li>
+ * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
+ * <b>mid-height</b> of container</div></li>
+ * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>bottom</b>
+ * side of container</div></li>
+ * </ul></div>
+ */
+
+ /**
+ * @cfg {Number} flex
+ * This configuation option is to be applied to <b>child <tt>items</tt></b> of the container managed
+ * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>vertically</b>
+ * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
+ * a <tt>flex</tt> value specified. Any child items that have either a <tt>flex = 0</tt> or
+ * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
+ */
+
+ /**
+ * @private
+ * See parent documentation
+ */
+ updateInnerCtSize: function(tSize, calcs) {
+ var innerCtHeight = tSize.height,
+ innerCtWidth = calcs.meta.maxWidth + this.padding.left + this.padding.right;
+
+ if (this.align == 'stretch') {
+ innerCtWidth = tSize.width;
+ } else if (this.align == 'center') {
+ innerCtWidth = Math.max(tSize.width, innerCtWidth);
+ }
+
+ //we set the innerCt size first because if our child items are larger than the previous innerCt size
+ //the browser will insert scrollbars and then remove them again immediately afterwards
+ this.innerCt.setSize(innerCtWidth || undefined, innerCtHeight || undefined);
+ },
+
+ /**
+ * @private
+ * Calculates the size and positioning of each item in the VBox. This iterates over all of the rendered,
+ * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
+ * returns meta data such as maxHeight which are useful when resizing layout wrappers such as this.innerCt.
+ * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
+ * @param {Object} targetSize Object containing target size and height
+ * @return {Object} Object containing box measurements for each child, plus meta data
+ */
+ calculateChildBoxes: function(visibleItems, targetSize) {
+ var visibleCount = visibleItems.length,
+
+ padding = this.padding,
+ topOffset = padding.top,
+ leftOffset = padding.left,
+ paddingVert = topOffset + padding.bottom,
+ paddingHoriz = leftOffset + padding.right,
+
+ width = targetSize.width - this.scrollOffset,
+ height = targetSize.height,
+ availWidth = Math.max(0, width - paddingHoriz),
+
+ isStart = this.pack == 'start',
+ isCenter = this.pack == 'center',
+ isEnd = this.pack == 'end',
+
+ nonFlexHeight= 0,
+ maxWidth = 0,
+ totalFlex = 0,
+
+ //used to cache the calculated size and position values for each child item
+ boxes = [],
+
+ //used in the for loops below, just declared here for brevity
+ child, childWidth, childHeight, childSize, childMargins, canLayout, i, calcs, flexedHeight, horizMargins, stretchWidth;
+
+ //gather the total flex of all flexed items and the width taken up by fixed width items
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ childHeight = child.height;
+ childWidth = child.width;
+ canLayout = !child.hasLayout && Ext.isFunction(child.doLayout);
+
+
+ // Static height (numeric) requires no calcs
+ if (!Ext.isNumber(childHeight)) {
+
+ // flex and not 'auto' height
+ if (child.flex && !childHeight) {
+ totalFlex += child.flex;
+
+ // Not flexed or 'auto' height or undefined height
+ } else {
+ //Render and layout sub-containers without a flex or width defined, as otherwise we
+ //don't know how wide the sub-container should be and cannot calculate flexed widths
+ if (!childHeight && canLayout) {
+ child.doLayout();
+ }
+
+ childSize = child.getSize();
+ childWidth = childSize.width;
+ childHeight = childSize.height;
+ }
+ }
+
+ childMargins = child.margins;
+
+ nonFlexHeight += (childHeight || 0) + childMargins.top + childMargins.bottom;
+
+ // Max width for align - force layout of non-layed out subcontainers without a numeric width
+ if (!Ext.isNumber(childWidth)) {
+ if (canLayout) {
+ child.doLayout();
+ }
+ childWidth = child.getWidth();
+ }
+
+ maxWidth = Math.max(maxWidth, childWidth + childMargins.left + childMargins.right);
+
+ //cache the size of each child component
+ boxes.push({
+ component: child,
+ height : childHeight || undefined,
+ width : childWidth || undefined
+ });
+ }
+
+ //the height available to the flexed items
+ var availableHeight = Math.max(0, (height - nonFlexHeight - paddingVert));
+
+ if (isCenter) {
+ topOffset += availableHeight / 2;
+ } else if (isEnd) {
+ topOffset += availableHeight;
+ }
+
+ //temporary variables used in the flex height calculations below
+ var remainingHeight = availableHeight,
+ remainingFlex = totalFlex;
+
+ //calculate the height of each flexed item, and the left + top positions of every item
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ calcs = boxes[i];
+
+ childMargins = child.margins;
+ horizMargins = childMargins.left + childMargins.right;
+
+ topOffset += childMargins.top;
+
+ if (isStart && child.flex && !child.height) {
+ flexedHeight = Math.ceil((child.flex / remainingFlex) * remainingHeight);
+ remainingHeight -= flexedHeight;
+ remainingFlex -= child.flex;
+
+ calcs.height = flexedHeight;
+ calcs.dirtySize = true;
+ }
+
+ calcs.left = leftOffset + childMargins.left;
+ calcs.top = topOffset;
+
+ switch (this.align) {
+ case 'stretch':
+ stretchWidth = availWidth - horizMargins;
+ calcs.width = stretchWidth.constrain(child.minHeight || 0, child.maxWidth || 1000000);
+ calcs.dirtySize = true;
+ break;
+ case 'stretchmax':
+ stretchWidth = maxWidth - horizMargins;
+ calcs.width = stretchWidth.constrain(child.minHeight || 0, child.maxWidth || 1000000);
+ calcs.dirtySize = true;
+ break;
+ case 'center':
+ var diff = availWidth - calcs.width - horizMargins;
+ if (diff > 0) {
+ calcs.left = leftOffset + horizMargins + (diff / 2);
+ }
+ }
+
+ topOffset += calcs.height + childMargins.bottom;
+ }
+
+ return {
+ boxes: boxes,
+ meta : {
+ maxWidth: maxWidth
+ }
+ };
+ }
+});
+
+Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout;
+
+/**
+ * @class Ext.layout.HBoxLayout
+ * @extends Ext.layout.BoxLayout
+ * <p>A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
+ * space between child items containing a numeric <code>flex</code> configuration.</p>
+ * 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:
+ * <div class="mdetail-params"><ul>
+ * <li><b><tt>top</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned vertically
+ * at the <b>top</b> of the container</div></li>
+ * <li><b><tt>middle</tt></b> : <div class="sub-desc">child items are aligned vertically in the
+ * <b>middle</b> of the container</div></li>
+ * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched vertically to fill
+ * the height of the container</div></li>
+ * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched vertically to
+ * the height of the largest item.</div></li>
+ */
+ align: 'top', // top, middle, stretch, strechmax
+
+ type : 'hbox',
+
+ /**
+ * @private
+ * See parent documentation
+ */
+ updateInnerCtSize: function(tSize, calcs) {
+ var innerCtWidth = tSize.width,
+ innerCtHeight = calcs.meta.maxHeight + this.padding.top + this.padding.bottom;
+
+ if (this.align == 'stretch') {
+ innerCtHeight = tSize.height;
+ } else if (this.align == 'middle') {
+ innerCtHeight = Math.max(tSize.height, innerCtHeight);
+ }
+
+ this.innerCt.setSize(innerCtWidth || undefined, innerCtHeight || undefined);
+ },
+
+ /**
+ * @cfg {String} pack
+ * Controls how the child items of the container are packed together. Acceptable configuration values
+ * for this property are:
+ * <div class="mdetail-params"><ul>
+ * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
+ * <b>left</b> side of container</div></li>
+ * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
+ * <b>mid-width</b> of container</div></li>
+ * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
+ * side of container</div></li>
+ * </ul></div>
+ */
+ /**
+ * @cfg {Number} flex
+ * This configuation option is to be applied to <b>child <tt>items</tt></b> of the container managed
+ * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
+ * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
+ * a <tt>flex</tt> value specified. Any child items that have either a <tt>flex = 0</tt> or
+ * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
+ */
+
+ /**
+ * @private
+ * Calculates the size and positioning of each item in the HBox. This iterates over all of the rendered,
+ * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
+ * returns meta data such as maxHeight which are useful when resizing layout wrappers such as this.innerCt.
+ * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
+ * @param {Object} targetSize Object containing target size and height
+ * @return {Object} Object containing box measurements for each child, plus meta data
+ */
+ calculateChildBoxes: function(visibleItems, targetSize) {
+ var visibleCount = visibleItems.length,
+
+ padding = this.padding,
+ topOffset = padding.top,
+ leftOffset = padding.left,
+ paddingVert = topOffset + padding.bottom,
+ paddingHoriz = leftOffset + padding.right,
+
+ width = targetSize.width - this.scrollOffset,
+ height = targetSize.height,
+ availHeight = Math.max(0, height - paddingVert),
+
+ isStart = this.pack == 'start',
+ isCenter = this.pack == 'center',
+ isEnd = this.pack == 'end',
+ // isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1,
+
+ nonFlexWidth = 0,
+ maxHeight = 0,
+ totalFlex = 0,
+
+ //used to cache the calculated size and position values for each child item
+ boxes = [],
+
+ //used in the for loops below, just declared here for brevity
+ child, childWidth, childHeight, childSize, childMargins, canLayout, i, calcs, flexedWidth, vertMargins, stretchHeight;
+
+ //gather the total flex of all flexed items and the width taken up by fixed width items
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ childHeight = child.height;
+ childWidth = child.width;
+ canLayout = !child.hasLayout && Ext.isFunction(child.doLayout);
+
+ // Static width (numeric) requires no calcs
+ if (!Ext.isNumber(childWidth)) {
+
+ // flex and not 'auto' width
+ if (child.flex && !childWidth) {
+ totalFlex += child.flex;
+
+ // Not flexed or 'auto' width or undefined width
+ } else {
+ //Render and layout sub-containers without a flex or width defined, as otherwise we
+ //don't know how wide the sub-container should be and cannot calculate flexed widths
+ if (!childWidth && canLayout) {
+ child.doLayout();
+ }
+
+ childSize = child.getSize();
+ childWidth = childSize.width;
+ childHeight = childSize.height;
+ }
+ }
+
+ childMargins = child.margins;
+
+ nonFlexWidth += (childWidth || 0) + childMargins.left + childMargins.right;
+
+ // Max height for align - force layout of non-layed out subcontainers without a numeric height
+ if (!Ext.isNumber(childHeight)) {
+ if (canLayout) {
+ child.doLayout();
+ }
+ childHeight = child.getHeight();
+ }
+
+ maxHeight = Math.max(maxHeight, childHeight + childMargins.top + childMargins.bottom);
+
+ //cache the size of each child component
+ boxes.push({
+ component: child,
+ height : childHeight || undefined,
+ width : childWidth || undefined
+ });
+ }
+
+ //the width available to the flexed items
+ var availableWidth = Math.max(0, (width - nonFlexWidth - paddingHoriz));
+
+ if (isCenter) {
+ leftOffset += availableWidth / 2;
+ } else if (isEnd) {
+ leftOffset += availableWidth;
+ }
+
+ //temporary variables used in the flex width calculations below
+ var remainingWidth = availableWidth,
+ remainingFlex = totalFlex;
+
+ //calculate the widths of each flexed item, and the left + top positions of every item
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ calcs = boxes[i];
+
+ childMargins = child.margins;
+ vertMargins = childMargins.top + childMargins.bottom;
+
+ leftOffset += childMargins.left;
+
+ if (isStart && child.flex && !child.width) {
+ flexedWidth = Math.ceil((child.flex / remainingFlex) * remainingWidth);
+ remainingWidth -= flexedWidth;
+ remainingFlex -= child.flex;
+
+ calcs.width = flexedWidth;
+ calcs.dirtySize = true;
+ }
+
+ calcs.left = leftOffset;
+ calcs.top = topOffset + childMargins.top;
+
+ switch (this.align) {
+ case 'stretch':
+ stretchHeight = availHeight - vertMargins;
+ calcs.height = stretchHeight.constrain(child.minHeight || 0, child.maxHeight || 1000000);
+ calcs.dirtySize = true;
+ break;
+ case 'stretchmax':
+ stretchHeight = maxHeight - vertMargins;
+ calcs.height = stretchHeight.constrain(child.minHeight || 0, child.maxHeight || 1000000);
+ calcs.dirtySize = true;
+ break;
+ case 'middle':
+ var diff = availHeight - calcs.height - vertMargins;
+ if (diff > 0) {
+ calcs.top = topOffset + vertMargins + (diff / 2);
+ }
+ }
+ leftOffset += calcs.width + childMargins.right;
+ }
+
+ return {
+ boxes: boxes,
+ meta : {
+ maxHeight: maxHeight
+ }
+ };
+ }
+});
+
+Ext.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout;
+/**
+ * @class Ext.layout.ToolbarLayout
+ * @extends Ext.layout.ContainerLayout
+ * Layout manager used by Ext.Toolbar. This is highly specialised for use by Toolbars and would not
+ * usually be used by any other class.
+ */
+Ext.layout.ToolbarLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ monitorResize : true,
+
+ type: 'toolbar',
+
+ /**
+ * @property triggerWidth
+ * @type Number
+ * The width allocated for the menu trigger at the extreme right end of the Toolbar
+ */
+ triggerWidth: 18,
+
+ /**
+ * @property noItemsMenuText
+ * @type String
+ * HTML fragment to render into the toolbar overflow menu if there are no items to display
+ */
+ noItemsMenuText : '<div class="x-toolbar-no-items">(None)</div>',
+
+ /**
+ * @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: [
+ '<table cellspacing="0" class="x-toolbar-ct">',
+ '<tbody>',
+ '<tr>',
+ '<td class="x-toolbar-left" align="{0}">',
+ '<table cellspacing="0">',
+ '<tbody>',
+ '<tr class="x-toolbar-left-row"></tr>',
+ '</tbody>',
+ '</table>',
+ '</td>',
+ '<td class="x-toolbar-right" align="right">',
+ '<table cellspacing="0" class="x-toolbar-right-ct">',
+ '<tbody>',
+ '<tr>',
+ '<td>',
+ '<table cellspacing="0">',
+ '<tbody>',
+ '<tr class="x-toolbar-right-row"></tr>',
+ '</tbody>',
+ '</table>',
+ '</td>',
+ '<td>',
+ '<table cellspacing="0">',
+ '<tbody>',
+ '<tr class="x-toolbar-extras-row"></tr>',
+ '</tbody>',
+ '</table>',
+ '</td>',
+ '</tr>',
+ '</tbody>',
+ '</table>',
+ '</td>',
+ '</tr>',
+ '</tbody>',
+ '</table>'
+ ].join(""),
+
+ /**
+ * @private
+ * Create the wrapping Toolbar HTML and render/move all the items into the correct places
+ */
+ onLayout : function(ct, target) {
+ //render the Toolbar <table> HTML if it's not already present
+ if (!this.leftTr) {
+ var align = ct.buttonAlign == 'center' ? 'center' : 'left';
+
+ target.addClass('x-toolbar-layout-ct');
+ target.insertHtml('beforeEnd', String.format(this.tableHTML, align));
+
+ this.leftTr = target.child('tr.x-toolbar-left-row', true);
+ this.rightTr = target.child('tr.x-toolbar-right-row', true);
+ this.extrasTr = target.child('tr.x-toolbar-extras-row', true);
+
+ if (this.hiddenItem == undefined) {
+ /**
+ * @property hiddenItems
+ * @type Array
+ * Holds all items that are currently hidden due to there not being enough space to render them
+ * These items will appear on the expand menu.
+ */
+ this.hiddenItems = [];
+ }
+ }
+
+ var side = ct.buttonAlign == 'right' ? this.rightTr : this.leftTr,
+ items = ct.items.items,
+ position = 0;
+
+ //render each item if not already rendered, place it into the correct (left or right) target
+ for (var i = 0, len = items.length, c; i < len; i++, position++) {
+ c = items[i];
+
+ if (c.isFill) {
+ side = this.rightTr;
+ position = -1;
+ } else if (!c.rendered) {
+ c.render(this.insertCell(c, side, position));
+ } else {
+ if (!c.xtbHidden && !this.isValidParent(c, side.childNodes[position])) {
+ var td = this.insertCell(c, side, position);
+ td.appendChild(c.getPositionEl().dom);
+ c.container = Ext.get(td);
+ }
+ }
+ }
+
+ //strip extra empty cells
+ this.cleanup(this.leftTr);
+ this.cleanup(this.rightTr);
+ this.cleanup(this.extrasTr);
+ this.fitToSize(target);
+ },
+
+ /**
+ * @private
+ * Removes any empty nodes from the given element
+ * @param {Ext.Element} el The element to clean up
+ */
+ cleanup : function(el) {
+ var cn = el.childNodes, i, c;
+
+ for (i = cn.length-1; i >= 0 && (c = cn[i]); i--) {
+ if (!c.firstChild) {
+ el.removeChild(c);
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Inserts the given Toolbar item into the given element
+ * @param {Ext.Component} c The component to add
+ * @param {Ext.Element} target The target to add the component to
+ * @param {Number} position The position to add the component at
+ */
+ insertCell : function(c, target, position) {
+ var td = document.createElement('td');
+ td.className = 'x-toolbar-cell';
+
+ target.insertBefore(td, target.childNodes[position] || null);
+
+ return td;
+ },
+
+ /**
+ * @private
+ * Hides an item because it will not fit in the available width. The item will be unhidden again
+ * if the Toolbar is resized to be large enough to show it
+ * @param {Ext.Component} item The item to hide
+ */
+ hideItem : function(item) {
+ this.hiddenItems.push(item);
+
+ item.xtbHidden = true;
+ item.xtbWidth = item.getPositionEl().dom.parentNode.offsetWidth;
+ item.hide();
+ },
+
+ /**
+ * @private
+ * Unhides an item that was previously hidden due to there not being enough space left on the Toolbar
+ * @param {Ext.Component} item The item to show
+ */
+ unhideItem : function(item) {
+ item.show();
+ item.xtbHidden = false;
+ this.hiddenItems.remove(item);
+ },
+
+ /**
+ * @private
+ * Returns the width of the given toolbar item. If the item is currently hidden because there
+ * is not enough room to render it, its previous width is returned
+ * @param {Ext.Component} c The component to measure
+ * @return {Number} The width of the item
+ */
+ getItemWidth : function(c) {
+ return c.hidden ? (c.xtbWidth || 0) : c.getPositionEl().dom.parentNode.offsetWidth;
+ },
+
+ /**
+ * @private
+ * Called at the end of onLayout. At this point the Toolbar has already been resized, so we need
+ * to fit the items into the available width. We add up the width required by all of the items in
+ * the toolbar - if we don't have enough space we hide the extra items and render the expand menu
+ * trigger.
+ * @param {Ext.Element} target The Element the Toolbar is currently laid out within
+ */
+ fitToSize : function(target) {
+ if (this.container.enableOverflow === false) {
+ return;
+ }
+
+ var width = target.dom.clientWidth,
+ tableWidth = target.dom.firstChild.offsetWidth,
+ clipWidth = width - this.triggerWidth,
+ lastWidth = this.lastWidth || 0,
+
+ hiddenItems = this.hiddenItems,
+ hasHiddens = hiddenItems.length != 0,
+ isLarger = width >= lastWidth;
+
+ this.lastWidth = width;
+
+ if (tableWidth > width || (hasHiddens && isLarger)) {
+ var items = this.container.items.items,
+ len = items.length,
+ loopWidth = 0,
+ item;
+
+ for (var i = 0; i < len; i++) {
+ item = items[i];
+
+ if (!item.isFill) {
+ loopWidth += this.getItemWidth(item);
+ if (loopWidth > clipWidth) {
+ if (!(item.hidden || item.xtbHidden)) {
+ this.hideItem(item);
+ }
+ } else if (item.xtbHidden) {
+ this.unhideItem(item);
+ }
+ }
+ }
+ }
+
+ //test for number of hidden items again here because they may have changed above
+ hasHiddens = hiddenItems.length != 0;
+
+ if (hasHiddens) {
+ this.initMore();
+
+ if (!this.lastOverflow) {
+ this.container.fireEvent('overflowchange', this.container, true);
+ this.lastOverflow = true;
+ }
+ } else if (this.more) {
+ this.clearMenu();
+ this.more.destroy();
+ delete this.more;
+
+ if (this.lastOverflow) {
+ this.container.fireEvent('overflowchange', this.container, false);
+ this.lastOverflow = false;
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Returns a menu config for a given component. This config is used to create a menu item
+ * to be added to the expander menu
+ * @param {Ext.Component} component The component to create the config for
+ * @param {Boolean} hideOnClick Passed through to the menu item
+ */
+ createMenuConfig : function(component, hideOnClick){
+ var config = Ext.apply({}, component.initialConfig),
+ group = component.toggleGroup;
+
+ Ext.copyTo(config, component, [
+ 'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
+ ]);
+
+ Ext.apply(config, {
+ text : component.overflowText || component.text,
+ hideOnClick: hideOnClick
+ });
+
+ if (group || component.enableToggle) {
+ Ext.apply(config, {
+ group : group,
+ checked: component.pressed,
+ listeners: {
+ checkchange: function(item, checked){
+ component.toggle(checked);
+ }
+ }
+ });
+ }
+
+ delete config.ownerCt;
+ delete config.xtype;
+ delete config.id;
+
+ return config;
+ },
+
+ /**
+ * @private
+ * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
+ * @param {Ext.menu.Menu} menu The menu to add to
+ * @param {Ext.Component} component The component to add
+ */
+ addComponentToMenu : function(menu, component) {
+ if (component instanceof Ext.Toolbar.Separator) {
+ menu.add('-');
+
+ } else if (Ext.isFunction(component.isXType)) {
+ if (component.isXType('splitbutton')) {
+ menu.add(this.createMenuConfig(component, true));
+
+ } else if (component.isXType('button')) {
+ menu.add(this.createMenuConfig(component, !component.menu));
+
+ } else if (component.isXType('buttongroup')) {
+ component.items.each(function(item){
+ this.addComponentToMenu(menu, item);
+ }, this);
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
+ * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
+ */
+ clearMenu : function(){
+ var menu = this.moreMenu;
+ if (menu && menu.items) {
+ menu.items.each(function(item){
+ delete item.menu;
+ });
+ }
+ },
+
+ /**
+ * @private
+ * Called before the expand menu is shown, this rebuilds the menu since it was last shown because
+ * it is possible that the items hidden due to space limitations on the Toolbar have changed since.
+ * @param {Ext.menu.Menu} m The menu
+ */
+ beforeMoreShow : function(menu) {
+ var items = this.container.items.items,
+ len = items.length,
+ item,
+ prev;
+
+ var needsSep = function(group, item){
+ return group.isXType('buttongroup') && !(item instanceof Ext.Toolbar.Separator);
+ };
+
+ this.clearMenu();
+ menu.removeAll();
+ for (var i = 0; i < len; i++) {
+ item = items[i];
+ if (item.xtbHidden) {
+ if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
+ menu.add('-');
+ }
+ this.addComponentToMenu(menu, item);
+ prev = item;
+ }
+ }
+
+ // put something so the menu isn't empty if no compatible items found
+ if (menu.items.length < 1) {
+ menu.add(this.noItemsMenuText);
+ }
+ },
+
+ /**
+ * @private
+ * Creates the expand trigger and menu, adding them to the <tr> 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
+ * <p>Layout manager used by {@link Ext.menu.Menu}. Generally this class should not need to be used directly.</p>
+ */
+ 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(
+ '<li id="{itemId}" class="{itemCls}">',
+ '<tpl if="needsIcon">',
+ '<img src="{icon}" class="{iconCls}"/>',
+ '</tpl>',
+ '</li>'
+ );
+ }
+
+ if(c && !c.rendered){
+ if(Ext.isNumber(position)){
+ position = target.dom.childNodes[position];
+ }
+ var a = this.getItemArgs(c);
+
+// The Component's positionEl is the <li> it is rendered into
+ c.render(c.positionEl = position ?
+ this.itemTpl.insertBefore(position, a, true) :
+ this.itemTpl.append(target, a, true));
+
+// Link the containing <li> to the item.
+ c.positionEl.menuItemId = c.getItemId();
+
+// If rendering a regular Component, and it needs an icon,
+// move the Component rightwards.
+ if (!a.isMenuItem && a.needsIcon) {
+ c.positionEl.addClass('x-menu-list-item-indent');
+ }
+ this.configureItem(c, position);
+ }else if(c && !this.isValidParent(c, target)){
+ if(Ext.isNumber(position)){
+ position = target.dom.childNodes[position];
+ }
+ target.dom.insertBefore(c.getActionEl().dom, position || null);
+ }
+ },
+
+ getItemArgs : function(c) {
+ var isMenuItem = c instanceof Ext.menu.Item;
+ return {
+ isMenuItem: isMenuItem,
+ needsIcon: !isMenuItem && (c.icon || c.iconCls),
+ icon: c.icon || Ext.BLANK_IMAGE_URL,
+ iconCls: 'x-menu-item-icon ' + (c.iconCls || ''),
+ itemId: 'x-menu-el-' + c.id,
+ itemCls: 'x-menu-list-item '
+ };
+ },
+
+ // Valid if the Component is in a <li> which is part of our target <ul>
+ 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
+ * <p>A specialized container representing the viewable application area (the browser viewport).</p>
+ * <p>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.</p>
+ * <p>The Viewport does not provide scrolling, so child Panels within the Viewport should provide
+ * for scrolling if needed using the {@link #autoScroll} config.</p>
+ * <p>An example showing a classic application border layout:</p><pre><code>
+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'
+ }
+ }]
+});
+</code></pre>
+ * @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
+ * <p>Panel is a container that has specific functionality and structural components that make
+ * it the perfect building block for application-oriented user interfaces.</p>
+ * <p>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.</p>
+ * <p>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 <code><b>{@link Ext.Container#layout layout}</b></code> 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 <b>does not apply any sizing</b>
+ * at all.</p>
+ * <p>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).</p>
+ * <p>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}.</p>
+ * @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.
+ * <p>This Element is used to house the {@link #title} and {@link #tools}</p>
+ * <br><p><b>Note</b>: see the Note for <code>{@link Ext.Component#el el}</code> also.</p>
+ * @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.
+ * <p>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.</p>
+ * <p>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.
+ * <br><p><b>Note</b>: see the Note for <code>{@link Ext.Component#el el}</code> also.</p>
+ * @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
+ * <p>A {@link Ext.DomHelper DomHelper} element specification object may be specified for any
+ * Panel Element.</p>
+ * <p>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 (<code>baseCls</code> will be
+ * replaced by <code>{@link #baseCls}</code>):</p>
+ * <pre>
+ * 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
+ * </pre>
+ * <p>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:</p>
+ * <pre><code>
+new Ext.Panel({
+ title: 'Message Title',
+ renderTo: Ext.getBody(),
+ width: 200, height: 130,
+ <b>bodyCfg</b>: {
+ 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}
+});
+ * </code></pre>
+ * <p>The example above also explicitly creates a <code>{@link #footer}</code> with custom markup and
+ * styling applied.</p>
+ */
+ /**
+ * @cfg {Object} headerCfg
+ * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
+ * of this Panel's {@link #header} Element. See <code>{@link #bodyCfg}</code> also.</p>
+ */
+ /**
+ * @cfg {Object} bwrapCfg
+ * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
+ * of this Panel's {@link #bwrap} Element. See <code>{@link #bodyCfg}</code> also.</p>
+ */
+ /**
+ * @cfg {Object} tbarCfg
+ * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
+ * of this Panel's {@link #tbar} Element. See <code>{@link #bodyCfg}</code> also.</p>
+ */
+ /**
+ * @cfg {Object} bbarCfg
+ * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
+ * of this Panel's {@link #bbar} Element. See <code>{@link #bodyCfg}</code> also.</p>
+ */
+ /**
+ * @cfg {Object} footerCfg
+ * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
+ * of this Panel's {@link #footer} Element. See <code>{@link #bodyCfg}</code> also.</p>
+ */
+ /**
+ * @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 <code>true</code>
+ * to enable closing in such situations. Defaults to <code>false</code>.
+ */
+ /**
+ * The Panel's footer {@link Ext.Element Element}. Read-only.
+ * <p>This Element is used to house the Panel's <code>{@link #buttons}</code> or <code>{@link #fbar}</code>.</p>
+ * <br><p><b>Note</b>: see the Note for <code>{@link Ext.Component#el el}</code> also.</p>
+ * @type Ext.Element
+ * @property footer
+ */
+ /**
+ * @cfg {Mixed} applyTo
+ * <p>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 <code>applyTo</code> 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.</p>
+ * <p>The following class names are supported (baseCls will be replaced by {@link #baseCls}):</p>
+ * <ul><li>baseCls + '-header'</li>
+ * <li>baseCls + '-header-text'</li>
+ * <li>baseCls + '-bwrap'</li>
+ * <li>baseCls + '-tbar'</li>
+ * <li>baseCls + '-body'</li>
+ * <li>baseCls + '-bbar'</li>
+ * <li>baseCls + '-footer'</li></ul>
+ * <p>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.</p>
+ */
+ /**
+ * @cfg {Object/Array} tbar
+ * <p>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}.</p>
+ * <p><b>Note:</b> Although a Toolbar may contain Field components, these will <b>not</b> 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 <b>will</b> be submitted because form
+ * submission parameters are collected from the DOM tree.</p>
+ */
+ /**
+ * @cfg {Object/Array} bbar
+ * <p>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}.</p>
+ * <p><b>Note:</b> Although a Toolbar may contain Field components, these will <b>not</b> 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 <b>will</b> be submitted because form
+ * submission parameters are collected from the DOM tree.</p>
+ */
+ /** @cfg {Object/Array} fbar
+ * <p>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.</p>
+ * <p>After render, the <code>fbar</code> property will be an {@link Ext.Toolbar Toolbar} instance.</p>
+ * <p>If <code>{@link #buttons}</code> are specified, they will supersede the <code>fbar</code> configuration property.</p>
+ * The Panel's <code>{@link #buttonAlign}</code> configuration affects the layout of these items, for example:
+ * <pre><code>
+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();
+ * </code></pre>
+ * <p><b>Note:</b> Although a Toolbar may contain Field components, these will <b>not</b> 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 <b>will</b> be submitted because form
+ * submission parameters are collected from the DOM tree.</p>
+ */
+ /**
+ * @cfg {Boolean} header
+ * <code>true</code> to create the Panel's header element explicitly, <code>false</code> to skip creating
+ * it. If a <code>{@link #title}</code> is set the header will be created automatically, otherwise it will not.
+ * If a <code>{@link #title}</code> is set but <code>header</code> is explicitly set to <code>false</code>, the header
+ * will not be rendered.
+ */
+ /**
+ * @cfg {Boolean} footer
+ * <code>true</code> to create the footer element explicitly, false to skip creating it. The footer
+ * will be created automatically if <code>{@link #buttons}</code> or a <code>{@link #fbar}</code> have
+ * been configured. See <code>{@link #bodyCfg}</code> for an example.
+ */
+ /**
+ * @cfg {String} title
+ * The title text to be used as innerHTML (html tags are accepted) to display in the panel
+ * <code>{@link #header}</code> (defaults to ''). When a <code>title</code> is specified the
+ * <code>{@link #header}</code> element will automatically be created and displayed unless
+ * {@link #header} is explicitly set to <code>false</code>. If you do not want to specify a
+ * <code>title</code> at config time, but you may want one later, you must either specify a non-empty
+ * <code>title</code> (a blank space ' ' will do) or <code>header:true</code> so that the container
+ * element will get created.
+ */
+ /**
+ * @cfg {Array} buttons
+ * <code>buttons</code> will be used as <code>{@link Ext.Container#items items}</code> for the toolbar in
+ * the footer (<code>{@link #fbar}</code>). 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 <code>minWidth</code> or the Panel is configured with <code>minButtonWidth</code>,
+ * 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.<p>
+ * 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.</p>
+ */
+ /**
+ * @cfg {Boolean} frame
+ * <code>false</code> by default to render with plain 1px square borders. <code>true</code> to render with
+ * 9 elements, complete with custom rounded corners (also see {@link Ext.Element#boxWrap}).
+ * <p>The template generated for each condition is depicted below:</p><pre><code>
+ *
+// 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>
+ * </code></pre>
+ */
+ /**
+ * @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 '').
+ * <p>An example of specifying a custom icon class would be something like:
+ * </p><pre><code>
+// 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; }
+</code></pre>
+ */
+ /**
+ * @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 <code><b></b>tools.<i><tool-type></i></code>
+ * <p>Each tool config may contain the following properties:
+ * <div class="mdetail-params"><ul>
+ * <li><b>id</b> : String<div class="sub-desc"><b>Required.</b> The type
+ * of tool to create. By default, this assigns a CSS class of the form <code>x-tool-<i><tool-type></i></code> 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:
+ * <ul>
+ * <div class="x-tool x-tool-toggle" style="float:left; margin-right:5;"> </div><div><code> toggle</code> (Created by default when {@link #collapsible} is <code>true</code>)</div>
+ * <div class="x-tool x-tool-close" style="float:left; margin-right:5;"> </div><div><code> close</code></div>
+ * <div class="x-tool x-tool-minimize" style="float:left; margin-right:5;"> </div><div><code> minimize</code></div>
+ * <div class="x-tool x-tool-maximize" style="float:left; margin-right:5;"> </div><div><code> maximize</code></div>
+ * <div class="x-tool x-tool-restore" style="float:left; margin-right:5;"> </div><div><code> restore</code></div>
+ * <div class="x-tool x-tool-gear" style="float:left; margin-right:5;"> </div><div><code> gear</code></div>
+ * <div class="x-tool x-tool-pin" style="float:left; margin-right:5;"> </div><div><code> pin</code></div>
+ * <div class="x-tool x-tool-unpin" style="float:left; margin-right:5;"> </div><div><code> unpin</code></div>
+ * <div class="x-tool x-tool-right" style="float:left; margin-right:5;"> </div><div><code> right</code></div>
+ * <div class="x-tool x-tool-left" style="float:left; margin-right:5;"> </div><div><code> left</code></div>
+ * <div class="x-tool x-tool-up" style="float:left; margin-right:5;"> </div><div><code> up</code></div>
+ * <div class="x-tool x-tool-down" style="float:left; margin-right:5;"> </div><div><code> down</code></div>
+ * <div class="x-tool x-tool-refresh" style="float:left; margin-right:5;"> </div><div><code> refresh</code></div>
+ * <div class="x-tool x-tool-minus" style="float:left; margin-right:5;"> </div><div><code> minus</code></div>
+ * <div class="x-tool x-tool-plus" style="float:left; margin-right:5;"> </div><div><code> plus</code></div>
+ * <div class="x-tool x-tool-help" style="float:left; margin-right:5;"> </div><div><code> help</code></div>
+ * <div class="x-tool x-tool-search" style="float:left; margin-right:5;"> </div><div><code> search</code></div>
+ * <div class="x-tool x-tool-save" style="float:left; margin-right:5;"> </div><div><code> save</code></div>
+ * <div class="x-tool x-tool-print" style="float:left; margin-right:5;"> </div><div><code> print</code></div>
+ * </ul></div></li>
+ * <li><b>handler</b> : Function<div class="sub-desc"><b>Required.</b> The function to
+ * call when clicked. Arguments passed are:<ul>
+ * <li><b>event</b> : Ext.EventObject<div class="sub-desc">The click event.</div></li>
+ * <li><b>toolEl</b> : Ext.Element<div class="sub-desc">The tool Element.</div></li>
+ * <li><b>panel</b> : Ext.Panel<div class="sub-desc">The host Panel</div></li>
+ * <li><b>tc</b> : Object<div class="sub-desc">The tool configuration object</div></li>
+ * </ul></div></li>
+ * <li><b>stopEvent</b> : Boolean<div class="sub-desc">Defaults to true. Specify as false to allow click event to propagate.</div></li>
+ * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the handler.</div></li>
+ * <li><b>qtip</b> : String/Object<div class="sub-desc">A tip string, or
+ * a config argument to {@link Ext.QuickTip#register}</div></li>
+ * <li><b>hidden</b> : Boolean<div class="sub-desc">True to initially render hidden.</div></li>
+ * <li><b>on</b> : Object<div class="sub-desc">A listener config object specifiying
+ * event listeners in the format of an argument to {@link #addListener}</div></li>
+ * </ul></div>
+ * <p>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.</p>
+ * <p>Example usage:</p>
+ * <pre><code>
+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
+ }
+}]
+</code></pre>
+ * <p>For the custom id of <code>'help'</code> define two relevant css classes with a link to
+ * a 15x15 image:</p>
+ * <pre><code>
+.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;}
+</code></pre>
+ */
+ /**
+ * @cfg {Ext.Template/Ext.XTemplate} toolTemplate
+ * <p>A Template used to create {@link #tools} in the {@link #header} Element. Defaults to:</p><pre><code>
+new Ext.Template('<div class="x-tool x-tool-{id}">&#160;</div>')</code></pre>
+ * <p>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:</p><pre><code>
+var win = new Ext.Window({
+ tools: [{
+ id: 'download',
+ href: '/MyPdfDoc.pdf'
+ }],
+ toolTemplate: new Ext.XTemplate(
+ '<tpl if="id==\'download\'">',
+ '<a class="x-tool x-tool-pdf" href="{href}"></a>',
+ '</tpl>',
+ '<tpl if="id!=\'download\'">',
+ '<div class="x-tool x-tool-{id}">&#160;</div>',
+ '</tpl>'
+ ),
+ width:500,
+ height:300,
+ closeAction:'hide'
+});</code></pre>
+ * <p>Note that the CSS class 'x-tool-pdf' should have an associated style rule which provides an
+ * appropriate background image, something like:</p>
+ <pre><code>
+ a.x-tool-pdf {background-image: url(../shared/extjs/images/pdf.gif)!important;}
+ </code></pre>
+ */
+ /**
+ * @cfg {Boolean} hideCollapseTool
+ * <code>true</code> to hide the expand/collapse toggle button when <code>{@link #collapsible} == true</code>,
+ * <code>false</code> to display it (defaults to <code>false</code>).
+ */
+ /**
+ * @cfg {Boolean} titleCollapse
+ * <code>true</code> to allow expanding and collapsing the panel (when <code>{@link #collapsible} = true</code>)
+ * by clicking anywhere in the header bar, <code>false</code>) to allow it only by clicking to tool button
+ * (defaults to <code>false</code>)). If this panel is a child item of a border layout also see the
+ * {@link Ext.layout.BorderLayout.Region BorderLayout.Region}
+ * <code>{@link Ext.layout.BorderLayout.Region#floatable floatable}</code> config option.
+ */
+
+ /**
+ * @cfg {Mixed} floating
+ * <p>This property is used to configure the underlying {@link Ext.Layer}. Acceptable values for this
+ * configuration property are:</p><div class="mdetail-params"><ul>
+ * <li><b><code>false</code></b> : <b>Default.</b><div class="sub-desc">Display the panel inline where it is
+ * rendered.</div></li>
+ * <li><b><code>true</code></b> : <div class="sub-desc">Float the panel (absolute position it with automatic
+ * shimming and shadow).<ul>
+ * <div class="sub-desc">Setting floating to true will create an Ext.Layer for this panel and display the
+ * panel at negative offsets so that it is hidden.</div>
+ * <div class="sub-desc">Since the panel will be absolute positioned, the position must be set explicitly
+ * <i>after</i> render (e.g., <code>myPanel.setPosition(100,100);</code>).</div>
+ * <div class="sub-desc"><b>Note</b>: 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.</div>
+ * </ul></div></li>
+ * <li><b><code>{@link Ext.Layer object}</code></b> : <div class="sub-desc">The specified object will be used
+ * as the configuration object for the {@link Ext.Layer} that will be created.</div></li>
+ * </ul></div>
+ */
+ /**
+ * @cfg {Boolean/String} shadow
+ * <code>true</code> (or a valid Ext.Shadow {@link Ext.Shadow#mode} value) to display a shadow behind the
+ * panel, <code>false</code> to display no shadow (defaults to <code>'sides'</code>). Note that this option
+ * only applies when <code>{@link #floating} = true</code>.
+ */
+ /**
+ * @cfg {Number} shadowOffset
+ * The number of pixels to offset the shadow if displayed (defaults to <code>4</code>). Note that this
+ * option only applies when <code>{@link #floating} = true</code>.
+ */
+ /**
+ * @cfg {Boolean} shim
+ * <code>false</code> to disable the iframe shim in browsers which need one (defaults to <code>true</code>).
+ * Note that this option only applies when <code>{@link #floating} = true</code>.
+ */
+ /**
+ * @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 <code>null</code>).
+ */
+ /**
+ * @cfg {Boolean/Object} draggable
+ * <p><code>true</code> to enable dragging of this Panel (defaults to <code>false</code>).</p>
+ * <p>For custom drag/drop implementations, an <b>Ext.Panel.DD</b> config could also be passed
+ * in this config instead of <code>true</code>. 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.:
+ * <pre><code>
+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();
+</code></pre>
+ */
+ /**
+ * @cfg {Boolean} disabled
+ * Render this panel disabled (default is <code>false</code>). 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:
+ * <pre><code>
+new Ext.Panel({
+ ...
+ listeners: {
+ 'afterlayout': {
+ fn: function(p){
+ p.disable();
+ },
+ single: true // important, as many layouts can occur
+ }
+ }
+});
+</code></pre>
+ */
+ /**
+ * @cfg {Boolean} autoHeight
+ * <code>true</code> to use height:'auto', <code>false</code> to use fixed height (defaults to <code>false</code>).
+ * <b>Note</b>: Setting <code>autoHeight: true</code> 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 (<code>fit</code>, <code>border</code>, etc.) then setting <code>autoHeight: true</code>
+ * 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 <code>'x-panel'</code>).
+ * <p>Another option available by default is to specify <code>'x-plain'</code> which strips all styling
+ * except for required attributes for Ext layouts to function (e.g. overflow:hidden).
+ * See <code>{@link #unstyled}</code> also.</p>
+ */
+ baseCls : 'x-panel',
+ /**
+ * @cfg {String} collapsedCls
+ * A CSS class to add to the panel's element after it has been collapsed (defaults to
+ * <code>'x-panel-collapsed'</code>).
+ */
+ collapsedCls : 'x-panel-collapsed',
+ /**
+ * @cfg {Boolean} maskDisabled
+ * <code>true</code> to mask the panel when it is {@link #disabled}, <code>false</code> to not mask it (defaults
+ * to <code>true</code>). 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
+ * <code>true</code> to animate the transition when the panel is collapsed, <code>false</code> to skip the
+ * animation (defaults to <code>true</code> if the {@link Ext.Fx} class is available, otherwise <code>false</code>).
+ */
+ animCollapse : Ext.enableFx,
+ /**
+ * @cfg {Boolean} headerAsText
+ * <code>true</code> to display the panel <code>{@link #title}</code> in the <code>{@link #header}</code>,
+ * <code>false</code> to hide it (defaults to <code>true</code>).
+ */
+ headerAsText : true,
+ /**
+ * @cfg {String} buttonAlign
+ * The alignment of any {@link #buttons} added to this panel. Valid values are <code>'right'</code>,
+ * <code>'left'</code> and <code>'center'</code> (defaults to <code>'right'</code>).
+ */
+ buttonAlign : 'right',
+ /**
+ * @cfg {Boolean} collapsed
+ * <code>true</code> to render the panel collapsed, <code>false</code> to render it expanded (defaults to
+ * <code>false</code>).
+ */
+ collapsed : false,
+ /**
+ * @cfg {Boolean} collapseFirst
+ * <code>true</code> to make sure the collapse/expand toggle button always renders first (to the left of)
+ * any other tools in the panel's title bar, <code>false</code> to render it last (defaults to <code>true</code>).
+ */
+ collapseFirst : true,
+ /**
+ * @cfg {Number} minButtonWidth
+ * Minimum width in pixels of all {@link #buttons} in this panel (defaults to <code>75</code>)
+ */
+ minButtonWidth : 75,
+ /**
+ * @cfg {Boolean} unstyled
+ * Overrides the <code>{@link #baseCls}</code> setting to <code>{@link #baseCls} = 'x-plain'</code> 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<div class="mdetail-params"><ul>
+ * <li><code>header</code></li>
+ * <li><code>tbar</code> (top bar)</li>
+ * <li><code>body</code></li>
+ * <li><code>bbar</code> (bottom bar)</li>
+ * <li><code>footer</code></li>
+ * </ul></div>
+ * Defaults to '<code>body</code>'.
+ */
+ elements : 'body',
+ /**
+ * @cfg {Boolean} preventBodyReset
+ * Defaults to <code>false</code>. When set to <code>true</code>, an extra css class <code>'x-panel-normal'</code>
+ * 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 <b>body</b> 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 <tt>undefined</tt>.
+ *
+ */
+ padding: undefined,
+
+ /** @cfg {String} resizeEvent
+ * The event to listen to for resizing in layouts. Defaults to <tt>'bodyresize'</tt>.
+ */
+ 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 <code>{@link #buttons}</code>
+ * config property. Read only.
+ * @type Array
+ * @property buttons
+ */
+ this.fbar.items.each(function(c){
+ c.minWidth = c.minWidth || this.minButtonWidth;
+ }, this);
+ this.buttons = this.fbar.items.items;
+ },
+
+ // private
+ createToolbar: function(tb, options){
+ var result;
+ // Convert array to proper toolbar config
+ if(Ext.isArray(tb)){
+ tb = {
+ items: tb
+ };
+ }
+ result = tb.events ? Ext.apply(tb, options) : this.createComponent(Ext.apply({}, tb, options), 'toolbar');
+ this.toolbars.push(result);
+ return result;
+ },
+
+ // private
+ createElement : function(name, pnode){
+ if(this[name]){
+ pnode.appendChild(this[name].dom);
+ return;
+ }
+
+ if(name === 'bwrap' || this.elements.indexOf(name) != -1){
+ if(this[name+'Cfg']){
+ this[name] = Ext.fly(pnode).createChild(this[name+'Cfg']);
+ }else{
+ var el = document.createElement('div');
+ el.className = this[name+'Cls'];
+ this[name] = Ext.get(pnode.appendChild(el));
+ }
+ if(this[name+'CssClass']){
+ this[name].addClass(this[name+'CssClass']);
+ }
+ if(this[name+'Style']){
+ this[name].applyStyles(this[name+'Style']);
+ }
+ }
+ },
+
+ // private
+ onRender : function(ct, position){
+ Ext.Panel.superclass.onRender.call(this, ct, position);
+ this.createClasses();
+
+ var el = this.el,
+ d = el.dom,
+ bw,
+ ts;
+
+
+ if(this.collapsible && !this.hideCollapseTool){
+ this.tools = this.tools ? this.tools.slice(0) : [];
+ this.tools[this.collapseFirst?'unshift':'push']({
+ id: 'toggle',
+ handler : this.toggleCollapse,
+ scope: this
+ });
+ }
+
+ if(this.tools){
+ ts = this.tools;
+ this.elements += (this.header !== false) ? ',header' : '';
+ }
+ this.tools = {};
+
+ el.addClass(this.baseCls);
+ if(d.firstChild){ // existing markup
+ this.header = el.down('.'+this.headerCls);
+ this.bwrap = el.down('.'+this.bwrapCls);
+ var cp = this.bwrap ? this.bwrap : el;
+ this.tbar = cp.down('.'+this.tbarCls);
+ this.body = cp.down('.'+this.bodyCls);
+ this.bbar = cp.down('.'+this.bbarCls);
+ this.footer = cp.down('.'+this.footerCls);
+ this.fromMarkup = true;
+ }
+ if (this.preventBodyReset === true) {
+ el.addClass('x-panel-reset');
+ }
+ if(this.cls){
+ el.addClass(this.cls);
+ }
+
+ if(this.buttons){
+ this.elements += ',footer';
+ }
+
+ // This block allows for maximum flexibility and performance when using existing markup
+
+ // framing requires special markup
+ if(this.frame){
+ el.insertHtml('afterBegin', String.format(Ext.Element.boxMarkup, this.baseCls));
+
+ this.createElement('header', d.firstChild.firstChild.firstChild);
+ this.createElement('bwrap', d);
+
+ // append the mid and bottom frame to the bwrap
+ bw = this.bwrap.dom;
+ var ml = d.childNodes[1], bl = d.childNodes[2];
+ bw.appendChild(ml);
+ bw.appendChild(bl);
+
+ var mc = bw.firstChild.firstChild.firstChild;
+ this.createElement('tbar', mc);
+ this.createElement('body', mc);
+ this.createElement('bbar', mc);
+ this.createElement('footer', bw.lastChild.firstChild.firstChild);
+
+ if(!this.footer){
+ this.bwrap.dom.lastChild.className += ' x-panel-nofooter';
+ }
+ /*
+ * Store a reference to this element so:
+ * a) We aren't looking it up all the time
+ * b) The last element is reported incorrectly when using a loadmask
+ */
+ this.ft = Ext.get(this.bwrap.dom.lastChild);
+ this.mc = Ext.get(mc);
+ }else{
+ this.createElement('header', d);
+ this.createElement('bwrap', d);
+
+ // append the mid and bottom frame to the bwrap
+ bw = this.bwrap.dom;
+ this.createElement('tbar', bw);
+ this.createElement('body', bw);
+ this.createElement('bbar', bw);
+ this.createElement('footer', bw);
+
+ if(!this.header){
+ this.body.addClass(this.bodyCls + '-noheader');
+ if(this.tbar){
+ this.tbar.addClass(this.tbarCls + '-noheader');
+ }
+ }
+ }
+
+ if(Ext.isDefined(this.padding)){
+ this.body.setStyle('padding', this.body.addUnits(this.padding));
+ }
+
+ if(this.border === false){
+ this.el.addClass(this.baseCls + '-noborder');
+ this.body.addClass(this.bodyCls + '-noborder');
+ if(this.header){
+ this.header.addClass(this.headerCls + '-noborder');
+ }
+ if(this.footer){
+ this.footer.addClass(this.footerCls + '-noborder');
+ }
+ if(this.tbar){
+ this.tbar.addClass(this.tbarCls + '-noborder');
+ }
+ if(this.bbar){
+ this.bbar.addClass(this.bbarCls + '-noborder');
+ }
+ }
+
+ if(this.bodyBorder === false){
+ this.body.addClass(this.bodyCls + '-noborder');
+ }
+
+ this.bwrap.enableDisplayMode('block');
+
+ if(this.header){
+ this.header.unselectable();
+
+ // for tools, we need to wrap any existing header markup
+ if(this.headerAsText){
+ this.header.dom.innerHTML =
+ '<span class="' + this.headerTextCls + '">'+this.header.dom.innerHTML+'</span>';
+
+ if(this.iconCls){
+ this.setIconClass(this.iconCls);
+ }
+ }
+ }
+
+ if(this.floating){
+ this.makeFloating(this.floating);
+ }
+
+ if(this.collapsible && this.titleCollapse && this.header){
+ this.mon(this.header, 'click', this.toggleCollapse, this);
+ this.header.setStyle('cursor', 'pointer');
+ }
+ if(ts){
+ this.addTool.apply(this, ts);
+ }
+
+ // Render Toolbars.
+ if(this.fbar){
+ this.footer.addClass('x-panel-btns');
+ this.fbar.ownerCt = this;
+ this.fbar.render(this.footer);
+ this.footer.createChild({cls:'x-clear'});
+ }
+ if(this.tbar && this.topToolbar){
+ this.topToolbar.ownerCt = this;
+ this.topToolbar.render(this.tbar);
+ }
+ if(this.bbar && this.bottomToolbar){
+ this.bottomToolbar.ownerCt = this;
+ this.bottomToolbar.render(this.bbar);
+ }
+ },
+
+ /**
+ * Sets the CSS class that provides the icon image for this panel. This method will replace any existing
+ * icon class if one has already been set and fire the {@link #iconchange} event after completion.
+ * @param {String} cls The new CSS class name
+ */
+ setIconClass : function(cls){
+ var old = this.iconCls;
+ this.iconCls = cls;
+ if(this.rendered && this.header){
+ if(this.frame){
+ this.header.addClass('x-panel-icon');
+ this.header.replaceClass(old, this.iconCls);
+ }else{
+ var hd = this.header,
+ img = hd.child('img.x-panel-inline-icon');
+ if(img){
+ Ext.fly(img).replaceClass(old, this.iconCls);
+ }else{
+ var hdspan = hd.child('span.' + this.headerTextCls);
+ if (hdspan) {
+ Ext.DomHelper.insertBefore(hdspan.dom, {
+ tag:'img', src: Ext.BLANK_IMAGE_URL, cls:'x-panel-inline-icon '+this.iconCls
+ });
+ }
+ }
+ }
+ }
+ this.fireEvent('iconchange', this, cls, old);
+ },
+
+ // private
+ makeFloating : function(cfg){
+ this.floating = true;
+ this.el = new Ext.Layer(Ext.apply({}, cfg, {
+ shadow: Ext.isDefined(this.shadow) ? this.shadow : 'sides',
+ shadowOffset: this.shadowOffset,
+ constrain:false,
+ shim: this.shim === false ? false : undefined
+ }), this.el);
+ },
+
+ /**
+ * Returns the {@link Ext.Toolbar toolbar} from the top (<code>{@link #tbar}</code>) section of the panel.
+ * @return {Ext.Toolbar} The toolbar
+ */
+ getTopToolbar : function(){
+ return this.topToolbar;
+ },
+
+ /**
+ * Returns the {@link Ext.Toolbar toolbar} from the bottom (<code>{@link #bbar}</code>) section of the panel.
+ * @return {Ext.Toolbar} The toolbar
+ */
+ getBottomToolbar : function(){
+ return this.bottomToolbar;
+ },
+
+ /**
+ * Returns the {@link Ext.Toolbar toolbar} from the footer (<code>{@link #fbar}</code>) section of the panel.
+ * @return {Ext.Toolbar} The toolbar
+ */
+ getFooterToolbar : function() {
+ return this.fbar;
+ },
+
+ /**
+ * Adds a button to this panel. Note that this method must be called prior to rendering. The preferred
+ * approach is to add buttons via the {@link #buttons} config.
+ * @param {String/Object} config A valid {@link Ext.Button} config. A string will become the text for a default
+ * button config, an object will be treated as a button config object.
+ * @param {Function} handler The function to be called on button {@link Ext.Button#click}
+ * @param {Object} scope The scope (<code>this</code> 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(
+ '<div class="x-tool x-tool-{id}"> </div>'
+ );
+ 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(){
+ /**
+ * <p>If this Panel is configured {@link #draggable}, this property will contain
+ * an instance of {@link Ext.dd.DragSource} which handles dragging the Panel.</p>
+ * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
+ * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
+ * @type Ext.dd.DragSource.
+ * @property dd
+ */
+ this.dd = new Ext.Panel.DD(this, Ext.isBoolean(this.draggable) ? null : this.draggable);
+ },
+
+ // private
+ beforeEffect : function(anim){
+ if(this.floating){
+ this.el.beforeAction();
+ }
+ if(anim !== false){
+ this.el.addClass('x-panel-animated');
+ }
+ },
+
+ // private
+ afterEffect : function(anim){
+ this.syncShadow();
+ this.el.removeClass('x-panel-animated');
+ },
+
+ // private - wraps up an animation param with internal callbacks
+ createEffect : function(a, cb, scope){
+ var o = {
+ scope:scope,
+ block:true
+ };
+ if(a === true){
+ o.callback = cb;
+ return o;
+ }else if(!a.callback){
+ o.callback = cb;
+ }else { // wrap it up
+ o.callback = function(){
+ cb.call(scope);
+ Ext.callback(a.callback, a.scope);
+ };
+ }
+ return Ext.applyIf(o, a);
+ },
+
+ /**
+ * Collapses the panel body so that it becomes hidden. Fires the {@link #beforecollapse} event which will
+ * cancel the collapse action if it returns false.
+ * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
+ * {@link #animCollapse} panel config)
+ * @return {Ext.Panel} this
+ */
+ collapse : function(animate){
+ if(this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforecollapse', this, animate) === false){
+ return;
+ }
+ var doAnim = animate === true || (animate !== false && this.animCollapse);
+ this.beforeEffect(doAnim);
+ this.onCollapse(doAnim, animate);
+ return this;
+ },
+
+ // private
+ onCollapse : function(doAnim, animArg){
+ if(doAnim){
+ this[this.collapseEl].slideOut(this.slideAnchor,
+ Ext.apply(this.createEffect(animArg||true, this.afterCollapse, this),
+ this.collapseDefaults));
+ }else{
+ this[this.collapseEl].hide(this.hideMode);
+ this.afterCollapse(false);
+ }
+ },
+
+ // private
+ afterCollapse : function(anim){
+ this.collapsed = true;
+ this.el.addClass(this.collapsedCls);
+ if(anim !== false){
+ this[this.collapseEl].hide(this.hideMode);
+ }
+ this.afterEffect(anim);
+
+ // Reset lastSize of all sub-components so they KNOW they are in a collapsed container
+ this.cascade(function(c) {
+ if (c.lastSize) {
+ c.lastSize = { width: 0, height: 0 };
+ }
+ });
+ this.fireEvent('collapse', this);
+ },
+
+ /**
+ * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will
+ * cancel the expand action if it returns false.
+ * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
+ * {@link #animCollapse} panel config)
+ * @return {Ext.Panel} this
+ */
+ expand : function(animate){
+ if(!this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforeexpand', this, animate) === false){
+ return;
+ }
+ var doAnim = animate === true || (animate !== false && this.animCollapse);
+ this.el.removeClass(this.collapsedCls);
+ this.beforeEffect(doAnim);
+ this.onExpand(doAnim, animate);
+ return this;
+ },
+
+ // private
+ onExpand : function(doAnim, animArg){
+ if(doAnim){
+ this[this.collapseEl].slideIn(this.slideAnchor,
+ Ext.apply(this.createEffect(animArg||true, this.afterExpand, this),
+ this.expandDefaults));
+ }else{
+ this[this.collapseEl].show(this.hideMode);
+ this.afterExpand(false);
+ }
+ },
+
+ // private
+ afterExpand : function(anim){
+ this.collapsed = false;
+ if(anim !== false){
+ this[this.collapseEl].show(this.hideMode);
+ }
+ this.afterEffect(anim);
+ if (this.deferLayout) {
+ delete this.deferLayout;
+ this.doLayout(true);
+ }
+ this.fireEvent('expand', this);
+ },
+
+ /**
+ * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel.
+ * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
+ * {@link #animCollapse} panel config)
+ * @return {Ext.Panel} this
+ */
+ toggleCollapse : function(animate){
+ this[this.collapsed ? 'expand' : 'collapse'](animate);
+ return this;
+ },
+
+ // private
+ onDisable : function(){
+ if(this.rendered && this.maskDisabled){
+ this.el.mask();
+ }
+ Ext.Panel.superclass.onDisable.call(this);
+ },
+
+ // private
+ onEnable : function(){
+ if(this.rendered && this.maskDisabled){
+ this.el.unmask();
+ }
+ Ext.Panel.superclass.onEnable.call(this);
+ },
+
+ // private
+ onResize : function(adjWidth, adjHeight, rawWidth, rawHeight){
+ var w = adjWidth,
+ h = adjHeight;
+
+ if(Ext.isDefined(w) || Ext.isDefined(h)){
+ if(!this.collapsed){
+ // First, set the the Panel's body width.
+ // If we have auto-widthed it, get the resulting full offset width so we can size the Toolbars to match
+ // The Toolbars must not buffer this resize operation because we need to know their heights.
+
+ if(Ext.isNumber(w)){
+ this.body.setWidth(w = this.adjustBodyWidth(w - this.getFrameWidth()));
+ } else if (w == 'auto') {
+ w = this.body.setWidth('auto').dom.offsetWidth;
+ } else {
+ w = this.body.dom.offsetWidth;
+ }
+
+ if(this.tbar){
+ this.tbar.setWidth(w);
+ if(this.topToolbar){
+ this.topToolbar.setSize(w);
+ }
+ }
+ if(this.bbar){
+ this.bbar.setWidth(w);
+ if(this.bottomToolbar){
+ this.bottomToolbar.setSize(w);
+ // The bbar does not move on resize without this.
+ if (Ext.isIE) {
+ this.bbar.setStyle('position', 'static');
+ this.bbar.setStyle('position', '');
+ }
+ }
+ }
+ if(this.footer){
+ this.footer.setWidth(w);
+ if(this.fbar){
+ this.fbar.setSize(Ext.isIE ? (w - this.footer.getFrameWidth('lr')) : 'auto');
+ }
+ }
+
+ // At this point, the Toolbars must be layed out for getFrameHeight to find a result.
+ if(Ext.isNumber(h)){
+ h = Math.max(0, h - this.getFrameHeight());
+ //h = Math.max(0, h - (this.getHeight() - this.body.getHeight()));
+ this.body.setHeight(h);
+ }else if(h == 'auto'){
+ this.body.setHeight(h);
+ }
+
+ if(this.disabled && this.el._mask){
+ this.el._mask.setSize(this.el.dom.clientWidth, this.el.getHeight());
+ }
+ }else{
+ // Adds an event to set the correct height afterExpand. This accounts for the deferHeight flag in panel
+ this.queuedBodySize = {width: w, height: h};
+ if(!this.queuedExpand && this.allowQueuedExpand !== false){
+ this.queuedExpand = true;
+ this.on('expand', function(){
+ delete this.queuedExpand;
+ this.onResize(this.queuedBodySize.width, this.queuedBodySize.height);
+ }, this, {single:true});
+ }
+ }
+ this.onBodyResize(w, h);
+ }
+ this.syncShadow();
+ Ext.Panel.superclass.onResize.call(this, adjWidth, adjHeight, rawWidth, rawHeight);
+
+ },
+
+ // private
+ onBodyResize: function(w, h){
+ this.fireEvent('bodyresize', this, w, h);
+ },
+
+ // private
+ getToolbarHeight: function(){
+ var h = 0;
+ if(this.rendered){
+ Ext.each(this.toolbars, function(tb){
+ h += tb.getHeight();
+ }, this);
+ }
+ return h;
+ },
+
+ // deprecate
+ adjustBodyHeight : function(h){
+ return h;
+ },
+
+ // private
+ adjustBodyWidth : function(w){
+ return w;
+ },
+
+ // private
+ onPosition : function(){
+ this.syncShadow();
+ },
+
+ /**
+ * Returns the width in pixels of the framing elements of this panel (not including the body width). To
+ * retrieve the body width see {@link #getInnerWidth}.
+ * @return {Number} The frame width
+ */
+ getFrameWidth : function(){
+ var w = this.el.getFrameWidth('lr') + this.bwrap.getFrameWidth('lr');
+
+ if(this.frame){
+ var l = this.bwrap.dom.firstChild;
+ w += (Ext.fly(l).getFrameWidth('l') + Ext.fly(l.firstChild).getFrameWidth('r'));
+ w += this.mc.getFrameWidth('lr');
+ }
+ return w;
+ },
+
+ /**
+ * Returns the height in pixels of the framing elements of this panel (including any top and bottom bars and
+ * header and footer elements, but not including the body height). To retrieve the body height see {@link #getInnerHeight}.
+ * @return {Number} The frame height
+ */
+ getFrameHeight : function() {
+ var h = Math.max(0, this.getHeight() - this.body.getHeight());
+
+ if (isNaN(h)) {
+ h = 0;
+ }
+ return h;
+
+ /* Deprecate
+ var h = this.el.getFrameWidth('tb') + this.bwrap.getFrameWidth('tb');
+ h += (this.tbar ? this.tbar.getHeight() : 0) +
+ (this.bbar ? this.bbar.getHeight() : 0);
+
+ if(this.frame){
+ h += this.el.dom.firstChild.offsetHeight + this.ft.dom.offsetHeight + this.mc.getFrameWidth('tb');
+ }else{
+ h += (this.header ? this.header.getHeight() : 0) +
+ (this.footer ? this.footer.getHeight() : 0);
+ }
+ return h;
+ */
+ },
+
+ /**
+ * Returns the width in pixels of the body element (not including the width of any framing elements).
+ * For the frame width see {@link #getFrameWidth}.
+ * @return {Number} The body width
+ */
+ getInnerWidth : function(){
+ return this.getSize().width - this.getFrameWidth();
+ },
+
+ /**
+ * Returns the height in pixels of the body element (not including the height of any framing elements).
+ * For the frame height see {@link #getFrameHeight}.
+ * @return {Number} The body height
+ */
+ getInnerHeight : function(){
+ return this.body.getHeight();
+ /* Deprecate
+ return this.getSize().height - this.getFrameHeight();
+ */
+ },
+
+ // private
+ syncShadow : function(){
+ if(this.floating){
+ this.el.sync(true);
+ }
+ },
+
+ // private
+ getLayoutTarget : function(){
+ return this.body;
+ },
+
+ // private
+ getContentTarget : function(){
+ return this.body;
+ },
+
+ /**
+ * <p>Sets the title text for the panel and optionally the {@link #iconCls icon class}.</p>
+ * <p>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 <code>{@link #title}</code>,
+ * or configuring it with <code><b>{@link #header}: true</b></code>.</p>
+ * @param {String} title The title text to set
+ * @param {String} iconCls (optional) {@link #iconCls iconCls} A user-defined CSS class that provides the icon image for this panel
+ */
+ setTitle : function(title, iconCls){
+ this.title = title;
+ if(this.header && this.headerAsText){
+ this.header.child('span').update(title);
+ }
+ if(iconCls){
+ this.setIconClass(iconCls);
+ }
+ this.fireEvent('titlechange', this, title);
+ return this;
+ },
+
+ /**
+ * Get the {@link Ext.Updater} for this panel. Enables you to perform Ajax updates of this panel's body.
+ * @return {Ext.Updater} The Updater
+ */
+ getUpdater : function(){
+ return this.body.getUpdater();
+ },
+
+ /**
+ * Loads this content panel immediately with content returned from an XHR call.
+ * @param {Object/String/Function} config A config object containing any of the following options:
+<pre><code>
+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
+});
+</code></pre>
+ * The only required property is url. The optional properties nocache, text and scripts
+ * are shorthand for disableCaching, indicatorText and loadScripts and are used to set their
+ * associated property on this panel Updater instance.
+ * @return {Ext.Panel} this
+ */
+ load : function(){
+ var um = this.body.getUpdater();
+ um.update.apply(um, arguments);
+ return this;
+ },
+
+ // private
+ beforeDestroy : function(){
+ Ext.Panel.superclass.beforeDestroy.call(this);
+ if(this.header){
+ this.header.removeAllListeners();
+ }
+ if(this.tools){
+ for(var k in this.tools){
+ Ext.destroy(this.tools[k]);
+ }
+ }
+ if(this.toolbars.length > 0){
+ Ext.each(this.toolbars, function(tb){
+ tb.un('afterlayout', this.syncHeight, this);
+ tb.un('remove', this.syncHeight, this);
+ }, this);
+ }
+ if(Ext.isArray(this.buttons)){
+ while(this.buttons.length) {
+ Ext.destroy(this.buttons[0]);
+ }
+ }
+ if(this.rendered){
+ Ext.destroy(
+ this.ft,
+ this.header,
+ this.footer,
+ this.toolbars,
+ this.tbar,
+ this.bbar,
+ this.body,
+ this.mc,
+ this.bwrap
+ );
+ if (this.fbar) {
+ Ext.destroy(
+ this.fbar,
+ this.fbar.el
+ );
+ }
+ }else{
+ Ext.destroy(
+ this.topToolbar,
+ this.bottomToolbar
+ );
+ }
+ },
+
+ // private
+ createClasses : function(){
+ this.headerCls = this.baseCls + '-header';
+ this.headerTextCls = this.baseCls + '-header-text';
+ this.bwrapCls = this.baseCls + '-bwrap';
+ this.tbarCls = this.baseCls + '-tbar';
+ this.bodyCls = this.baseCls + '-body';
+ this.bbarCls = this.baseCls + '-bbar';
+ this.footerCls = this.baseCls + '-footer';
+ },
+
+ // private
+ createGhost : function(cls, useShim, appendTo){
+ var el = document.createElement('div');
+ el.className = 'x-panel-ghost ' + (cls ? cls : '');
+ if(this.header){
+ el.appendChild(this.el.dom.firstChild.cloneNode(true));
+ }
+ Ext.fly(el.appendChild(document.createElement('ul'))).setHeight(this.bwrap.getHeight());
+ el.style.width = this.el.dom.offsetWidth + 'px';;
+ if(!appendTo){
+ this.container.dom.appendChild(el);
+ }else{
+ Ext.getDom(appendTo).appendChild(el);
+ }
+ if(useShim !== false && this.el.useShim !== false){
+ var layer = new Ext.Layer({shadow:false, useDisplay:true, constrain:false}, el);
+ layer.show();
+ return layer;
+ }else{
+ return new Ext.Element(el);
+ }
+ },
+
+ // 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});
+ },
+
+ /**
+ * 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 {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 <tt>true</tt>.
+ */
+ allowBlur: true,
+ /**
+ * @cfg {Boolean/String} autoSize
+ * True for the editor to automatically adopt the size of the underlying field, "width" to adopt the width only,
+ * or "height" to adopt the height only, "none" to always use the field dimensions. (defaults to false)
+ */
+ /**
+ * @cfg {Boolean} revertInvalid
+ * True to automatically revert the field value and cancel the edit when the user completes an edit and the field
+ * validation fails (defaults to true)
+ */
+ /**
+ * @cfg {Boolean} ignoreNoChange
+ * True to skip the edit completion process (no save, no events fired) if the user completes an edit and
+ * the value has not changed (defaults to false). Applies only to string values - edits for other data types
+ * will never be ignored.
+ */
+ /**
+ * @cfg {Boolean} hideEl
+ * False to keep the bound element visible while the editor is displayed (defaults to true)
+ */
+ /**
+ * @cfg {Mixed} value
+ * The data value of the underlying field (defaults to "")
+ */
+ value : "",
+ /**
+ * @cfg {String} alignment
+ * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "c-c?").
+ */
+ alignment: "c-c?",
+ /**
+ * @cfg {Array} offsets
+ * The offsets to use when aligning (see {@link Ext.Element#alignTo} for more details. Defaults to <tt>[0, 0]</tt>.
+ */
+ offsets: [0, 0],
+ /**
+ * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop"
+ * for bottom-right shadow (defaults to "frame")
+ */
+ shadow : "frame",
+ /**
+ * @cfg {Boolean} constrain True to constrain the editor to the viewport
+ */
+ constrain : false,
+ /**
+ * @cfg {Boolean} swallowKeys Handle the keydown/keypress events so they don't propagate (defaults to true)
+ */
+ swallowKeys : true,
+ /**
+ * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed. Defaults to <tt>true</tt>.
+ */
+ completeOnEnter : true,
+ /**
+ * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed. Defaults to <tt>true</tt>.
+ */
+ cancelOnEsc : true,
+ /**
+ * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false)
+ */
+ updateEl : false,
+
+ initComponent : function(){
+ Ext.Editor.superclass.initComponent.call(this);
+ this.addEvents(
+ /**
+ * @event beforestartedit
+ * Fires when editing is initiated, but before the value changes. Editing can be canceled by returning
+ * false from the handler of this event.
+ * @param {Editor} this
+ * @param {Ext.Element} boundEl The underlying element bound to this editor
+ * @param {Mixed} value The field value being set
+ */
+ "beforestartedit",
+ /**
+ * @event startedit
+ * Fires when this editor is displayed
+ * @param {Ext.Element} boundEl The underlying element bound to this editor
+ * @param {Mixed} value The starting field value
+ */
+ "startedit",
+ /**
+ * @event beforecomplete
+ * Fires after a change has been made to the field, but before the change is reflected in the underlying
+ * field. Saving the change to the field can be canceled by returning false from the handler of this event.
+ * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
+ * event will not fire since no edit actually occurred.
+ * @param {Editor} this
+ * @param {Mixed} value The current field value
+ * @param {Mixed} startValue The original field value
+ */
+ "beforecomplete",
+ /**
+ * @event complete
+ * Fires after editing is complete and any changed value has been written to the underlying field.
+ * @param {Editor} this
+ * @param {Mixed} value The current field value
+ * @param {Mixed} startValue The original field value
+ */
+ "complete",
+ /**
+ * @event canceledit
+ * Fires after editing has been canceled and the editor's value has been reset.
+ * @param {Editor} this
+ * @param {Mixed} value The user-entered field value that was discarded
+ * @param {Mixed} startValue The original field value that was set back into the editor after cancel
+ */
+ "canceledit",
+ /**
+ * @event specialkey
+ * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. You can check
+ * {@link Ext.EventObject#getKey} to determine which key was pressed.
+ * @param {Ext.form.Field} this
+ * @param {Ext.EventObject} e The event object
+ */
+ "specialkey"
+ );
+ },
+
+ // private
+ 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);
+ },
+
+ /**
+ * Starts the editing process and shows the editor.
+ * @param {Mixed} el The element to edit
+ * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
+ * to the innerHTML of el.
+ */
+ startEdit : function(el, value){
+ if(this.editing){
+ this.completeEdit();
+ }
+ this.boundEl = Ext.get(el);
+ var v = value !== undefined ? value : this.boundEl.dom.innerHTML;
+ if(!this.rendered){
+ this.render(this.parentEl || document.body);
+ }
+ if(this.fireEvent("beforestartedit", this, this.boundEl, v) !== false){
+ this.startValue = v;
+ this.field.reset();
+ this.field.setValue(v);
+ this.realign(true);
+ this.editing = true;
+ this.show();
+ }
+ },
+
+ // private
+ doAutoSize : function(){
+ if(this.autoSize){
+ var sz = this.boundEl.getSize(),
+ fs = this.field.getSize();
+
+ switch(this.autoSize){
+ case "width":
+ this.setSize(sz.width, fs.height);
+ break;
+ case "height":
+ this.setSize(fs.width, sz.height);
+ break;
+ case "none":
+ this.setSize(fs.width, fs.height);
+ break;
+ default:
+ this.setSize(sz.width, sz.height);
+ }
+ }
+ },
+
+ /**
+ * Sets the height and width of this editor.
+ * @param {Number} width The new width
+ * @param {Number} height The new height
+ */
+ setSize : function(w, h){
+ delete this.field.lastSize;
+ this.field.setSize(w, h);
+ if(this.el){
+ if(Ext.isGecko2 || Ext.isOpera){
+ // prevent layer scrollbars
+ this.el.setSize(w, h);
+ }
+ 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);
+ }
+ return;
+ }
+ if(String(v) === String(this.startValue) && this.ignoreNoChange){
+ this.hideEdit(remainVisible);
+ return;
+ }
+ if(this.fireEvent("beforecomplete", this, v, this.startValue) !== false){
+ v = this.getValue();
+ if(this.updateEl && this.boundEl){
+ this.boundEl.update(v);
+ }
+ this.hideEdit(remainVisible);
+ this.fireEvent("complete", this, v, this.startValue);
+ }
+ },
+
+ // private
+ onShow : function(){
+ this.el.show();
+ if(this.hideEl !== false){
+ this.boundEl.hide();
+ }
+ this.field.show().focus(false, true);
+ this.fireEvent("startedit", this.boundEl, this.startValue);
+ },
+
+ /**
+ * 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();
+ }
+ },
+
+ // 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();
+ }
+ },
+
+ // private
+ onHide : function(){
+ if(this.editing){
+ this.completeEdit();
+ return;
+ }
+ this.field.blur();
+ if(this.field.collapse){
+ this.field.collapse();
+ }
+ this.el.hide();
+ if(this.hideEl !== false){
+ this.boundEl.show();
+ }
+ },
+
+ /**
+ * Sets the data value of the editor
+ * @param {Mixed} value Any valid value supported by the underlying field
+ */
+ setValue : function(v){
+ this.field.setValue(v);
+ },
+
+ /**
+ * Gets the data value of the editor
+ * @return {Mixed} The data value
+ */
+ getValue : function(){
+ return this.field.getValue();
+ },
+
+ beforeDestroy : function(){
+ Ext.destroyMembers(this, 'field');
+
+ delete this.parentEl;
+ delete this.boundEl;
+ }
+});
+Ext.reg('editor', Ext.Editor);
+/**
+ * @class Ext.ColorPalette
+ * @extends Ext.Component
+ * Simple color palette class for choosing colors. The palette can be rendered to any container.<br />
+ * Here's an example of typical usage:
+ * <pre><code>
+var cp = new Ext.ColorPalette({value:'993300'}); // initial selected color
+cp.render('my-div');
+
+cp.on('select', function(palette, selColor){
+ // do something with selColor
+});
+</code></pre>
+ * @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 <tt>'click'</tt>.
+ */
+ clickEvent :'click',
+ // private
+ ctype : 'Ext.ColorPalette',
+
+ /**
+ * @cfg {Boolean} allowReselect If set to true then reselecting a color that is already selected fires the {@link #select} event
+ */
+ allowReselect : false,
+
+ /**
+ * <p>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.</p>
+ * <p>You can override individual colors if needed:</p>
+ * <pre><code>
+var cp = new Ext.ColorPalette();
+cp.colors[0] = 'FF0000'; // change the first box to red
+</code></pre>
+
+Or you can provide a custom array of your own for complete control:
+<pre><code>
+var cp = new Ext.ColorPalette();
+cp.colors = ['000000', '993300', '333300'];
+</code></pre>
+ * @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:<div class="mdetail-params"><ul>
+ * <li><code>palette</code> : ColorPalette<div class="sub-desc">The {@link #palette Ext.ColorPalette}.</div></li>
+ * <li><code>color</code> : String<div class="sub-desc">The 6-digit color hex code (without the # symbol).</div></li>
+ * </ul></div>
+ */
+ /**
+ * @cfg {Object} scope
+ * The scope (<tt><b>this</b></tt> reference) in which the <code>{@link #handler}</code>
+ * function will be called. Defaults to this ColorPalette instance.
+ */
+
+ // private
+ initComponent : function(){
+ Ext.ColorPalette.superclass.initComponent.call(this);
+ this.addEvents(
+ /**
+ * @event select
+ * Fires when a color is selected
+ * @param {ColorPalette} this
+ * @param {String} color The 6-digit color hex code (without the # symbol)
+ */
+ 'select'
+ );
+
+ if(this.handler){
+ this.on('select', this.handler, this.scope, true);
+ }
+ },
+
+ // private
+ onRender : function(container, position){
+ this.autoEl = {
+ tag: 'div',
+ cls: this.itemCls
+ };
+ Ext.ColorPalette.superclass.onRender.call(this, container, position);
+ var t = this.tpl || new Ext.XTemplate(
+ '<tpl for="."><a href="#" class="color-{.}" hidefocus="on"><em><span style="background:#{.}" unselectable="on"> </span></em></a></tpl>'
+ );
+ 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});
+ }
+ },
+
+ // private
+ afterRender : function(){
+ Ext.ColorPalette.superclass.afterRender.call(this);
+ if(this.value){
+ var s = this.value;
+ this.value = null;
+ this.select(s);
+ }
+ },
+
+ // 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)
+ */
+ select : function(color){
+ color = color.replace('#', '');
+ if(color != this.value || this.allowReselect){
+ var el = this.el;
+ if(this.value){
+ el.child('a.color-'+this.value).removeClass('x-color-palette-sel');
+ }
+ el.child('a.color-'+color).addClass('x-color-palette-sel');
+ this.value = color;
+ this.fireEvent('select', this, color);
+ }
+ }
+
+ /**
+ * @cfg {String} autoEl @hide
+ */
+});
+Ext.reg('colorpalette', Ext.ColorPalette);
+/**
+ * @class Ext.DatePicker
+ * @extends Ext.Component
+ * <p>A popup date picker. This class is used by the {@link Ext.form.DateField DateField} class
+ * to allow browsing and selection of valid dates.</p>
+ * <p>All the string values documented below may be overridden by including an Ext locale file in
+ * your page.</p>
+ * @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 <code>'Today'</code>)
+ */
+ todayText : 'Today',
+ /**
+ * @cfg {String} okText
+ * The text to display on the ok button (defaults to <code>' OK '</code> to give the user extra clicking room)
+ */
+ okText : ' OK ',
+ /**
+ * @cfg {String} cancelText
+ * The text to display on the cancel button (defaults to <code>'Cancel'</code>)
+ */
+ cancelText : 'Cancel',
+ /**
+ * @cfg {Function} handler
+ * Optional. A function that will handle the select event of this picker.
+ * The handler is passed the following parameters:<div class="mdetail-params"><ul>
+ * <li><code>picker</code> : DatePicker<div class="sub-desc">This DatePicker.</div></li>
+ * <li><code>date</code> : Date<div class="sub-desc">The selected date.</div></li>
+ * </ul></div>
+ */
+ /**
+ * @cfg {Object} scope
+ * The scope (<code><b>this</b></code> reference) in which the <code>{@link #handler}</code>
+ * 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 <code>'{0} (Spacebar)'</code> where
+ * the <code>{0}</code> 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 <code>'This date is before the minimum date'</code>)
+ */
+ minText : 'This date is before the minimum date',
+ /**
+ * @cfg {String} maxText
+ * The error text to display if the maxDate validation fails (defaults to <code>'This date is after the maximum date'</code>)
+ */
+ 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 <code>'m/d/y'</code>).
+ */
+ format : 'm/d/y',
+ /**
+ * @cfg {String} disabledDaysText
+ * The tooltip to display when the date falls on a disabled day (defaults to <code>'Disabled'</code>)
+ */
+ disabledDaysText : 'Disabled',
+ /**
+ * @cfg {String} disabledDatesText
+ * The tooltip text to display when the date falls on a disabled date (defaults to <code>'Disabled'</code>)
+ */
+ 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 <code>'Next Month (Control+Right)'</code>)
+ */
+ nextText : 'Next Month (Control+Right)',
+ /**
+ * @cfg {String} prevText
+ * The previous month navigation button tooltip (defaults to <code>'Previous Month (Control+Left)'</code>)
+ */
+ prevText : 'Previous Month (Control+Left)',
+ /**
+ * @cfg {String} monthYearText
+ * The header month selector tooltip (defaults to <code>'Choose a month (Control+Up/Down to move years)'</code>)
+ */
+ 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 <code>true</code>).
+ */
+ 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:
+ * <ul>
+ * <li>['03/08/2003', '09/16/2003'] would disable those exact dates</li>
+ * <li>['03/08', '09/16'] would disable those days for every year</li>
+ * <li>['^03/08'] would only match the beginning (useful if you are using short years)</li>
+ * <li>['03/../2006'] would disable every day in March 2006</li>
+ * <li>['^03'] would disable every day in every March</li>
+ * </ul>
+ * Note that the format of the dates included in the array should exactly match the {@link #format} config.
+ * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to
+ * escape the dot when restricting dates. For example: ['03\\.08\\.03'].
+ */
+
+ // private
+ // Set by other components to stop the picker focus being updated when the value changes.
+ focusOnSelect: true,
+
+ // default value used to initialise each date in the DatePicker
+ // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
+ initHour: 12, // 24-hour format
+
+ // private
+ initComponent : function(){
+ Ext.DatePicker.superclass.initComponent.call(this);
+
+ this.value = this.value ?
+ this.value.clearTime(true) : new Date().clearTime();
+
+ this.addEvents(
+ /**
+ * @event select
+ * Fires when a date is selected
+ * @param {DatePicker} this DatePicker
+ * @param {Date} date The selected date
+ */
+ 'select'
+ );
+
+ if(this.handler){
+ this.on('select', this.handler, this.scope || this);
+ }
+
+ this.initDisabledDays();
+ },
+
+ // private
+ initDisabledDays : function(){
+ if(!this.disabledDatesRE && this.disabledDates){
+ var dd = this.disabledDates,
+ len = dd.length - 1,
+ re = '(?:';
+
+ Ext.each(dd, function(d, i){
+ re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i];
+ if(i != len){
+ re += '|';
+ }
+ }, this);
+ this.disabledDatesRE = new RegExp(re + ')');
+ }
+ },
+
+ /**
+ * 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 = [
+ '<table cellspacing="0">',
+ '<tr><td class="x-date-left"><a href="#" title="', this.prevText ,'"> </a></td><td class="x-date-middle" align="center"></td><td class="x-date-right"><a href="#" title="', this.nextText ,'"> </a></td></tr>',
+ '<tr><td colspan="3"><table class="x-date-inner" cellspacing="0"><thead><tr>'],
+ dn = this.dayNames,
+ i;
+ for(i = 0; i < 7; i++){
+ var d = this.startDay+i;
+ if(d > 6){
+ d = d-7;
+ }
+ m.push('<th><span>', dn[d].substr(0,1), '</span></th>');
+ }
+ m[m.length] = '</tr></thead><tbody><tr>';
+ for(i = 0; i < 42; i++) {
+ if(i % 7 === 0 && i !== 0){
+ m[m.length] = '</tr><tr>';
+ }
+ m[m.length] = '<td><a href="#" hidefocus="on" class="x-date-date" tabIndex="1"><em><span></span></em></a></td>';
+ }
+ m.push('</tr></tbody></table></td></tr>',
+ this.showToday ? '<tr><td colspan="3" class="x-date-bottom" align="center"></td></tr>' : '',
+ '</table><div class="x-date-mp"></div>');
+
+ 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 = ['<table border="0" cellspacing="0">'];
+ for(var i = 0; i < 6; i++){
+ buf.push(
+ '<tr><td class="x-date-mp-month"><a href="#">', Date.getShortMonthName(i), '</a></td>',
+ '<td class="x-date-mp-month x-date-mp-sep"><a href="#">', Date.getShortMonthName(i + 6), '</a></td>',
+ i === 0 ?
+ '<td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-prev"></a></td><td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-next"></a></td></tr>' :
+ '<td class="x-date-mp-year"><a href="#"></a></td><td class="x-date-mp-year"><a href="#"></a></td></tr>'
+ );
+ }
+ buf.push(
+ '<tr class="x-date-mp-btns"><td colspan="4"><button type="button" class="x-date-mp-ok">',
+ this.okText,
+ '</button><button type="button" class="x-date-mp-cancel">',
+ this.cancelText,
+ '</button></td></tr>',
+ '</table>'
+ );
+ 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.cancelFocus = this.focusOnSelect === false;
+ this.setValue(new Date(t.dateValue));
+ delete this.cancelFocus;
+ 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 && !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
+ 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.
+ * <p>Example usage:</p>
+ *<pre><code>
+// Basic mask:
+var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
+myMask.show();
+</code></pre>
+ * @constructor
+ * Create a new LoadMask
+ * @param {Mixed} el The element or DOM node, or its id
+ * @param {Object} config The config object
+ */
+Ext.LoadMask = function(el, config){
+ this.el = Ext.get(el);
+ Ext.apply(this, config);
+ if(this.store){
+ this.store.on({
+ scope: this,
+ beforeload: this.onBeforeLoad,
+ load: this.onLoad,
+ exception: this.onLoad
+ });
+ this.removeMask = Ext.value(this.removeMask, false);
+ }else{
+ var um = this.el.getUpdater();
+ um.showLoadIndicator = false; // disable the default indicator
+ um.on({
+ scope: this,
+ beforeupdate: this.onBeforeLoad,
+ update: this.onLoad,
+ failure: this.onLoad
+ });
+ this.removeMask = Ext.value(this.removeMask, true);
+ }
+};
+
+Ext.LoadMask.prototype = {
+ /**
+ * @cfg {Ext.data.Store} store
+ * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
+ * hidden on either load sucess, or load fail.
+ */
+ /**
+ * @cfg {Boolean} removeMask
+ * True to create a single-use mask that is automatically destroyed after loading (useful for page loads),
+ * False to persist the mask element reference for multiple uses (e.g., for paged data widgets). Defaults to false.
+ */
+ /**
+ * @cfg {String} msg
+ * The text to display in a centered loading message box (defaults to 'Loading...')
+ */
+ msg : 'Loading...',
+ /**
+ * @cfg {String} msgCls
+ * The CSS class to apply to the loading message element (defaults to "x-mask-loading")
+ */
+ msgCls : 'x-mask-loading',
+
+ /**
+ * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false)
+ * @type Boolean
+ */
+ disabled: false,
+
+ /**
+ * Disables the mask to prevent it from being displayed
+ */
+ disable : function(){
+ this.disabled = true;
+ },
+
+ /**
+ * Enables the mask so that it can be displayed
+ */
+ enable : function(){
+ this.disabled = false;
+ },
+
+ // private
+ onLoad : function(){
+ this.el.unmask(this.removeMask);
+ },
+
+ // private
+ onBeforeLoad : function(){
+ if(!this.disabled){
+ this.el.mask(this.msg, this.msgCls);
+ }
+ },
+
+ /**
+ * Show this LoadMask over the configured Element.
+ */
+ show: function(){
+ this.onBeforeLoad();
+ },
+
+ /**
+ * Hide this LoadMask.
+ */
+ hide: function(){
+ this.onLoad();
+ },
+
+ // private
+ destroy : function(){
+ if(this.store){
+ this.store.un('beforeload', this.onBeforeLoad, this);
+ this.store.un('load', this.onLoad, this);
+ this.store.un('exception', this.onLoad, this);
+ }else{
+ var um = this.el.getUpdater();
+ um.un('beforeupdate', this.onBeforeLoad, this);
+ um.un('update', this.onLoad, this);
+ um.un('failure', this.onLoad, this);
+ }
+ }
+};Ext.ns('Ext.slider');
+
+/**
+ * @class Ext.slider.Thumb
+ * @extends Object
+ * Represents a single thumb element on a Slider. This would not usually be created manually and would instead
+ * be created internally by an {@link Ext.slider.MultiSlider Ext.Slider}.
+ */
+Ext.slider.Thumb = Ext.extend(Object, {
+
+ /**
+ * @constructor
+ * @cfg {Ext.slider.MultiSlider} slider The Slider to render to (required)
+ */
+ constructor: function(config) {
+ /**
+ * @property slider
+ * @type Ext.slider.MultiSlider
+ * The slider this thumb is contained within
+ */
+ Ext.apply(this, config || {}, {
+ cls: 'x-slider-thumb',
+
+ /**
+ * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings
+ */
+ constrain: false
+ });
+
+ Ext.slider.Thumb.superclass.constructor.call(this, config);
+
+ if (this.slider.vertical) {
+ Ext.apply(this, Ext.slider.Thumb.Vertical);
+ }
+ },
+
+ /**
+ * Renders the thumb into a slider
+ */
+ render: function() {
+ this.el = this.slider.innerEl.insertFirst({cls: this.cls});
+
+ this.initEvents();
+ },
+
+ /**
+ * Enables the thumb if it is currently disabled
+ */
+ enable: function() {
+ this.disabled = false;
+ this.el.removeClass(this.slider.disabledClass);
+ },
+
+ /**
+ * Disables the thumb if it is currently enabled
+ */
+ disable: function() {
+ this.disabled = true;
+ this.el.addClass(this.slider.disabledClass);
+ },
+
+ /**
+ * Sets up an Ext.dd.DragTracker for this thumb
+ */
+ initEvents: function() {
+ var el = this.el;
+
+ el.addClassOnOver('x-slider-thumb-over');
+
+ this.tracker = new Ext.dd.DragTracker({
+ onBeforeStart: this.onBeforeDragStart.createDelegate(this),
+ onStart : this.onDragStart.createDelegate(this),
+ onDrag : this.onDrag.createDelegate(this),
+ onEnd : this.onDragEnd.createDelegate(this),
+ tolerance : 3,
+ autoStart : 300
+ });
+
+ this.tracker.initEl(el);
+ },
+
+ /**
+ * @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
+ */
+ onBeforeDragStart : function(e) {
+ if (this.disabled) {
+ return false;
+ } else {
+ this.slider.promoteThumb(this);
+ return true;
+ }
+ },
+
+ /**
+ * @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
+ */
+ onDragStart: function(e){
+ this.el.addClass('x-slider-thumb-drag');
+ this.dragging = true;
+ this.dragStartValue = this.value;
+
+ this.slider.fireEvent('dragstart', this.slider, e, this);
+ },
+
+ /**
+ * @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;
+ }
+
+ slider.setValue(index, newValue, false);
+ slider.fireEvent('drag', slider, e, this);
+ },
+
+ getNewValue: function() {
+ var slider = this.slider,
+ pos = slider.innerEl.translatePoints(this.tracker.getXY());
+
+ return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision);
+ },
+
+ /**
+ * @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);
+ }
+ }
+});
+
+/**
+ * @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:
+<pre>
+new Ext.Slider({
+ renderTo: Ext.getBody(),
+ width: 200,
+ value: 50,
+ increment: 10,
+ minValue: 0,
+ maxValue: 100
+});
+</pre>
+ * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
+<pre>
+new Ext.Slider({
+ renderTo: Ext.getBody(),
+ width: 200,
+ values: [25, 50, 75],
+ minValue: 0,
+ maxValue: 100,
+
+ //this defaults to true, setting to false allows the thumbs to pass each other
+ {@link #constrainThumbs}: false
+});
+</pre>
+ */
+Ext.slider.MultiSlider = 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.
+ * <p>The number of decimal places to which to round the Slider's value. Defaults to 0.</p>
+ * <p>To disable rounding, configure as <tt><b>false</b></tt>.</p>
+ */
+ 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
+ * @property clickRange
+ * @type Array
+ * Determines whether or not a click to the slider component is considered to be a user request to change the value. Specified as an array of [top, bottom],
+ * the click event's 'top' property is compared to these numbers and the click only considered a change request if it falls within them. e.g. if the 'top'
+ * value of the click event is 4 or 16, the click is not considered a change request as it falls outside of the [5, 15] range
+ */
+ clickRange: [5,15],
+
+ /**
+ * @cfg {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,
+
+ /**
+ * @cfg {Boolean} constrainThumbs True to disallow thumbs from overlapping one another. Defaults to true
+ */
+ constrainThumbs: true,
+
+ /**
+ * @private
+ * @property topThumbZIndex
+ * @type Number
+ * The number used internally to set the z index of the top thumb (see promoteThumb for details)
+ */
+ topThumbZIndex: 10000,
+
+ // private override
+ initComponent : function(){
+ 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 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.
+ * @param {Ext.slider.Thumb} thumb The thumb that was changed
+ */
+ 'change',
+
+ /**
+ * @event changecomplete
+ * Fires when the slider value is changed by the user and any drag operations have completed.
+ * @param {Ext.Slider} slider The slider
+ * @param {Number} newValue The new value which the slider has been changed to.
+ * @param {Ext.slider.Thumb} thumb The thumb that was changed
+ */
+ 'changecomplete',
+
+ /**
+ * @event dragstart
+ * Fires after a drag operation has started.
+ * @param {Ext.Slider} 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'
+ );
+
+ /**
+ * @property values
+ * @type Array
+ * Array of values to initalize the thumbs with
+ */
+ if (this.values == undefined || Ext.isEmpty(this.values)) this.values = [0];
+
+ var values = this.values;
+
+ for (var i=0; i < values.length; i++) {
+ this.addThumb(values[i]);
+ }
+
+ if(this.vertical){
+ Ext.apply(this, Ext.slider.Vertical);
+ }
+ },
+
+ /**
+ * Creates a new thumb and adds it to the slider
+ * @param {Number} value The initial value to set on the thumb. Defaults to 0
+ */
+ addThumb: function(value) {
+ var thumb = new Ext.slider.Thumb({
+ value : value,
+ slider : this,
+ index : this.thumbs.length,
+ constrain: this.constrainThumbs
+ });
+ this.thumbs.push(thumb);
+
+ //render the thumb now if needed
+ if (this.rendered) thumb.render();
+ },
+
+ /**
+ * @private
+ * Moves the given thumb above all other by increasing its z-index. This is called when as drag
+ * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
+ * required when the thumbs are stacked on top of each other at one of the ends of the slider's
+ * range, which can result in the user not being able to move any of them.
+ * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
+ */
+ promoteThumb: function(topThumb) {
+ var thumbs = this.thumbs,
+ zIndex, thumb;
+
+ for (var i = 0, j = thumbs.length; i < j; i++) {
+ thumb = thumbs[i];
+
+ if (thumb == topThumb) {
+ zIndex = this.topThumbZIndex;
+ } else {
+ zIndex = '';
+ }
+
+ thumb.el.setStyle('zIndex', zIndex);
+ }
+ },
+
+ // private override
+ onRender : function() {
+ this.autoEl = {
+ cls: 'x-slider ' + (this.vertical ? 'x-slider-vert' : 'x-slider-horz'),
+ cn : {
+ cls: 'x-slider-end',
+ cn : {
+ cls:'x-slider-inner',
+ cn : [{tag:'a', cls:'x-slider-focus', href:"#", tabIndex: '-1', hidefocus:'on'}]
+ }
+ }
+ };
+
+ Ext.slider.MultiSlider.superclass.onRender.apply(this, arguments);
+
+ this.endEl = this.el.first();
+ this.innerEl = this.endEl.first();
+ this.focusEl = this.innerEl.child('.x-slider-focus');
+
+ //render each thumb
+ for (var i=0; i < this.thumbs.length; i++) {
+ this.thumbs[i].render();
+ }
+
+ //calculate the size of half a thumb
+ var thumb = this.innerEl.child('.x-slider-thumb');
+ this.halfThumb = (this.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
+
+ this.initEvents();
+ },
+
+ /**
+ * @private
+ * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
+ * Creates a new DragTracker which is used to control what happens when the user drags the thumb around.
+ */
+ initEvents : function(){
+ this.mon(this.el, {
+ scope : this,
+ mousedown: this.onMouseDown,
+ keydown : this.onKeyDown
+ });
+
+ this.focusEl.swallowEvent("click", true);
+ },
+
+ /**
+ * @private
+ * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
+ * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
+ * @param {Ext.EventObject} e The click event
+ */
+ onMouseDown : function(e){
+ if(this.disabled){
+ return;
+ }
+
+ //see if the click was on any of the thumbs
+ var thumbClicked = false;
+ for (var i=0; i < this.thumbs.length; i++) {
+ thumbClicked = thumbClicked || e.target == this.thumbs[i].el.dom;
+ }
+
+ if (this.clickToChange && !thumbClicked) {
+ var local = this.innerEl.translatePoints(e.getXY());
+ this.onClickChange(local);
+ }
+ this.focus();
+ },
+
+ /**
+ * @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);
+ }
+ },
+
+ /**
+ * @private
+ * Returns the nearest thumb to a click event, along with its distance
+ * @param {Object} local Object containing top and left values from a click event
+ * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
+ * @return {Object} The closest thumb object and its distance from the click event
+ */
+ getNearest: function(local, prop) {
+ var localValue = prop == 'top' ? this.innerEl.getHeight() - local[prop] : local[prop],
+ clickValue = this.reverseValue(localValue),
+ nearestDistance = (this.maxValue - this.minValue) + 5, //add a small fudge for the end of the slider
+ index = 0,
+ nearest = null;
+
+ for (var i=0; i < this.thumbs.length; i++) {
+ var thumb = this.thumbs[i],
+ value = thumb.value,
+ dist = Math.abs(value - clickValue);
+
+ if (Math.abs(dist <= nearestDistance)) {
+ nearest = thumb;
+ index = i;
+ nearestDistance = dist;
+ }
+ }
+ return nearest;
+ },
+
+ /**
+ * @private
+ * 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){
+ 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
+ * 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
+ 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);
+ }
+ }
+ };
+ },
+
+ /**
+ * @private
+ * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
+ * the ratio is 2
+ * @return {Number} The ratio of pixels to mapped values
+ */
+ getRatio : function(){
+ var w = this.innerEl.getWidth(),
+ v = this.maxValue - this.minValue;
+ return v == 0 ? w : (w/v);
+ },
+
+ /**
+ * @private
+ * Returns a snapped, constrained value when given a desired value
+ * @param {Number} value Raw number value
+ * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
+ */
+ normalizeValue : function(v){
+ v = this.doSnap(v);
+ v = Ext.util.Format.round(v, this.decimalPrecision);
+ v = v.constrain(this.minValue, this.maxValue);
+ return v;
+ },
+
+ /**
+ * Sets the minimum value for the slider instance. If the current value is less than the
+ * minimum value, the current value will be changed.
+ * @param {Number} val The new minimum value
+ */
+ setMinValue : function(val){
+ this.minValue = val;
+ this.syncThumb();
+
+ for (var i=0, j = this.thumbs.length; i < j; i++) {
+ if (this.thumbs[i].value < val) this.thumbs[i].value = val;
+ }
+ },
+
+ /**
+ * Sets the maximum value for the slider instance. If the current value is more than the
+ * maximum value, the current value will be changed.
+ * @param {Number} val The new maximum value
+ */
+ setMaxValue : function(val){
+ this.maxValue = val;
+ this.syncThumb();
+
+ for (var i=0; i < this.thumbs.length; i++) {
+ if (this.thumbs[i].value > val) this.thumbs[i].value = val;
+ }
+ },
+
+ /**
+ * Programmatically sets the value of the Slider. Ensures that the value is constrained within
+ * the minValue and maxValue.
+ * @param {Number} index Index of the thumb to move
+ * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
+ * @param {Boolean} animate Turn on or off animation, defaults to true
+ */
+ setValue : function(index, v, animate, changeComplete) {
+ var thumb = this.thumbs[index],
+ el = thumb.el;
+
+ v = this.normalizeValue(v);
+
+ if (v !== thumb.value && this.fireEvent('beforechange', this, v, thumb.value) !== false) {
+ thumb.value = v;
+ this.moveThumb(index, this.translateValue(v), animate !== false);
+ this.fireEvent('change', this, v, thumb);
+ if(changeComplete){
+ this.fireEvent('changecomplete', this, v, thumb);
+ }
+ }
+ },
+
+ /**
+ * @private
+ */
+ translateValue : function(v) {
+ var ratio = this.getRatio();
+ return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
+ },
+
+ /**
+ * @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;
+ },
+
+ /**
+ * @private
+ * @param {Number} index Index of the thumb to move
+ */
+ moveThumb: function(index, v, animate){
+ var thumb = this.thumbs[index].el;
+
+ if(!animate || this.animate === false){
+ thumb.setLeft(v);
+ }else{
+ thumb.shift({left: v, stopFx: true, duration:.35});
+ }
+ },
+
+ // private
+ 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();
+ }
+ this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r')));
+ this.syncThumb();
+ Ext.slider.MultiSlider.superclass.onResize.apply(this, arguments);
+ },
+
+ //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();
+ }
+ }
+ },
+
+ /**
+ * Synchronizes the thumb position to the proper proportion of the total component width based
+ * on the current slider {@link #value}. This will be called automatically when the Slider
+ * is resized by a layout, but if it is rendered auto width, this method can be called from
+ * another resize handler to sync the Slider if necessary.
+ */
+ syncThumb : function() {
+ if (this.rendered) {
+ for (var i=0; i < this.thumbs.length; i++) {
+ this.moveThumb(i, this.translateValue(this.thumbs[i].value));
+ }
+ }
+ },
+
+ /**
+ * 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(){
+ Ext.destroyMembers(this, 'endEl', 'innerEl', 'thumb', 'halfThumb', 'focusEl', 'tracker', 'thumbHolder');
+ Ext.slider.MultiSlider.superclass.beforeDestroy.call(this);
+ }
+});
+
+Ext.reg('multislider', Ext.slider.MultiSlider);
+
+/**
+ * @class Ext.slider.SingleSlider
+ * @extends Ext.slider.MultiSlider
+ * Slider which supports vertical or horizontal orientation, keyboard adjustments,
+ * configurable snapping, axis clicking and animation. Can be added as an item to
+ * any container. Example usage:
+<pre><code>
+new Ext.slider.SingleSlider({
+ renderTo: Ext.getBody(),
+ width: 200,
+ value: 50,
+ increment: 10,
+ minValue: 0,
+ maxValue: 100
+});
+</code></pre>
+ * The class Ext.slider.SingleSlider is aliased to Ext.Slider for backwards compatibility.
+ */
+Ext.slider.SingleSlider = Ext.extend(Ext.slider.MultiSlider, {
+ constructor: function(config) {
+ config = config || {};
+
+ Ext.applyIf(config, {
+ values: [config.value || 0]
+ });
+
+ Ext.slider.SingleSlider.superclass.constructor.call(this, config);
+ },
+
+ /**
+ * Returns the current value of the slider
+ * @return {Number} The current value of the slider
+ */
+ getValue: function() {
+ //just returns the value of the first thumb, which should be the only one in a single slider
+ return Ext.slider.SingleSlider.superclass.getValue.call(this, 0);
+ },
+
+ /**
+ * Programmatically sets the value of the Slider. Ensures that the value is constrained within
+ * the minValue and maxValue.
+ * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
+ * @param {Boolean} animate Turn on or off animation, defaults to true
+ */
+ setValue: function(value, animate) {
+ var args = Ext.toArray(arguments),
+ len = args.length;
+
+ //this is to maintain backwards compatiblity for sliders with only one thunb. Usually you must pass the thumb
+ //index to setValue, but if we only have one thumb we inject the index here first if given the multi-slider
+ //signature without the required index. The index will always be 0 for a single slider
+ if (len == 1 || (len <= 3 && typeof arguments[1] != 'number')) {
+ args.unshift(0);
+ }
+
+ return Ext.slider.SingleSlider.superclass.setValue.apply(this, args);
+ },
+
+ /**
+ * Synchronizes the thumb position to the proper proportion of the total component width based
+ * on the current slider {@link #value}. This will be called automatically when the Slider
+ * is resized by a layout, but if it is rendered auto width, this method can be called from
+ * another resize handler to sync the Slider if necessary.
+ */
+ syncThumb : function() {
+ return Ext.slider.SingleSlider.superclass.syncThumb.apply(this, [0].concat(arguments));
+ },
+
+ // private
+ 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
+ * <p>An updateable progress bar component. The progress bar supports two different modes: manual and automatic.</p>
+ * <p>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.</p>
+ * <p>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.</p>
+ * @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(
+ '<div class="{cls}-wrap">',
+ '<div class="{cls}-inner">',
+ '<div class="{cls}-bar">',
+ '<div class="{cls}-text">',
+ '<div> </div>',
+ '</div>',
+ '</div>',
+ '<div class="{cls}-text {cls}-text-back">',
+ '<div> </div>',
+ '</div>',
+ '</div>',
+ '</div>'
+ );
+
+ this.el = position ? tpl.insertBefore(position, {cls: this.baseCls}, true)
+ : tpl.append(ct, {cls: this.baseCls}, true);
+
+ if(this.id){
+ this.el.dom.id = this.id;
+ }
+ var inner = this.el.dom.firstChild;
+ this.progressBar = Ext.get(inner.firstChild);
+
+ if(this.textEl){
+ //use an external text el
+ this.textEl = Ext.get(this.textEl);
+ delete this.textTopEl;
+ }else{
+ //setup our internal layered text els
+ this.textTopEl = Ext.get(this.progressBar.dom.firstChild);
+ var textBackEl = Ext.get(inner.childNodes[1]);
+ this.textTopEl.setStyle("z-index", 99).addClass('x-hidden');
+ this.textEl = new Ext.CompositeElement([this.textTopEl.dom.firstChild, textBackEl.dom.firstChild]);
+ this.textEl.setWidth(inner.offsetWidth);
+ }
+ this.progressBar.setHeight(inner.offsetHeight);
+ },
+
+ // private
+ afterRender : function(){
+ Ext.ProgressBar.superclass.afterRender.call(this);
+ if(this.value){
+ this.updateProgress(this.value, this.text);
+ }else{
+ this.updateText(this.text);
+ }
+ },
+
+ /**
+ * Updates the progress bar value, and optionally its text. If the text argument is not specified,
+ * any existing text value will be unchanged. To blank out existing text, pass ''. Note that even
+ * if the progress bar value exceeds 1, it will never automatically reset -- you are responsible for
+ * determining when the progress is complete and calling {@link #reset} to clear and/or hide the control.
+ * @param {Float} value (optional) A floating point value between 0 and 1 (e.g., .5, defaults to 0)
+ * @param {String} text (optional) The string to display in the progress text element (defaults to '')
+ * @param {Boolean} animate (optional) Whether to animate the transition of the progress bar. If this value is
+ * not specified, the default for the class is used (default to false)
+ * @return {Ext.ProgressBar} this
+ */
+ updateProgress : function(value, text, animate){
+ this.value = value || 0;
+ if(text){
+ this.updateText(text);
+ }
+ if(this.rendered && !this.isDestroyed){
+ var w = Math.floor(value*this.el.dom.firstChild.offsetWidth);
+ this.progressBar.setWidth(w, animate === true || (animate !== false && this.animate));
+ if(this.textTopEl){
+ //textTopEl should be the same width as the bar so overflow will clip as the bar moves
+ this.textTopEl.removeClass('x-hidden').setWidth(w);
+ }
+ }
+ this.fireEvent('update', this, value, text);
+ return this;
+ },
+
+ /**
+ * Initiates an auto-updating progress bar. A duration can be specified, in which case the progress
+ * bar will automatically reset after a fixed amount of time and optionally call a callback function
+ * if specified. If no duration is passed in, then the progress bar will run indefinitely and must
+ * be manually cleared by calling {@link #reset}. The wait method accepts a config object with
+ * the following properties:
+ * <pre>
+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).
+</pre>
+ *
+ * Example usage:
+ * <pre><code>
+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!');
+});
+</code></pre>
+ * @param {Object} config (optional) Configuration options
+ * @return {Ext.ProgressBar} this
+ */
+ wait : function(o){
+ if(!this.waitTimer){
+ var scope = this;
+ o = o || {};
+ this.updateText(o.text);
+ this.waitTimer = Ext.TaskMgr.start({
+ run: function(i){
+ var inc = o.increment || 10;
+ i -= 1;
+ this.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
+ },
+ interval: o.interval || 1000,
+ duration: o.duration,
+ onStop: function(){
+ if(o.fn){
+ o.fn.apply(o.scope || this);
+ }
+ this.reset();
+ },
+ scope: scope
+ });
+ }
+ return this;
+ },
+
+ /**
+ * Returns true if the progress bar is currently in a {@link #wait} operation
+ * @return {Boolean} True if waiting, else false
+ */
+ isWaiting : function(){
+ return this.waitTimer !== null;
+ },
+
+ /**
+ * Updates the progress bar text. If specified, textEl will be updated, otherwise the progress
+ * bar itself will display the updated text.
+ * @param {String} text (optional) The string to display in the progress text element (defaults to '')
+ * @return {Ext.ProgressBar} this
+ */
+ updateText : function(text){
+ this.text = text || ' ';
+ if(this.rendered){
+ this.textEl.update(this.text);
+ }
+ return this;
+ },
+
+ /**
+ * Synchronizes the inner bar width to the proper proportion of the total componet width based
+ * on the current progress {@link #value}. This will be called automatically when the ProgressBar
+ * is resized by a layout, but if it is rendered auto width, this method can be called from
+ * another resize handler to sync the ProgressBar if necessary.
+ */
+ syncProgressBar : function(){
+ if(this.value){
+ this.updateProgress(this.value, this.text);
+ }
+ return this;
+ },
+
+ /**
+ * Sets the size of the progress bar.
+ * @param {Number} width The new width in pixels
+ * @param {Number} height The new height in pixels
+ * @return {Ext.ProgressBar} this
+ */
+ setSize : function(w, h){
+ Ext.ProgressBar.superclass.setSize.call(this, w, h);
+ if(this.textTopEl){
+ var inner = this.el.dom.firstChild;
+ this.textEl.setSize(inner.offsetWidth, inner.offsetHeight);
+ }
+ this.syncProgressBar();
+ return this;
+ },
+
+ /**
+ * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress
+ * bar will also be hidden (using the {@link #hideMode} property internally).
+ * @param {Boolean} hide (optional) True to hide the progress bar (defaults to false)
+ * @return {Ext.ProgressBar} this
+ */
+ reset : function(hide){
+ this.updateProgress(0);
+ if(this.textTopEl){
+ this.textTopEl.addClass('x-hidden');
+ }
+ this.clearTimer();
+ if(hide === true){
+ this.hide();
+ }
+ return this;
+ },
+
+ // private
+ clearTimer : function(){
+ if(this.waitTimer){
+ this.waitTimer.onStop = null; //prevent recursion
+ Ext.TaskMgr.stop(this.waitTimer);
+ this.waitTimer = null;
+ }
+ },
+
+ onDestroy: function(){
+ this.clearTimer();
+ if(this.rendered){
+ if(this.textEl.isComposite){
+ this.textEl.clear();
+ }
+ Ext.destroyMembers(this, 'textEl', 'progressBar', 'textTopEl');
+ }
+ Ext.ProgressBar.superclass.onDestroy.call(this);
+ }
+});
+Ext.reg('progress', Ext.ProgressBar);/*
+ * These classes are derivatives of the similarly named classes in the YUI Library.
+ * The original license:
+ * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+ * Code licensed under the BSD License:
+ * http://developer.yahoo.net/yui/license.txt
+ */
+
+(function() {
+
+var Event=Ext.EventManager;
+var Dom=Ext.lib.Dom;
+
+/**
+ * @class Ext.dd.DragDrop
+ * Defines the interface and base operation of items that that can be
+ * dragged or can be drop targets. It was designed to be extended, overriding
+ * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
+ * Up to three html elements can be associated with a DragDrop instance:
+ * <ul>
+ * <li>linked element: the element that is passed into the constructor.
+ * This is the element which defines the boundaries for interaction with
+ * other DragDrop objects.</li>
+ * <li>handle element(s): The drag operation only occurs if the element that
+ * was clicked matches a handle element. By default this is the linked
+ * element, but there are times that you will want only a portion of the
+ * linked element to initiate the drag operation, and the setHandleElId()
+ * method provides a way to define this.</li>
+ * <li>drag element: this represents the element that would be moved along
+ * with the cursor during a drag operation. By default, this is the linked
+ * element itself as in {@link Ext.dd.DD}. setDragElId() lets you define
+ * a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
+ * </li>
+ * </ul>
+ * This class should not be instantiated until the onload event to ensure that
+ * the associated elements are available.
+ * The following would define a DragDrop obj that would interact with any
+ * other DragDrop obj in the "group1" group:
+ * <pre>
+ * dd = new Ext.dd.DragDrop("div1", "group1");
+ * </pre>
+ * Since none of the event handlers have been implemented, nothing would
+ * actually happen if you were to run the code above. Normally you would
+ * override this class or one of the default implementations, but you can
+ * also override the methods you want on an instance of the class...
+ * <pre>
+ * dd.onDragDrop = function(e, id) {
+ * alert("dd was dropped on " + id);
+ * }
+ * </pre>
+ * @constructor
+ * @param {String} id of the element that is linked to this instance
+ * @param {String} sGroup the group of related DragDrop objects
+ * @param {object} config an object containing configurable attributes
+ * Valid properties for DragDrop:
+ * padding, isTarget, maintainOffset, primaryButtonOnly
+ */
+Ext.dd.DragDrop = function(id, sGroup, config) {
+ if(id) {
+ this.init(id, sGroup, config);
+ }
+};
+
+Ext.dd.DragDrop.prototype = {
+
+ /**
+ * Set to false to enable a DragDrop object to fire drag events while dragging
+ * over its own Element. Defaults to true - DragDrop objects do not by default
+ * fire drag events to themselves.
+ * @property ignoreSelf
+ * @type Boolean
+ */
+
+ /**
+ * The id of the element associated with this object. This is what we
+ * refer to as the "linked element" because the size and position of
+ * this element is used to determine when the drag and drop objects have
+ * interacted.
+ * @property id
+ * @type String
+ */
+ id: null,
+
+ /**
+ * Configuration attributes passed into the constructor
+ * @property config
+ * @type object
+ */
+ config: null,
+
+ /**
+ * The id of the element that will be dragged. By default this is same
+ * as the linked element, but could be changed to another element. Ex:
+ * Ext.dd.DDProxy
+ * @property dragElId
+ * @type String
+ * @private
+ */
+ dragElId: null,
+
+ /**
+ * The ID of the element that initiates the drag operation. By default
+ * this is the linked element, but could be changed to be a child of this
+ * element. This lets us do things like only starting the drag when the
+ * header element within the linked html element is clicked.
+ * @property handleElId
+ * @type String
+ * @private
+ */
+ handleElId: null,
+
+ /**
+ * An object who's property names identify HTML tags to be considered invalid as drag handles.
+ * A non-null property value identifies the tag as invalid. Defaults to the
+ * following value which prevents drag operations from being initiated by <a> elements:<pre><code>
+{
+ A: "A"
+}</code></pre>
+ * @property invalidHandleTypes
+ * @type Object
+ */
+ invalidHandleTypes: null,
+
+ /**
+ * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
+ * A non-null property value identifies the ID as invalid. For example, to prevent
+ * dragging from being initiated on element ID "foo", use:<pre><code>
+{
+ foo: true
+}</code></pre>
+ * @property invalidHandleIds
+ * @type Object
+ */
+ invalidHandleIds: null,
+
+ /**
+ * An Array of CSS class names for elements to be considered in valid as drag handles.
+ * @property invalidHandleClasses
+ * @type Array
+ */
+ invalidHandleClasses: null,
+
+ /**
+ * The linked element's absolute X position at the time the drag was
+ * started
+ * @property startPageX
+ * @type int
+ * @private
+ */
+ startPageX: 0,
+
+ /**
+ * The linked element's absolute X position at the time the drag was
+ * started
+ * @property startPageY
+ * @type int
+ * @private
+ */
+ startPageY: 0,
+
+ /**
+ * The group defines a logical collection of DragDrop objects that are
+ * related. Instances only get events when interacting with other
+ * DragDrop object in the same group. This lets us define multiple
+ * groups using a single DragDrop subclass if we want.
+ * @property groups
+ * @type object An object in the format {'group1':true, 'group2':true}
+ */
+ groups: null,
+
+ /**
+ * Individual drag/drop instances can be locked. This will prevent
+ * onmousedown start drag.
+ * @property locked
+ * @type boolean
+ * @private
+ */
+ locked: false,
+
+ /**
+ * Lock this instance
+ * @method lock
+ */
+ lock: function() {
+ this.locked = true;
+ },
+
+ /**
+ * When set to true, other DD objects in cooperating DDGroups do not receive
+ * notification events when this DD object is dragged over them. Defaults to false.
+ * @property moveOnly
+ * @type boolean
+ */
+ moveOnly: false,
+
+ /**
+ * Unlock this instace
+ * @method unlock
+ */
+ unlock: function() {
+ this.locked = false;
+ },
+
+ /**
+ * By default, all instances can be a drop target. This can be disabled by
+ * setting isTarget to false.
+ * @property isTarget
+ * @type boolean
+ */
+ isTarget: true,
+
+ /**
+ * The padding configured for this drag and drop object for calculating
+ * the drop zone intersection with this object.
+ * @property padding
+ * @type int[] An array containing the 4 padding values: [top, right, bottom, left]
+ */
+ padding: null,
+
+ /**
+ * Cached reference to the linked element
+ * @property _domRef
+ * @private
+ */
+ _domRef: null,
+
+ /**
+ * Internal typeof flag
+ * @property __ygDragDrop
+ * @private
+ */
+ __ygDragDrop: true,
+
+ /**
+ * Set to true when horizontal contraints are applied
+ * @property constrainX
+ * @type boolean
+ * @private
+ */
+ constrainX: false,
+
+ /**
+ * Set to true when vertical contraints are applied
+ * @property constrainY
+ * @type boolean
+ * @private
+ */
+ constrainY: false,
+
+ /**
+ * The left constraint
+ * @property minX
+ * @type int
+ * @private
+ */
+ minX: 0,
+
+ /**
+ * The right constraint
+ * @property maxX
+ * @type int
+ * @private
+ */
+ maxX: 0,
+
+ /**
+ * The up constraint
+ * @property minY
+ * @type int
+ * @private
+ */
+ minY: 0,
+
+ /**
+ * The down constraint
+ * @property maxY
+ * @type int
+ * @private
+ */
+ maxY: 0,
+
+ /**
+ * Maintain offsets when we resetconstraints. Set to true when you want
+ * the position of the element relative to its parent to stay the same
+ * when the page changes
+ *
+ * @property maintainOffset
+ * @type boolean
+ */
+ maintainOffset: false,
+
+ /**
+ * Array of pixel locations the element will snap to if we specified a
+ * horizontal graduation/interval. This array is generated automatically
+ * when you define a tick interval.
+ * @property xTicks
+ * @type int[]
+ */
+ xTicks: null,
+
+ /**
+ * Array of pixel locations the element will snap to if we specified a
+ * vertical graduation/interval. This array is generated automatically
+ * when you define a tick interval.
+ * @property yTicks
+ * @type int[]
+ */
+ yTicks: null,
+
+ /**
+ * By default the drag and drop instance will only respond to the primary
+ * button click (left button for a right-handed mouse). Set to true to
+ * allow drag and drop to start with any mouse click that is propogated
+ * by the browser
+ * @property primaryButtonOnly
+ * @type boolean
+ */
+ primaryButtonOnly: true,
+
+ /**
+ * The available property is false until the linked dom element is accessible.
+ * @property available
+ * @type boolean
+ */
+ available: false,
+
+ /**
+ * By default, drags can only be initiated if the mousedown occurs in the
+ * region the linked element is. This is done in part to work around a
+ * bug in some browsers that mis-report the mousedown if the previous
+ * mouseup happened outside of the window. This property is set to true
+ * if outer handles are defined.
+ *
+ * @property hasOuterHandles
+ * @type boolean
+ * @default false
+ */
+ hasOuterHandles: false,
+
+ /**
+ * Code that executes immediately before the startDrag event
+ * @method b4StartDrag
+ * @private
+ */
+ b4StartDrag: function(x, y) { },
+
+ /**
+ * Abstract method called after a drag/drop object is clicked
+ * and the drag or mousedown time thresholds have beeen met.
+ * @method startDrag
+ * @param {int} X click location
+ * @param {int} Y click location
+ */
+ startDrag: function(x, y) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the onDrag event
+ * @method b4Drag
+ * @private
+ */
+ b4Drag: function(e) { },
+
+ /**
+ * Abstract method called during the onMouseMove event while dragging an
+ * object.
+ * @method onDrag
+ * @param {Event} e the mousemove event
+ */
+ onDrag: function(e) { /* override this */ },
+
+ /**
+ * Abstract method called when this element fist begins hovering over
+ * another DragDrop obj
+ * @method onDragEnter
+ * @param {Event} e the mousemove event
+ * @param {String|DragDrop[]} id In POINT mode, the element
+ * id this is hovering over. In INTERSECT mode, an array of one or more
+ * dragdrop items being hovered over.
+ */
+ onDragEnter: function(e, id) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the onDragOver event
+ * @method b4DragOver
+ * @private
+ */
+ b4DragOver: function(e) { },
+
+ /**
+ * Abstract method called when this element is hovering over another
+ * DragDrop obj
+ * @method onDragOver
+ * @param {Event} e the mousemove event
+ * @param {String|DragDrop[]} id In POINT mode, the element
+ * id this is hovering over. In INTERSECT mode, an array of dd items
+ * being hovered over.
+ */
+ onDragOver: function(e, id) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the onDragOut event
+ * @method b4DragOut
+ * @private
+ */
+ b4DragOut: function(e) { },
+
+ /**
+ * Abstract method called when we are no longer hovering over an element
+ * @method onDragOut
+ * @param {Event} e the mousemove event
+ * @param {String|DragDrop[]} id In POINT mode, the element
+ * id this was hovering over. In INTERSECT mode, an array of dd items
+ * that the mouse is no longer over.
+ */
+ onDragOut: function(e, id) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the onDragDrop event
+ * @method b4DragDrop
+ * @private
+ */
+ b4DragDrop: function(e) { },
+
+ /**
+ * Abstract method called when this item is dropped on another DragDrop
+ * obj
+ * @method onDragDrop
+ * @param {Event} e the mouseup event
+ * @param {String|DragDrop[]} id In POINT mode, the element
+ * id this was dropped on. In INTERSECT mode, an array of dd items this
+ * was dropped on.
+ */
+ onDragDrop: function(e, id) { /* override this */ },
+
+ /**
+ * Abstract method called when this item is dropped on an area with no
+ * drop target
+ * @method onInvalidDrop
+ * @param {Event} e the mouseup event
+ */
+ onInvalidDrop: function(e) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the endDrag event
+ * @method b4EndDrag
+ * @private
+ */
+ b4EndDrag: function(e) { },
+
+ /**
+ * Fired when we are done dragging the object
+ * @method endDrag
+ * @param {Event} e the mouseup event
+ */
+ endDrag: function(e) { /* override this */ },
+
+ /**
+ * Code executed immediately before the onMouseDown event
+ * @method b4MouseDown
+ * @param {Event} e the mousedown event
+ * @private
+ */
+ b4MouseDown: function(e) { },
+
+ /**
+ * Event handler that fires when a drag/drop obj gets a mousedown
+ * @method onMouseDown
+ * @param {Event} e the mousedown event
+ */
+ onMouseDown: function(e) { /* override this */ },
+
+ /**
+ * Event handler that fires when a drag/drop obj gets a mouseup
+ * @method onMouseUp
+ * @param {Event} e the mouseup event
+ */
+ onMouseUp: function(e) { /* override this */ },
+
+ /**
+ * Override the onAvailable method to do what is needed after the initial
+ * position was determined.
+ * @method onAvailable
+ */
+ onAvailable: function () {
+ },
+
+ /**
+ * Provides default constraint padding to "constrainTo" elements (defaults to {left: 0, right:0, top:0, bottom:0}).
+ * @type Object
+ */
+ defaultPadding : {left:0, right:0, top:0, bottom:0},
+
+ /**
+ * Initializes the drag drop object's constraints to restrict movement to a certain element.
+ *
+ * Usage:
+ <pre><code>
+ var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
+ { dragElId: "existingProxyDiv" });
+ dd.startDrag = function(){
+ this.constrainTo("parent-id");
+ };
+ </code></pre>
+ * Or you can initalize it using the {@link Ext.Element} object:
+ <pre><code>
+ Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
+ startDrag : function(){
+ this.constrainTo("parent-id");
+ }
+ });
+ </code></pre>
+ * @param {Mixed} constrainTo The element to constrain to.
+ * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
+ * and can be either a number for symmetrical padding (4 would be equal to {left:4, right:4, top:4, bottom:4}) or
+ * an object containing the sides to pad. For example: {right:10, bottom:10}
+ * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
+ */
+ constrainTo : function(constrainTo, pad, inContent){
+ if(Ext.isNumber(pad)){
+ pad = {left: pad, right:pad, top:pad, bottom:pad};
+ }
+ pad = pad || this.defaultPadding;
+ var b = Ext.get(this.getEl()).getBox(),
+ ce = Ext.get(constrainTo),
+ s = ce.getScroll(),
+ c,
+ cd = ce.dom;
+ if(cd == document.body){
+ c = { x: s.left, y: s.top, width: Ext.lib.Dom.getViewWidth(), height: Ext.lib.Dom.getViewHeight()};
+ }else{
+ var xy = ce.getXY();
+ c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
+ }
+
+
+ var topSpace = b.y - c.y,
+ leftSpace = b.x - c.x;
+
+ this.resetConstraints();
+ this.setXConstraint(leftSpace - (pad.left||0), // left
+ c.width - leftSpace - b.width - (pad.right||0), //right
+ this.xTickSize
+ );
+ this.setYConstraint(topSpace - (pad.top||0), //top
+ c.height - topSpace - b.height - (pad.bottom||0), //bottom
+ this.yTickSize
+ );
+ },
+
+ /**
+ * Returns a reference to the linked element
+ * @method getEl
+ * @return {HTMLElement} the html element
+ */
+ getEl: function() {
+ if (!this._domRef) {
+ this._domRef = Ext.getDom(this.id);
+ }
+
+ return this._domRef;
+ },
+
+ /**
+ * Returns a reference to the actual element to drag. By default this is
+ * the same as the html element, but it can be assigned to another
+ * element. An example of this can be found in Ext.dd.DDProxy
+ * @method getDragEl
+ * @return {HTMLElement} the html element
+ */
+ getDragEl: function() {
+ return Ext.getDom(this.dragElId);
+ },
+
+ /**
+ * Sets up the DragDrop object. Must be called in the constructor of any
+ * Ext.dd.DragDrop subclass
+ * @method init
+ * @param id the id of the linked element
+ * @param {String} sGroup the group of related items
+ * @param {object} config configuration attributes
+ */
+ init: function(id, sGroup, config) {
+ this.initTarget(id, sGroup, config);
+ Event.on(this.id, "mousedown", this.handleMouseDown, this);
+ // Event.on(this.id, "selectstart", Event.preventDefault);
+ },
+
+ /**
+ * Initializes Targeting functionality only... the object does not
+ * get a mousedown handler.
+ * @method initTarget
+ * @param id the id of the linked element
+ * @param {String} sGroup the group of related items
+ * @param {object} config configuration attributes
+ */
+ initTarget: function(id, sGroup, config) {
+
+ // configuration attributes
+ this.config = config || {};
+
+ // create a local reference to the drag and drop manager
+ this.DDM = Ext.dd.DDM;
+ // initialize the groups array
+ this.groups = {};
+
+ // assume that we have an element reference instead of an id if the
+ // parameter is not a string
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+
+ // set the id
+ this.id = id;
+
+ // add to an interaction group
+ this.addToGroup((sGroup) ? sGroup : "default");
+
+ // We don't want to register this as the handle with the manager
+ // so we just set the id rather than calling the setter.
+ this.handleElId = id;
+
+ // the linked element is the element that gets dragged by default
+ this.setDragElId(id);
+
+ // by default, clicked anchors will not start drag operations.
+ this.invalidHandleTypes = { A: "A" };
+ this.invalidHandleIds = {};
+ this.invalidHandleClasses = [];
+
+ this.applyConfig();
+
+ this.handleOnAvailable();
+ },
+
+ /**
+ * Applies the configuration parameters that were passed into the constructor.
+ * This is supposed to happen at each level through the inheritance chain. So
+ * a DDProxy implentation will execute apply config on DDProxy, DD, and
+ * DragDrop in order to get all of the parameters that are available in
+ * each object.
+ * @method applyConfig
+ */
+ applyConfig: function() {
+
+ // configurable properties:
+ // padding, isTarget, maintainOffset, primaryButtonOnly
+ this.padding = this.config.padding || [0, 0, 0, 0];
+ this.isTarget = (this.config.isTarget !== false);
+ this.maintainOffset = (this.config.maintainOffset);
+ this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
+
+ },
+
+ /**
+ * Executed when the linked element is available
+ * @method handleOnAvailable
+ * @private
+ */
+ handleOnAvailable: function() {
+ this.available = true;
+ this.resetConstraints();
+ this.onAvailable();
+ },
+
+ /**
+ * Configures the padding for the target zone in px. Effectively expands
+ * (or reduces) the virtual object size for targeting calculations.
+ * Supports css-style shorthand; if only one parameter is passed, all sides
+ * will have that padding, and if only two are passed, the top and bottom
+ * will have the first param, the left and right the second.
+ * @method setPadding
+ * @param {int} iTop Top pad
+ * @param {int} iRight Right pad
+ * @param {int} iBot Bot pad
+ * @param {int} iLeft Left pad
+ */
+ setPadding: function(iTop, iRight, iBot, iLeft) {
+ // this.padding = [iLeft, iRight, iTop, iBot];
+ if (!iRight && 0 !== iRight) {
+ this.padding = [iTop, iTop, iTop, iTop];
+ } else if (!iBot && 0 !== iBot) {
+ this.padding = [iTop, iRight, iTop, iRight];
+ } else {
+ this.padding = [iTop, iRight, iBot, iLeft];
+ }
+ },
+
+ /**
+ * Stores the initial placement of the linked element.
+ * @method setInitPosition
+ * @param {int} diffX the X offset, default 0
+ * @param {int} diffY the Y offset, default 0
+ */
+ setInitPosition: function(diffX, diffY) {
+ var el = this.getEl();
+
+ if (!this.DDM.verifyEl(el)) {
+ return;
+ }
+
+ var dx = diffX || 0;
+ var dy = diffY || 0;
+
+ var p = Dom.getXY( el );
+
+ this.initPageX = p[0] - dx;
+ this.initPageY = p[1] - dy;
+
+ this.lastPageX = p[0];
+ this.lastPageY = p[1];
+
+ this.setStartPosition(p);
+ },
+
+ /**
+ * Sets the start position of the element. This is set when the obj
+ * is initialized, the reset when a drag is started.
+ * @method setStartPosition
+ * @param pos current position (from previous lookup)
+ * @private
+ */
+ setStartPosition: function(pos) {
+ var p = pos || Dom.getXY( this.getEl() );
+ this.deltaSetXY = null;
+
+ this.startPageX = p[0];
+ this.startPageY = p[1];
+ },
+
+ /**
+ * Add this instance to a group of related drag/drop objects. All
+ * instances belong to at least one group, and can belong to as many
+ * groups as needed.
+ * @method addToGroup
+ * @param sGroup {string} the name of the group
+ */
+ addToGroup: function(sGroup) {
+ this.groups[sGroup] = true;
+ this.DDM.regDragDrop(this, sGroup);
+ },
+
+ /**
+ * Remove's this instance from the supplied interaction group
+ * @method removeFromGroup
+ * @param {string} sGroup The group to drop
+ */
+ removeFromGroup: function(sGroup) {
+ if (this.groups[sGroup]) {
+ delete this.groups[sGroup];
+ }
+
+ this.DDM.removeDDFromGroup(this, sGroup);
+ },
+
+ /**
+ * Allows you to specify that an element other than the linked element
+ * will be moved with the cursor during a drag
+ * @method setDragElId
+ * @param id {string} the id of the element that will be used to initiate the drag
+ */
+ setDragElId: function(id) {
+ this.dragElId = id;
+ },
+
+ /**
+ * Allows you to specify a child of the linked element that should be
+ * used to initiate the drag operation. An example of this would be if
+ * you have a content div with text and links. Clicking anywhere in the
+ * content area would normally start the drag operation. Use this method
+ * to specify that an element inside of the content div is the element
+ * that starts the drag operation.
+ * @method setHandleElId
+ * @param id {string} the id of the element that will be used to
+ * initiate the drag.
+ */
+ setHandleElId: function(id) {
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+ this.handleElId = id;
+ this.DDM.regHandle(this.id, id);
+ },
+
+ /**
+ * Allows you to set an element outside of the linked element as a drag
+ * handle
+ * @method setOuterHandleElId
+ * @param id the id of the element that will be used to initiate the drag
+ */
+ setOuterHandleElId: function(id) {
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+ Event.on(id, "mousedown",
+ this.handleMouseDown, this);
+ this.setHandleElId(id);
+
+ this.hasOuterHandles = true;
+ },
+
+ /**
+ * Remove all drag and drop hooks for this element
+ * @method unreg
+ */
+ unreg: function() {
+ Event.un(this.id, "mousedown",
+ this.handleMouseDown);
+ this._domRef = null;
+ this.DDM._remove(this);
+ },
+
+ destroy : function(){
+ this.unreg();
+ },
+
+ /**
+ * Returns true if this instance is locked, or the drag drop mgr is locked
+ * (meaning that all drag/drop is disabled on the page.)
+ * @method isLocked
+ * @return {boolean} true if this obj or all drag/drop is locked, else
+ * false
+ */
+ isLocked: function() {
+ return (this.DDM.isLocked() || this.locked);
+ },
+
+ /**
+ * Fired when this object is clicked
+ * @method handleMouseDown
+ * @param {Event} e
+ * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
+ * @private
+ */
+ handleMouseDown: function(e, oDD){
+ if (this.primaryButtonOnly && e.button != 0) {
+ return;
+ }
+
+ if (this.isLocked()) {
+ return;
+ }
+
+ this.DDM.refreshCache(this.groups);
+
+ var pt = new Ext.lib.Point(Ext.lib.Event.getPageX(e), Ext.lib.Event.getPageY(e));
+ if (!this.hasOuterHandles && !this.DDM.isOverTarget(pt, this) ) {
+ } else {
+ if (this.clickValidator(e)) {
+
+ // set the initial element position
+ this.setStartPosition();
+
+ this.b4MouseDown(e);
+ this.onMouseDown(e);
+
+ this.DDM.handleMouseDown(e, this);
+
+ this.DDM.stopEvent(e);
+ } else {
+
+
+ }
+ }
+ },
+
+ clickValidator: function(e) {
+ var target = e.getTarget();
+ return ( this.isValidHandleChild(target) &&
+ (this.id == this.handleElId ||
+ this.DDM.handleWasClicked(target, this.id)) );
+ },
+
+ /**
+ * Allows you to specify a tag name that should not start a drag operation
+ * when clicked. This is designed to facilitate embedding links within a
+ * drag handle that do something other than start the drag.
+ * @method addInvalidHandleType
+ * @param {string} tagName the type of element to exclude
+ */
+ addInvalidHandleType: function(tagName) {
+ var type = tagName.toUpperCase();
+ this.invalidHandleTypes[type] = type;
+ },
+
+ /**
+ * Lets you to specify an element id for a child of a drag handle
+ * that should not initiate a drag
+ * @method addInvalidHandleId
+ * @param {string} id the element id of the element you wish to ignore
+ */
+ addInvalidHandleId: function(id) {
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+ this.invalidHandleIds[id] = id;
+ },
+
+ /**
+ * Lets you specify a css class of elements that will not initiate a drag
+ * @method addInvalidHandleClass
+ * @param {string} cssClass the class of the elements you wish to ignore
+ */
+ addInvalidHandleClass: function(cssClass) {
+ this.invalidHandleClasses.push(cssClass);
+ },
+
+ /**
+ * Unsets an excluded tag name set by addInvalidHandleType
+ * @method removeInvalidHandleType
+ * @param {string} tagName the type of element to unexclude
+ */
+ removeInvalidHandleType: function(tagName) {
+ var type = tagName.toUpperCase();
+ // this.invalidHandleTypes[type] = null;
+ delete this.invalidHandleTypes[type];
+ },
+
+ /**
+ * Unsets an invalid handle id
+ * @method removeInvalidHandleId
+ * @param {string} id the id of the element to re-enable
+ */
+ removeInvalidHandleId: function(id) {
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+ delete this.invalidHandleIds[id];
+ },
+
+ /**
+ * Unsets an invalid css class
+ * @method removeInvalidHandleClass
+ * @param {string} cssClass the class of the element(s) you wish to
+ * re-enable
+ */
+ removeInvalidHandleClass: function(cssClass) {
+ for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
+ if (this.invalidHandleClasses[i] == cssClass) {
+ delete this.invalidHandleClasses[i];
+ }
+ }
+ },
+
+ /**
+ * Checks the tag exclusion list to see if this click should be ignored
+ * @method isValidHandleChild
+ * @param {HTMLElement} node the HTMLElement to evaluate
+ * @return {boolean} true if this is a valid tag type, false if not
+ */
+ isValidHandleChild: function(node) {
+
+ var valid = true;
+ // var n = (node.nodeName == "#text") ? node.parentNode : node;
+ var nodeName;
+ try {
+ nodeName = node.nodeName.toUpperCase();
+ } catch(e) {
+ nodeName = node.nodeName;
+ }
+ valid = valid && !this.invalidHandleTypes[nodeName];
+ valid = valid && !this.invalidHandleIds[node.id];
+
+ for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
+ valid = !Ext.fly(node).hasClass(this.invalidHandleClasses[i]);
+ }
+
+
+ return valid;
+
+ },
+
+ /**
+ * Create the array of horizontal tick marks if an interval was specified
+ * in setXConstraint().
+ * @method setXTicks
+ * @private
+ */
+ setXTicks: function(iStartX, iTickSize) {
+ this.xTicks = [];
+ this.xTickSize = iTickSize;
+
+ var tickMap = {};
+
+ for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
+ if (!tickMap[i]) {
+ this.xTicks[this.xTicks.length] = i;
+ tickMap[i] = true;
+ }
+ }
+
+ for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
+ if (!tickMap[i]) {
+ this.xTicks[this.xTicks.length] = i;
+ tickMap[i] = true;
+ }
+ }
+
+ this.xTicks.sort(this.DDM.numericSort) ;
+ },
+
+ /**
+ * Create the array of vertical tick marks if an interval was specified in
+ * setYConstraint().
+ * @method setYTicks
+ * @private
+ */
+ setYTicks: function(iStartY, iTickSize) {
+ this.yTicks = [];
+ this.yTickSize = iTickSize;
+
+ var tickMap = {};
+
+ for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
+ if (!tickMap[i]) {
+ this.yTicks[this.yTicks.length] = i;
+ tickMap[i] = true;
+ }
+ }
+
+ for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
+ if (!tickMap[i]) {
+ this.yTicks[this.yTicks.length] = i;
+ tickMap[i] = true;
+ }
+ }
+
+ this.yTicks.sort(this.DDM.numericSort) ;
+ },
+
+ /**
+ * By default, the element can be dragged any place on the screen. Use
+ * this method to limit the horizontal travel of the element. Pass in
+ * 0,0 for the parameters if you want to lock the drag to the y axis.
+ * @method setXConstraint
+ * @param {int} iLeft the number of pixels the element can move to the left
+ * @param {int} iRight the number of pixels the element can move to the
+ * right
+ * @param {int} iTickSize optional parameter for specifying that the
+ * element
+ * should move iTickSize pixels at a time.
+ */
+ setXConstraint: function(iLeft, iRight, iTickSize) {
+ this.leftConstraint = iLeft;
+ this.rightConstraint = iRight;
+
+ this.minX = this.initPageX - iLeft;
+ this.maxX = this.initPageX + iRight;
+ if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
+
+ this.constrainX = true;
+ },
+
+ /**
+ * Clears any constraints applied to this instance. Also clears ticks
+ * since they can't exist independent of a constraint at this time.
+ * @method clearConstraints
+ */
+ clearConstraints: function() {
+ this.constrainX = false;
+ this.constrainY = false;
+ this.clearTicks();
+ },
+
+ /**
+ * Clears any tick interval defined for this instance
+ * @method clearTicks
+ */
+ clearTicks: function() {
+ this.xTicks = null;
+ this.yTicks = null;
+ this.xTickSize = 0;
+ this.yTickSize = 0;
+ },
+
+ /**
+ * By default, the element can be dragged any place on the screen. Set
+ * this to limit the vertical travel of the element. Pass in 0,0 for the
+ * parameters if you want to lock the drag to the x axis.
+ * @method setYConstraint
+ * @param {int} iUp the number of pixels the element can move up
+ * @param {int} iDown the number of pixels the element can move down
+ * @param {int} iTickSize optional parameter for specifying that the
+ * element should move iTickSize pixels at a time.
+ */
+ setYConstraint: function(iUp, iDown, iTickSize) {
+ this.topConstraint = iUp;
+ this.bottomConstraint = iDown;
+
+ this.minY = this.initPageY - iUp;
+ this.maxY = this.initPageY + iDown;
+ if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
+
+ this.constrainY = true;
+
+ },
+
+ /**
+ * resetConstraints must be called if you manually reposition a dd element.
+ * @method resetConstraints
+ * @param {boolean} maintainOffset
+ */
+ resetConstraints: function() {
+ // Maintain offsets if necessary
+ if (this.initPageX || this.initPageX === 0) {
+ // figure out how much this thing has moved
+ var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
+ var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
+
+ this.setInitPosition(dx, dy);
+
+ // This is the first time we have detected the element's position
+ } else {
+ this.setInitPosition();
+ }
+
+ if (this.constrainX) {
+ this.setXConstraint( this.leftConstraint,
+ this.rightConstraint,
+ this.xTickSize );
+ }
+
+ if (this.constrainY) {
+ this.setYConstraint( this.topConstraint,
+ this.bottomConstraint,
+ this.yTickSize );
+ }
+ },
+
+ /**
+ * Normally the drag element is moved pixel by pixel, but we can specify
+ * that it move a number of pixels at a time. This method resolves the
+ * location when we have it set up like this.
+ * @method getTick
+ * @param {int} val where we want to place the object
+ * @param {int[]} tickArray sorted array of valid points
+ * @return {int} the closest tick
+ * @private
+ */
+ getTick: function(val, tickArray) {
+ if (!tickArray) {
+ // If tick interval is not defined, it is effectively 1 pixel,
+ // so we return the value passed to us.
+ return val;
+ } else if (tickArray[0] >= val) {
+ // The value is lower than the first tick, so we return the first
+ // tick.
+ return tickArray[0];
+ } else {
+ for (var i=0, len=tickArray.length; i<len; ++i) {
+ var next = i + 1;
+ if (tickArray[next] && tickArray[next] >= val) {
+ var diff1 = val - tickArray[i];
+ var diff2 = tickArray[next] - val;
+ return (diff2 > diff1) ? tickArray[i] : tickArray[next];
+ }
+ }
+
+ // The value is larger than the last tick, so we return the last
+ // tick.
+ return tickArray[tickArray.length - 1];
+ }
+ },
+
+ /**
+ * toString method
+ * @method toString
+ * @return {string} string representation of the dd obj
+ */
+ toString: function() {
+ return ("DragDrop " + this.id);
+ }
+
+};
+
+})();
+/*!
+ * The drag and drop utility provides a framework for building drag and drop
+ * applications. In addition to enabling drag and drop for specific elements,
+ * the drag and drop elements are tracked by the manager class, and the
+ * interactions between the various elements are tracked during the drag and
+ * the implementing code is notified about these important moments.
+ */
+
+// Only load the library once. Rewriting the manager class would orphan
+// existing drag and drop instances.
+if (!Ext.dd.DragDropMgr) {
+
+/**
+ * @class Ext.dd.DragDropMgr
+ * DragDropMgr is a singleton that tracks the element interaction for
+ * all DragDrop items in the window. Generally, you will not call
+ * this class directly, but it does have helper methods that could
+ * be useful in your DragDrop implementations.
+ * @singleton
+ */
+Ext.dd.DragDropMgr = function() {
+
+ var Event = Ext.EventManager;
+
+ return {
+
+ /**
+ * Two dimensional Array of registered DragDrop objects. The first
+ * dimension is the DragDrop item group, the second the DragDrop
+ * object.
+ * @property ids
+ * @type String[]
+ * @private
+ * @static
+ */
+ ids: {},
+
+ /**
+ * Array of element ids defined as drag handles. Used to determine
+ * if the element that generated the mousedown event is actually the
+ * handle and not the html element itself.
+ * @property handleIds
+ * @type String[]
+ * @private
+ * @static
+ */
+ handleIds: {},
+
+ /**
+ * the DragDrop object that is currently being dragged
+ * @property dragCurrent
+ * @type DragDrop
+ * @private
+ * @static
+ **/
+ dragCurrent: null,
+
+ /**
+ * the DragDrop object(s) that are being hovered over
+ * @property dragOvers
+ * @type Array
+ * @private
+ * @static
+ */
+ dragOvers: {},
+
+ /**
+ * the X distance between the cursor and the object being dragged
+ * @property deltaX
+ * @type int
+ * @private
+ * @static
+ */
+ deltaX: 0,
+
+ /**
+ * the Y distance between the cursor and the object being dragged
+ * @property deltaY
+ * @type int
+ * @private
+ * @static
+ */
+ deltaY: 0,
+
+ /**
+ * Flag to determine if we should prevent the default behavior of the
+ * events we define. By default this is true, but this can be set to
+ * false if you need the default behavior (not recommended)
+ * @property preventDefault
+ * @type boolean
+ * @static
+ */
+ preventDefault: true,
+
+ /**
+ * Flag to determine if we should stop the propagation of the events
+ * we generate. This is true by default but you may want to set it to
+ * false if the html element contains other features that require the
+ * mouse click.
+ * @property stopPropagation
+ * @type boolean
+ * @static
+ */
+ stopPropagation: true,
+
+ /**
+ * Internal flag that is set to true when drag and drop has been
+ * intialized
+ * @property initialized
+ * @private
+ * @static
+ */
+ initialized: false,
+
+ /**
+ * All drag and drop can be disabled.
+ * @property locked
+ * @private
+ * @static
+ */
+ locked: false,
+
+ /**
+ * Called the first time an element is registered.
+ * @method init
+ * @private
+ * @static
+ */
+ init: function() {
+ this.initialized = true;
+ },
+
+ /**
+ * In point mode, drag and drop interaction is defined by the
+ * location of the cursor during the drag/drop
+ * @property POINT
+ * @type int
+ * @static
+ */
+ POINT: 0,
+
+ /**
+ * In intersect mode, drag and drop interaction is defined by the
+ * overlap of two or more drag and drop objects.
+ * @property INTERSECT
+ * @type int
+ * @static
+ */
+ INTERSECT: 1,
+
+ /**
+ * The current drag and drop mode. Default: POINT
+ * @property mode
+ * @type int
+ * @static
+ */
+ mode: 0,
+
+ /**
+ * Runs method on all drag and drop objects
+ * @method _execOnAll
+ * @private
+ * @static
+ */
+ _execOnAll: function(sMethod, args) {
+ for (var i in this.ids) {
+ for (var j in this.ids[i]) {
+ var oDD = this.ids[i][j];
+ if (! this.isTypeOfDD(oDD)) {
+ continue;
+ }
+ oDD[sMethod].apply(oDD, args);
+ }
+ }
+ },
+
+ /**
+ * Drag and drop initialization. Sets up the global event handlers
+ * @method _onLoad
+ * @private
+ * @static
+ */
+ _onLoad: function() {
+
+ this.init();
+
+
+ Event.on(document, "mouseup", this.handleMouseUp, this, true);
+ Event.on(document, "mousemove", this.handleMouseMove, this, true);
+ Event.on(window, "unload", this._onUnload, this, true);
+ Event.on(window, "resize", this._onResize, this, true);
+ // Event.on(window, "mouseout", this._test);
+
+ },
+
+ /**
+ * Reset constraints on all drag and drop objs
+ * @method _onResize
+ * @private
+ * @static
+ */
+ _onResize: function(e) {
+ this._execOnAll("resetConstraints", []);
+ },
+
+ /**
+ * Lock all drag and drop functionality
+ * @method lock
+ * @static
+ */
+ lock: function() { this.locked = true; },
+
+ /**
+ * Unlock all drag and drop functionality
+ * @method unlock
+ * @static
+ */
+ unlock: function() { this.locked = false; },
+
+ /**
+ * Is drag and drop locked?
+ * @method isLocked
+ * @return {boolean} True if drag and drop is locked, false otherwise.
+ * @static
+ */
+ isLocked: function() { return this.locked; },
+
+ /**
+ * Location cache that is set for all drag drop objects when a drag is
+ * initiated, cleared when the drag is finished.
+ * @property locationCache
+ * @private
+ * @static
+ */
+ locationCache: {},
+
+ /**
+ * Set useCache to false if you want to force object the lookup of each
+ * drag and drop linked element constantly during a drag.
+ * @property useCache
+ * @type boolean
+ * @static
+ */
+ useCache: true,
+
+ /**
+ * The number of pixels that the mouse needs to move after the
+ * mousedown before the drag is initiated. Default=3;
+ * @property clickPixelThresh
+ * @type int
+ * @static
+ */
+ clickPixelThresh: 3,
+
+ /**
+ * The number of milliseconds after the mousedown event to initiate the
+ * drag if we don't get a mouseup event. Default=350
+ * @property clickTimeThresh
+ * @type int
+ * @static
+ */
+ clickTimeThresh: 350,
+
+ /**
+ * Flag that indicates that either the drag pixel threshold or the
+ * mousdown time threshold has been met
+ * @property dragThreshMet
+ * @type boolean
+ * @private
+ * @static
+ */
+ dragThreshMet: false,
+
+ /**
+ * Timeout used for the click time threshold
+ * @property clickTimeout
+ * @type Object
+ * @private
+ * @static
+ */
+ clickTimeout: null,
+
+ /**
+ * The X position of the mousedown event stored for later use when a
+ * drag threshold is met.
+ * @property startX
+ * @type int
+ * @private
+ * @static
+ */
+ startX: 0,
+
+ /**
+ * The Y position of the mousedown event stored for later use when a
+ * drag threshold is met.
+ * @property startY
+ * @type int
+ * @private
+ * @static
+ */
+ startY: 0,
+
+ /**
+ * Each DragDrop instance must be registered with the DragDropMgr.
+ * This is executed in DragDrop.init()
+ * @method regDragDrop
+ * @param {DragDrop} oDD the DragDrop object to register
+ * @param {String} sGroup the name of the group this element belongs to
+ * @static
+ */
+ regDragDrop: function(oDD, sGroup) {
+ if (!this.initialized) { this.init(); }
+
+ if (!this.ids[sGroup]) {
+ this.ids[sGroup] = {};
+ }
+ this.ids[sGroup][oDD.id] = oDD;
+ },
+
+ /**
+ * Removes the supplied dd instance from the supplied group. Executed
+ * by DragDrop.removeFromGroup, so don't call this function directly.
+ * @method removeDDFromGroup
+ * @private
+ * @static
+ */
+ removeDDFromGroup: function(oDD, sGroup) {
+ if (!this.ids[sGroup]) {
+ this.ids[sGroup] = {};
+ }
+
+ var obj = this.ids[sGroup];
+ if (obj && obj[oDD.id]) {
+ delete obj[oDD.id];
+ }
+ },
+
+ /**
+ * Unregisters a drag and drop item. This is executed in
+ * DragDrop.unreg, use that method instead of calling this directly.
+ * @method _remove
+ * @private
+ * @static
+ */
+ _remove: function(oDD) {
+ for (var g in oDD.groups) {
+ if (g && this.ids[g] && this.ids[g][oDD.id]) {
+ delete this.ids[g][oDD.id];
+ }
+ }
+ delete this.handleIds[oDD.id];
+ },
+
+ /**
+ * Each DragDrop handle element must be registered. This is done
+ * automatically when executing DragDrop.setHandleElId()
+ * @method regHandle
+ * @param {String} sDDId the DragDrop id this element is a handle for
+ * @param {String} sHandleId the id of the element that is the drag
+ * handle
+ * @static
+ */
+ regHandle: function(sDDId, sHandleId) {
+ if (!this.handleIds[sDDId]) {
+ this.handleIds[sDDId] = {};
+ }
+ this.handleIds[sDDId][sHandleId] = sHandleId;
+ },
+
+ /**
+ * Utility function to determine if a given element has been
+ * registered as a drag drop item.
+ * @method isDragDrop
+ * @param {String} id the element id to check
+ * @return {boolean} true if this element is a DragDrop item,
+ * false otherwise
+ * @static
+ */
+ isDragDrop: function(id) {
+ return ( this.getDDById(id) ) ? true : false;
+ },
+
+ /**
+ * Returns the drag and drop instances that are in all groups the
+ * passed in instance belongs to.
+ * @method getRelated
+ * @param {DragDrop} p_oDD the obj to get related data for
+ * @param {boolean} bTargetsOnly if true, only return targetable objs
+ * @return {DragDrop[]} the related instances
+ * @static
+ */
+ getRelated: function(p_oDD, bTargetsOnly) {
+ var oDDs = [];
+ for (var i in p_oDD.groups) {
+ for (var j in this.ids[i]) {
+ var dd = this.ids[i][j];
+ if (! this.isTypeOfDD(dd)) {
+ continue;
+ }
+ if (!bTargetsOnly || dd.isTarget) {
+ oDDs[oDDs.length] = dd;
+ }
+ }
+ }
+
+ return oDDs;
+ },
+
+ /**
+ * Returns true if the specified dd target is a legal target for
+ * the specifice drag obj
+ * @method isLegalTarget
+ * @param {DragDrop} oDD the drag obj
+ * @param {DragDrop} oTargetDD the target
+ * @return {boolean} true if the target is a legal target for the
+ * dd obj
+ * @static
+ */
+ isLegalTarget: function (oDD, oTargetDD) {
+ var targets = this.getRelated(oDD, true);
+ for (var i=0, len=targets.length;i<len;++i) {
+ if (targets[i].id == oTargetDD.id) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * My goal is to be able to transparently determine if an object is
+ * typeof DragDrop, and the exact subclass of DragDrop. typeof
+ * returns "object", oDD.constructor.toString() always returns
+ * "DragDrop" and not the name of the subclass. So for now it just
+ * evaluates a well-known variable in DragDrop.
+ * @method isTypeOfDD
+ * @param {Object} the object to evaluate
+ * @return {boolean} true if typeof oDD = DragDrop
+ * @static
+ */
+ isTypeOfDD: function (oDD) {
+ return (oDD && oDD.__ygDragDrop);
+ },
+
+ /**
+ * Utility function to determine if a given element has been
+ * registered as a drag drop handle for the given Drag Drop object.
+ * @method isHandle
+ * @param {String} id the element id to check
+ * @return {boolean} true if this element is a DragDrop handle, false
+ * otherwise
+ * @static
+ */
+ isHandle: function(sDDId, sHandleId) {
+ return ( this.handleIds[sDDId] &&
+ this.handleIds[sDDId][sHandleId] );
+ },
+
+ /**
+ * Returns the DragDrop instance for a given id
+ * @method getDDById
+ * @param {String} id the id of the DragDrop object
+ * @return {DragDrop} the drag drop object, null if it is not found
+ * @static
+ */
+ getDDById: function(id) {
+ for (var i in this.ids) {
+ if (this.ids[i][id]) {
+ return this.ids[i][id];
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Fired after a registered DragDrop object gets the mousedown event.
+ * Sets up the events required to track the object being dragged
+ * @method handleMouseDown
+ * @param {Event} e the event
+ * @param oDD the DragDrop object being dragged
+ * @private
+ * @static
+ */
+ handleMouseDown: function(e, oDD) {
+ if(Ext.QuickTips){
+ Ext.QuickTips.disable();
+ }
+ if(this.dragCurrent){
+ // the original browser mouseup wasn't handled (e.g. outside FF browser window)
+ // so clean up first to avoid breaking the next drag
+ this.handleMouseUp(e);
+ }
+
+ this.currentTarget = e.getTarget();
+ this.dragCurrent = oDD;
+
+ var el = oDD.getEl();
+
+ // track start position
+ this.startX = e.getPageX();
+ this.startY = e.getPageY();
+
+ this.deltaX = this.startX - el.offsetLeft;
+ this.deltaY = this.startY - el.offsetTop;
+
+ this.dragThreshMet = false;
+
+ this.clickTimeout = setTimeout(
+ function() {
+ var DDM = Ext.dd.DDM;
+ DDM.startDrag(DDM.startX, DDM.startY);
+ },
+ this.clickTimeThresh );
+ },
+
+ /**
+ * Fired when either the drag pixel threshol or the mousedown hold
+ * time threshold has been met.
+ * @method startDrag
+ * @param x {int} the X position of the original mousedown
+ * @param y {int} the Y position of the original mousedown
+ * @static
+ */
+ startDrag: function(x, y) {
+ clearTimeout(this.clickTimeout);
+ if (this.dragCurrent) {
+ this.dragCurrent.b4StartDrag(x, y);
+ this.dragCurrent.startDrag(x, y);
+ }
+ this.dragThreshMet = true;
+ },
+
+ /**
+ * Internal function to handle the mouseup event. Will be invoked
+ * from the context of the document.
+ * @method handleMouseUp
+ * @param {Event} e the event
+ * @private
+ * @static
+ */
+ handleMouseUp: function(e) {
+
+ if(Ext.QuickTips){
+ Ext.QuickTips.enable();
+ }
+ if (! this.dragCurrent) {
+ return;
+ }
+
+ clearTimeout(this.clickTimeout);
+
+ if (this.dragThreshMet) {
+ this.fireEvents(e, true);
+ } else {
+ }
+
+ this.stopDrag(e);
+
+ this.stopEvent(e);
+ },
+
+ /**
+ * Utility to stop event propagation and event default, if these
+ * features are turned on.
+ * @method stopEvent
+ * @param {Event} e the event as returned by this.getEvent()
+ * @static
+ */
+ stopEvent: function(e){
+ if(this.stopPropagation) {
+ e.stopPropagation();
+ }
+
+ if (this.preventDefault) {
+ e.preventDefault();
+ }
+ },
+
+ /**
+ * Internal function to clean up event handlers after the drag
+ * operation is complete
+ * @method stopDrag
+ * @param {Event} e the event
+ * @private
+ * @static
+ */
+ stopDrag: function(e) {
+ // Fire the drag end event for the item that was dragged
+ if (this.dragCurrent) {
+ if (this.dragThreshMet) {
+ this.dragCurrent.b4EndDrag(e);
+ this.dragCurrent.endDrag(e);
+ }
+
+ this.dragCurrent.onMouseUp(e);
+ }
+
+ this.dragCurrent = null;
+ this.dragOvers = {};
+ },
+
+ /**
+ * Internal function to handle the mousemove event. Will be invoked
+ * from the context of the html element.
+ *
+ * @TODO figure out what we can do about mouse events lost when the
+ * user drags objects beyond the window boundary. Currently we can
+ * detect this in internet explorer by verifying that the mouse is
+ * down during the mousemove event. Firefox doesn't give us the
+ * button state on the mousemove event.
+ * @method handleMouseMove
+ * @param {Event} e the event
+ * @private
+ * @static
+ */
+ handleMouseMove: function(e) {
+ if (! this.dragCurrent) {
+ return true;
+ }
+ // var button = e.which || e.button;
+
+ // check for IE mouseup outside of page boundary
+ if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
+ this.stopEvent(e);
+ return this.handleMouseUp(e);
+ }
+
+ if (!this.dragThreshMet) {
+ var diffX = Math.abs(this.startX - e.getPageX());
+ var diffY = Math.abs(this.startY - e.getPageY());
+ if (diffX > this.clickPixelThresh ||
+ diffY > this.clickPixelThresh) {
+ this.startDrag(this.startX, this.startY);
+ }
+ }
+
+ if (this.dragThreshMet) {
+ this.dragCurrent.b4Drag(e);
+ this.dragCurrent.onDrag(e);
+ if(!this.dragCurrent.moveOnly){
+ this.fireEvents(e, false);
+ }
+ }
+
+ this.stopEvent(e);
+
+ return true;
+ },
+
+ /**
+ * Iterates over all of the DragDrop elements to find ones we are
+ * hovering over or dropping on
+ * @method fireEvents
+ * @param {Event} e the event
+ * @param {boolean} isDrop is this a drop op or a mouseover op?
+ * @private
+ * @static
+ */
+ fireEvents: function(e, isDrop) {
+ var dc = this.dragCurrent;
+
+ // If the user did the mouse up outside of the window, we could
+ // get here even though we have ended the drag.
+ if (!dc || dc.isLocked()) {
+ return;
+ }
+
+ var pt = e.getPoint();
+
+ // cache the previous dragOver array
+ var oldOvers = [];
+
+ var outEvts = [];
+ var overEvts = [];
+ var dropEvts = [];
+ var enterEvts = [];
+
+ // Check to see if the object(s) we were hovering over is no longer
+ // being hovered over so we can fire the onDragOut event
+ for (var i in this.dragOvers) {
+
+ var ddo = this.dragOvers[i];
+
+ if (! this.isTypeOfDD(ddo)) {
+ continue;
+ }
+
+ if (! this.isOverTarget(pt, ddo, this.mode)) {
+ outEvts.push( ddo );
+ }
+
+ oldOvers[i] = true;
+ delete this.dragOvers[i];
+ }
+
+ for (var sGroup in dc.groups) {
+
+ if ("string" != typeof sGroup) {
+ continue;
+ }
+
+ for (i in this.ids[sGroup]) {
+ var oDD = this.ids[sGroup][i];
+ if (! this.isTypeOfDD(oDD)) {
+ continue;
+ }
+
+ if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
+ if (this.isOverTarget(pt, oDD, this.mode)) {
+ // look for drop interactions
+ if (isDrop) {
+ dropEvts.push( oDD );
+ // look for drag enter and drag over interactions
+ } else {
+
+ // initial drag over: dragEnter fires
+ if (!oldOvers[oDD.id]) {
+ enterEvts.push( oDD );
+ // subsequent drag overs: dragOver fires
+ } else {
+ overEvts.push( oDD );
+ }
+
+ this.dragOvers[oDD.id] = oDD;
+ }
+ }
+ }
+ }
+ }
+
+ if (this.mode) {
+ if (outEvts.length) {
+ dc.b4DragOut(e, outEvts);
+ dc.onDragOut(e, outEvts);
+ }
+
+ if (enterEvts.length) {
+ dc.onDragEnter(e, enterEvts);
+ }
+
+ if (overEvts.length) {
+ dc.b4DragOver(e, overEvts);
+ dc.onDragOver(e, overEvts);
+ }
+
+ if (dropEvts.length) {
+ dc.b4DragDrop(e, dropEvts);
+ dc.onDragDrop(e, dropEvts);
+ }
+
+ } else {
+ // fire dragout events
+ var len = 0;
+ for (i=0, len=outEvts.length; i<len; ++i) {
+ dc.b4DragOut(e, outEvts[i].id);
+ dc.onDragOut(e, outEvts[i].id);
+ }
+
+ // fire enter events
+ for (i=0,len=enterEvts.length; i<len; ++i) {
+ // dc.b4DragEnter(e, oDD.id);
+ dc.onDragEnter(e, enterEvts[i].id);
+ }
+
+ // fire over events
+ for (i=0,len=overEvts.length; i<len; ++i) {
+ dc.b4DragOver(e, overEvts[i].id);
+ dc.onDragOver(e, overEvts[i].id);
+ }
+
+ // fire drop events
+ for (i=0, len=dropEvts.length; i<len; ++i) {
+ dc.b4DragDrop(e, dropEvts[i].id);
+ dc.onDragDrop(e, dropEvts[i].id);
+ }
+
+ }
+
+ // notify about a drop that did not find a target
+ if (isDrop && !dropEvts.length) {
+ dc.onInvalidDrop(e);
+ }
+
+ },
+
+ /**
+ * Helper function for getting the best match from the list of drag
+ * and drop objects returned by the drag and drop events when we are
+ * in INTERSECT mode. It returns either the first object that the
+ * cursor is over, or the object that has the greatest overlap with
+ * the dragged element.
+ * @method getBestMatch
+ * @param {DragDrop[]} dds The array of drag and drop objects
+ * targeted
+ * @return {DragDrop} The best single match
+ * @static
+ */
+ getBestMatch: function(dds) {
+ var winner = null;
+ // Return null if the input is not what we expect
+ //if (!dds || !dds.length || dds.length == 0) {
+ // winner = null;
+ // If there is only one item, it wins
+ //} else if (dds.length == 1) {
+
+ var len = dds.length;
+
+ if (len == 1) {
+ winner = dds[0];
+ } else {
+ // Loop through the targeted items
+ for (var i=0; i<len; ++i) {
+ var dd = dds[i];
+ // If the cursor is over the object, it wins. If the
+ // cursor is over multiple matches, the first one we come
+ // to wins.
+ if (dd.cursorIsOver) {
+ winner = dd;
+ break;
+ // Otherwise the object with the most overlap wins
+ } else {
+ if (!winner ||
+ winner.overlap.getArea() < dd.overlap.getArea()) {
+ winner = dd;
+ }
+ }
+ }
+ }
+
+ return winner;
+ },
+
+ /**
+ * Refreshes the cache of the top-left and bottom-right points of the
+ * drag and drop objects in the specified group(s). This is in the
+ * format that is stored in the drag and drop instance, so typical
+ * usage is:
+ * <code>
+ * Ext.dd.DragDropMgr.refreshCache(ddinstance.groups);
+ * </code>
+ * Alternatively:
+ * <code>
+ * Ext.dd.DragDropMgr.refreshCache({group1:true, group2:true});
+ * </code>
+ * @TODO this really should be an indexed array. Alternatively this
+ * method could accept both.
+ * @method refreshCache
+ * @param {Object} groups an associative array of groups to refresh
+ * @static
+ */
+ refreshCache: function(groups) {
+ for (var sGroup in groups) {
+ if ("string" != typeof sGroup) {
+ continue;
+ }
+ for (var i in this.ids[sGroup]) {
+ var oDD = this.ids[sGroup][i];
+
+ if (this.isTypeOfDD(oDD)) {
+ // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
+ var loc = this.getLocation(oDD);
+ if (loc) {
+ this.locationCache[oDD.id] = loc;
+ } else {
+ delete this.locationCache[oDD.id];
+ // this will unregister the drag and drop object if
+ // the element is not in a usable state
+ // oDD.unreg();
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * This checks to make sure an element exists and is in the DOM. The
+ * main purpose is to handle cases where innerHTML is used to remove
+ * drag and drop objects from the DOM. IE provides an 'unspecified
+ * error' when trying to access the offsetParent of such an element
+ * @method verifyEl
+ * @param {HTMLElement} el the element to check
+ * @return {boolean} true if the element looks usable
+ * @static
+ */
+ verifyEl: function(el) {
+ if (el) {
+ var parent;
+ if(Ext.isIE){
+ try{
+ parent = el.offsetParent;
+ }catch(e){}
+ }else{
+ parent = el.offsetParent;
+ }
+ if (parent) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Returns a Region object containing the drag and drop element's position
+ * and size, including the padding configured for it
+ * @method getLocation
+ * @param {DragDrop} oDD the drag and drop object to get the
+ * location for
+ * @return {Ext.lib.Region} a Region object representing the total area
+ * the element occupies, including any padding
+ * the instance is configured for.
+ * @static
+ */
+ getLocation: function(oDD) {
+ if (! this.isTypeOfDD(oDD)) {
+ return null;
+ }
+
+ var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
+
+ try {
+ pos= Ext.lib.Dom.getXY(el);
+ } catch (e) { }
+
+ if (!pos) {
+ return null;
+ }
+
+ x1 = pos[0];
+ x2 = x1 + el.offsetWidth;
+ y1 = pos[1];
+ y2 = y1 + el.offsetHeight;
+
+ t = y1 - oDD.padding[0];
+ r = x2 + oDD.padding[1];
+ b = y2 + oDD.padding[2];
+ l = x1 - oDD.padding[3];
+
+ return new Ext.lib.Region( t, r, b, l );
+ },
+
+ /**
+ * Checks the cursor location to see if it over the target
+ * @method isOverTarget
+ * @param {Ext.lib.Point} pt The point to evaluate
+ * @param {DragDrop} oTarget the DragDrop object we are inspecting
+ * @return {boolean} true if the mouse is over the target
+ * @private
+ * @static
+ */
+ isOverTarget: function(pt, oTarget, intersect) {
+ // use cache if available
+ var loc = this.locationCache[oTarget.id];
+ if (!loc || !this.useCache) {
+ loc = this.getLocation(oTarget);
+ this.locationCache[oTarget.id] = loc;
+
+ }
+
+ if (!loc) {
+ return false;
+ }
+
+ oTarget.cursorIsOver = loc.contains( pt );
+
+ // DragDrop is using this as a sanity check for the initial mousedown
+ // in this case we are done. In POINT mode, if the drag obj has no
+ // contraints, we are also done. Otherwise we need to evaluate the
+ // location of the target as related to the actual location of the
+ // dragged element.
+ var dc = this.dragCurrent;
+ if (!dc || !dc.getTargetCoord ||
+ (!intersect && !dc.constrainX && !dc.constrainY)) {
+ return oTarget.cursorIsOver;
+ }
+
+ oTarget.overlap = null;
+
+ // Get the current location of the drag element, this is the
+ // location of the mouse event less the delta that represents
+ // where the original mousedown happened on the element. We
+ // need to consider constraints and ticks as well.
+ var pos = dc.getTargetCoord(pt.x, pt.y);
+
+ var el = dc.getDragEl();
+ var curRegion = new Ext.lib.Region( pos.y,
+ pos.x + el.offsetWidth,
+ pos.y + el.offsetHeight,
+ pos.x );
+
+ var overlap = curRegion.intersect(loc);
+
+ if (overlap) {
+ oTarget.overlap = overlap;
+ return (intersect) ? true : oTarget.cursorIsOver;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * unload event handler
+ * @method _onUnload
+ * @private
+ * @static
+ */
+ _onUnload: function(e, me) {
+ Ext.dd.DragDropMgr.unregAll();
+ },
+
+ /**
+ * Cleans up the drag and drop events and objects.
+ * @method unregAll
+ * @private
+ * @static
+ */
+ unregAll: function() {
+
+ if (this.dragCurrent) {
+ this.stopDrag();
+ this.dragCurrent = null;
+ }
+
+ this._execOnAll("unreg", []);
+
+ for (var i in this.elementCache) {
+ delete this.elementCache[i];
+ }
+
+ this.elementCache = {};
+ this.ids = {};
+ },
+
+ /**
+ * A cache of DOM elements
+ * @property elementCache
+ * @private
+ * @static
+ */
+ elementCache: {},
+
+ /**
+ * Get the wrapper for the DOM element specified
+ * @method getElWrapper
+ * @param {String} id the id of the element to get
+ * @return {Ext.dd.DDM.ElementWrapper} the wrapped element
+ * @private
+ * @deprecated This wrapper isn't that useful
+ * @static
+ */
+ getElWrapper: function(id) {
+ var oWrapper = this.elementCache[id];
+ if (!oWrapper || !oWrapper.el) {
+ oWrapper = this.elementCache[id] =
+ new this.ElementWrapper(Ext.getDom(id));
+ }
+ return oWrapper;
+ },
+
+ /**
+ * Returns the actual DOM element
+ * @method getElement
+ * @param {String} id the id of the elment to get
+ * @return {Object} The element
+ * @deprecated use Ext.lib.Ext.getDom instead
+ * @static
+ */
+ getElement: function(id) {
+ return Ext.getDom(id);
+ },
+
+ /**
+ * Returns the style property for the DOM element (i.e.,
+ * document.getElById(id).style)
+ * @method getCss
+ * @param {String} id the id of the elment to get
+ * @return {Object} The style property of the element
+ * @deprecated use Ext.lib.Dom instead
+ * @static
+ */
+ getCss: function(id) {
+ var el = Ext.getDom(id);
+ return (el) ? el.style : null;
+ },
+
+ /**
+ * Inner class for cached elements
+ * @class Ext.dd.DragDropMgr.ElementWrapper
+ * @for DragDropMgr
+ * @private
+ * @deprecated
+ */
+ ElementWrapper: function(el) {
+ /**
+ * The element
+ * @property el
+ */
+ this.el = el || null;
+ /**
+ * The element id
+ * @property id
+ */
+ this.id = this.el && el.id;
+ /**
+ * A reference to the style property
+ * @property css
+ */
+ this.css = this.el && el.style;
+ },
+
+ /**
+ * Returns the X position of an html element
+ * @method getPosX
+ * @param el the element for which to get the position
+ * @return {int} the X coordinate
+ * @for DragDropMgr
+ * @deprecated use Ext.lib.Dom.getX instead
+ * @static
+ */
+ getPosX: function(el) {
+ return Ext.lib.Dom.getX(el);
+ },
+
+ /**
+ * Returns the Y position of an html element
+ * @method getPosY
+ * @param el the element for which to get the position
+ * @return {int} the Y coordinate
+ * @deprecated use Ext.lib.Dom.getY instead
+ * @static
+ */
+ getPosY: function(el) {
+ return Ext.lib.Dom.getY(el);
+ },
+
+ /**
+ * Swap two nodes. In IE, we use the native method, for others we
+ * emulate the IE behavior
+ * @method swapNode
+ * @param n1 the first node to swap
+ * @param n2 the other node to swap
+ * @static
+ */
+ swapNode: function(n1, n2) {
+ if (n1.swapNode) {
+ n1.swapNode(n2);
+ } else {
+ var p = n2.parentNode;
+ var s = n2.nextSibling;
+
+ if (s == n1) {
+ p.insertBefore(n1, n2);
+ } else if (n2 == n1.nextSibling) {
+ p.insertBefore(n2, n1);
+ } else {
+ n1.parentNode.replaceChild(n2, n1);
+ p.insertBefore(n1, s);
+ }
+ }
+ },
+
+ /**
+ * Returns the current scroll position
+ * @method getScroll
+ * @private
+ * @static
+ */
+ getScroll: function () {
+ var t, l, dde=document.documentElement, db=document.body;
+ if (dde && (dde.scrollTop || dde.scrollLeft)) {
+ t = dde.scrollTop;
+ l = dde.scrollLeft;
+ } else if (db) {
+ t = db.scrollTop;
+ l = db.scrollLeft;
+ } else {
+
+ }
+ return { top: t, left: l };
+ },
+
+ /**
+ * Returns the specified element style property
+ * @method getStyle
+ * @param {HTMLElement} el the element
+ * @param {string} styleProp the style property
+ * @return {string} The value of the style property
+ * @deprecated use Ext.lib.Dom.getStyle
+ * @static
+ */
+ getStyle: function(el, styleProp) {
+ return Ext.fly(el).getStyle(styleProp);
+ },
+
+ /**
+ * Gets the scrollTop
+ * @method getScrollTop
+ * @return {int} the document's scrollTop
+ * @static
+ */
+ getScrollTop: function () {
+ return this.getScroll().top;
+ },
+
+ /**
+ * Gets the scrollLeft
+ * @method getScrollLeft
+ * @return {int} the document's scrollTop
+ * @static
+ */
+ getScrollLeft: function () {
+ return this.getScroll().left;
+ },
+
+ /**
+ * Sets the x/y position of an element to the location of the
+ * target element.
+ * @method moveToEl
+ * @param {HTMLElement} moveEl The element to move
+ * @param {HTMLElement} targetEl The position reference element
+ * @static
+ */
+ moveToEl: function (moveEl, targetEl) {
+ var aCoord = Ext.lib.Dom.getXY(targetEl);
+ Ext.lib.Dom.setXY(moveEl, aCoord);
+ },
+
+ /**
+ * Numeric array sort function
+ * @method numericSort
+ * @static
+ */
+ numericSort: function(a, b) {
+ return (a - b);
+ },
+
+ /**
+ * Internal counter
+ * @property _timeoutCount
+ * @private
+ * @static
+ */
+ _timeoutCount: 0,
+
+ /**
+ * Trying to make the load order less important. Without this we get
+ * an error if this file is loaded before the Event Utility.
+ * @method _addListeners
+ * @private
+ * @static
+ */
+ _addListeners: function() {
+ var DDM = Ext.dd.DDM;
+ if ( Ext.lib.Event && document ) {
+ DDM._onLoad();
+ } else {
+ if (DDM._timeoutCount > 2000) {
+ } else {
+ setTimeout(DDM._addListeners, 10);
+ if (document && document.body) {
+ DDM._timeoutCount += 1;
+ }
+ }
+ }
+ },
+
+ /**
+ * Recursively searches the immediate parent and all child nodes for
+ * the handle element in order to determine wheter or not it was
+ * clicked.
+ * @method handleWasClicked
+ * @param node the html element to inspect
+ * @static
+ */
+ handleWasClicked: function(node, id) {
+ if (this.isHandle(id, node.id)) {
+ return true;
+ } else {
+ // check to see if this is a text node child of the one we want
+ var p = node.parentNode;
+
+ while (p) {
+ if (this.isHandle(id, p.id)) {
+ return true;
+ } else {
+ p = p.parentNode;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ };
+
+}();
+
+// shorter alias, save a few bytes
+Ext.dd.DDM = Ext.dd.DragDropMgr;
+Ext.dd.DDM._addListeners();
+
+}
+
+/**
+ * @class Ext.dd.DD
+ * A DragDrop implementation where the linked element follows the
+ * mouse cursor during a drag.
+ * @extends Ext.dd.DragDrop
+ * @constructor
+ * @param {String} id the id of the linked element
+ * @param {String} sGroup the group of related DragDrop items
+ * @param {object} config an object containing configurable attributes
+ * Valid properties for DD:
+ * scroll
+ */
+Ext.dd.DD = function(id, sGroup, config) {
+ if (id) {
+ this.init(id, sGroup, config);
+ }
+};
+
+Ext.extend(Ext.dd.DD, Ext.dd.DragDrop, {
+
+ /**
+ * When set to true, the utility automatically tries to scroll the browser
+ * window when a drag and drop element is dragged near the viewport boundary.
+ * Defaults to true.
+ * @property scroll
+ * @type boolean
+ */
+ scroll: true,
+
+ /**
+ * Sets the pointer offset to the distance between the linked element's top
+ * left corner and the location the element was clicked
+ * @method autoOffset
+ * @param {int} iPageX the X coordinate of the click
+ * @param {int} iPageY the Y coordinate of the click
+ */
+ autoOffset: function(iPageX, iPageY) {
+ var x = iPageX - this.startPageX;
+ var y = iPageY - this.startPageY;
+ this.setDelta(x, y);
+ },
+
+ /**
+ * Sets the pointer offset. You can call this directly to force the
+ * offset to be in a particular location (e.g., pass in 0,0 to set it
+ * to the center of the object)
+ * @method setDelta
+ * @param {int} iDeltaX the distance from the left
+ * @param {int} iDeltaY the distance from the top
+ */
+ setDelta: function(iDeltaX, iDeltaY) {
+ this.deltaX = iDeltaX;
+ this.deltaY = iDeltaY;
+ },
+
+ /**
+ * Sets the drag element to the location of the mousedown or click event,
+ * maintaining the cursor location relative to the location on the element
+ * that was clicked. Override this if you want to place the element in a
+ * location other than where the cursor is.
+ * @method setDragElPos
+ * @param {int} iPageX the X coordinate of the mousedown or drag event
+ * @param {int} iPageY the Y coordinate of the mousedown or drag event
+ */
+ setDragElPos: function(iPageX, iPageY) {
+ // the first time we do this, we are going to check to make sure
+ // the element has css positioning
+
+ var el = this.getDragEl();
+ this.alignElWithMouse(el, iPageX, iPageY);
+ },
+
+ /**
+ * Sets the element to the location of the mousedown or click event,
+ * maintaining the cursor location relative to the location on the element
+ * that was clicked. Override this if you want to place the element in a
+ * location other than where the cursor is.
+ * @method alignElWithMouse
+ * @param {HTMLElement} el the element to move
+ * @param {int} iPageX the X coordinate of the mousedown or drag event
+ * @param {int} iPageY the Y coordinate of the mousedown or drag event
+ */
+ alignElWithMouse: function(el, iPageX, iPageY) {
+ var oCoord = this.getTargetCoord(iPageX, iPageY);
+ var fly = el.dom ? el : Ext.fly(el, '_dd');
+ if (!this.deltaSetXY) {
+ var aCoord = [oCoord.x, oCoord.y];
+ fly.setXY(aCoord);
+ var newLeft = fly.getLeft(true);
+ var newTop = fly.getTop(true);
+ this.deltaSetXY = [ newLeft - oCoord.x, newTop - oCoord.y ];
+ } else {
+ fly.setLeftTop(oCoord.x + this.deltaSetXY[0], oCoord.y + this.deltaSetXY[1]);
+ }
+
+ this.cachePosition(oCoord.x, oCoord.y);
+ this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
+ return oCoord;
+ },
+
+ /**
+ * Saves the most recent position so that we can reset the constraints and
+ * tick marks on-demand. We need to know this so that we can calculate the
+ * number of pixels the element is offset from its original position.
+ * @method cachePosition
+ * @param iPageX the current x position (optional, this just makes it so we
+ * don't have to look it up again)
+ * @param iPageY the current y position (optional, this just makes it so we
+ * don't have to look it up again)
+ */
+ cachePosition: function(iPageX, iPageY) {
+ if (iPageX) {
+ this.lastPageX = iPageX;
+ this.lastPageY = iPageY;
+ } else {
+ var aCoord = Ext.lib.Dom.getXY(this.getEl());
+ this.lastPageX = aCoord[0];
+ this.lastPageY = aCoord[1];
+ }
+ },
+
+ /**
+ * Auto-scroll the window if the dragged object has been moved beyond the
+ * visible window boundary.
+ * @method autoScroll
+ * @param {int} x the drag element's x position
+ * @param {int} y the drag element's y position
+ * @param {int} h the height of the drag element
+ * @param {int} w the width of the drag element
+ * @private
+ */
+ autoScroll: function(x, y, h, w) {
+
+ if (this.scroll) {
+ // The client height
+ var clientH = Ext.lib.Dom.getViewHeight();
+
+ // The client width
+ var clientW = Ext.lib.Dom.getViewWidth();
+
+ // The amt scrolled down
+ var st = this.DDM.getScrollTop();
+
+ // The amt scrolled right
+ var sl = this.DDM.getScrollLeft();
+
+ // Location of the bottom of the element
+ var bot = h + y;
+
+ // Location of the right of the element
+ var right = w + x;
+
+ // The distance from the cursor to the bottom of the visible area,
+ // adjusted so that we don't scroll if the cursor is beyond the
+ // element drag constraints
+ var toBot = (clientH + st - y - this.deltaY);
+
+ // The distance from the cursor to the right of the visible area
+ var toRight = (clientW + sl - x - this.deltaX);
+
+
+ // How close to the edge the cursor must be before we scroll
+ // var thresh = (document.all) ? 100 : 40;
+ var thresh = 40;
+
+ // How many pixels to scroll per autoscroll op. This helps to reduce
+ // clunky scrolling. IE is more sensitive about this ... it needs this
+ // value to be higher.
+ var scrAmt = (document.all) ? 80 : 30;
+
+ // Scroll down if we are near the bottom of the visible page and the
+ // obj extends below the crease
+ if ( bot > clientH && toBot < thresh ) {
+ window.scrollTo(sl, st + scrAmt);
+ }
+
+ // Scroll up if the window is scrolled down and the top of the object
+ // goes above the top border
+ if ( y < st && st > 0 && y - st < thresh ) {
+ window.scrollTo(sl, st - scrAmt);
+ }
+
+ // Scroll right if the obj is beyond the right border and the cursor is
+ // near the border.
+ if ( right > clientW && toRight < thresh ) {
+ window.scrollTo(sl + scrAmt, st);
+ }
+
+ // Scroll left if the window has been scrolled to the right and the obj
+ // extends past the left border
+ if ( x < sl && sl > 0 && x - sl < thresh ) {
+ window.scrollTo(sl - scrAmt, st);
+ }
+ }
+ },
+
+ /**
+ * Finds the location the element should be placed if we want to move
+ * it to where the mouse location less the click offset would place us.
+ * @method getTargetCoord
+ * @param {int} iPageX the X coordinate of the click
+ * @param {int} iPageY the Y coordinate of the click
+ * @return an object that contains the coordinates (Object.x and Object.y)
+ * @private
+ */
+ getTargetCoord: function(iPageX, iPageY) {
+ var x = iPageX - this.deltaX;
+ var y = iPageY - this.deltaY;
+
+ if (this.constrainX) {
+ if (x < this.minX) { x = this.minX; }
+ if (x > this.maxX) { x = this.maxX; }
+ }
+
+ if (this.constrainY) {
+ if (y < this.minY) { y = this.minY; }
+ if (y > this.maxY) { y = this.maxY; }
+ }
+
+ x = this.getTick(x, this.xTicks);
+ y = this.getTick(y, this.yTicks);
+
+
+ return {x:x, y:y};
+ },
+
+ /**
+ * Sets up config options specific to this class. Overrides
+ * Ext.dd.DragDrop, but all versions of this method through the
+ * inheritance chain are called
+ */
+ applyConfig: function() {
+ Ext.dd.DD.superclass.applyConfig.call(this);
+ this.scroll = (this.config.scroll !== false);
+ },
+
+ /**
+ * Event that fires prior to the onMouseDown event. Overrides
+ * Ext.dd.DragDrop.
+ */
+ b4MouseDown: function(e) {
+ // this.resetConstraints();
+ this.autoOffset(e.getPageX(),
+ e.getPageY());
+ },
+
+ /**
+ * Event that fires prior to the onDrag event. Overrides
+ * Ext.dd.DragDrop.
+ */
+ b4Drag: function(e) {
+ this.setDragElPos(e.getPageX(),
+ e.getPageY());
+ },
+
+ toString: function() {
+ return ("DD " + this.id);
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Debugging ygDragDrop events that can be overridden
+ //////////////////////////////////////////////////////////////////////////
+ /*
+ startDrag: function(x, y) {
+ },
+
+ onDrag: function(e) {
+ },
+
+ onDragEnter: function(e, id) {
+ },
+
+ onDragOver: function(e, id) {
+ },
+
+ onDragOut: function(e, id) {
+ },
+
+ onDragDrop: function(e, id) {
+ },
+
+ endDrag: function(e) {
+ }
+
+ */
+
+});
+/**
+ * @class Ext.dd.DDProxy
+ * A DragDrop implementation that inserts an empty, bordered div into
+ * the document that follows the cursor during drag operations. At the time of
+ * the click, the frame div is resized to the dimensions of the linked html
+ * element, and moved to the exact location of the linked element.
+ *
+ * References to the "frame" element refer to the single proxy element that
+ * was created to be dragged in place of all DDProxy elements on the
+ * page.
+ *
+ * @extends Ext.dd.DD
+ * @constructor
+ * @param {String} id the id of the linked html element
+ * @param {String} sGroup the group of related DragDrop objects
+ * @param {object} config an object containing configurable attributes
+ * Valid properties for DDProxy in addition to those in DragDrop:
+ * resizeFrame, centerFrame, dragElId
+ */
+Ext.dd.DDProxy = function(id, sGroup, config) {
+ if (id) {
+ this.init(id, sGroup, config);
+ this.initFrame();
+ }
+};
+
+/**
+ * The default drag frame div id
+ * @property Ext.dd.DDProxy.dragElId
+ * @type String
+ * @static
+ */
+Ext.dd.DDProxy.dragElId = "ygddfdiv";
+
+Ext.extend(Ext.dd.DDProxy, Ext.dd.DD, {
+
+ /**
+ * By default we resize the drag frame to be the same size as the element
+ * we want to drag (this is to get the frame effect). We can turn it off
+ * if we want a different behavior.
+ * @property resizeFrame
+ * @type boolean
+ */
+ resizeFrame: true,
+
+ /**
+ * By default the frame is positioned exactly where the drag element is, so
+ * we use the cursor offset provided by Ext.dd.DD. Another option that works only if
+ * you do not have constraints on the obj is to have the drag frame centered
+ * around the cursor. Set centerFrame to true for this effect.
+ * @property centerFrame
+ * @type boolean
+ */
+ centerFrame: false,
+
+ /**
+ * Creates the proxy element if it does not yet exist
+ * @method createFrame
+ */
+ createFrame: function() {
+ var self = this;
+ var body = document.body;
+
+ if (!body || !body.firstChild) {
+ setTimeout( function() { self.createFrame(); }, 50 );
+ return;
+ }
+
+ var div = this.getDragEl();
+
+ if (!div) {
+ div = document.createElement("div");
+ div.id = this.dragElId;
+ var s = div.style;
+
+ s.position = "absolute";
+ s.visibility = "hidden";
+ s.cursor = "move";
+ s.border = "2px solid #aaa";
+ s.zIndex = 999;
+
+ // appendChild can blow up IE if invoked prior to the window load event
+ // while rendering a table. It is possible there are other scenarios
+ // that would cause this to happen as well.
+ body.insertBefore(div, body.firstChild);
+ }
+ },
+
+ /**
+ * Initialization for the drag frame element. Must be called in the
+ * constructor of all subclasses
+ * @method initFrame
+ */
+ initFrame: function() {
+ this.createFrame();
+ },
+
+ applyConfig: function() {
+ Ext.dd.DDProxy.superclass.applyConfig.call(this);
+
+ this.resizeFrame = (this.config.resizeFrame !== false);
+ this.centerFrame = (this.config.centerFrame);
+ this.setDragElId(this.config.dragElId || Ext.dd.DDProxy.dragElId);
+ },
+
+ /**
+ * Resizes the drag frame to the dimensions of the clicked object, positions
+ * it over the object, and finally displays it
+ * @method showFrame
+ * @param {int} iPageX X click position
+ * @param {int} iPageY Y click position
+ * @private
+ */
+ showFrame: function(iPageX, iPageY) {
+ var el = this.getEl();
+ var dragEl = this.getDragEl();
+ var s = dragEl.style;
+
+ this._resizeProxy();
+
+ if (this.centerFrame) {
+ this.setDelta( Math.round(parseInt(s.width, 10)/2),
+ Math.round(parseInt(s.height, 10)/2) );
+ }
+
+ this.setDragElPos(iPageX, iPageY);
+
+ Ext.fly(dragEl).show();
+ },
+
+ /**
+ * The proxy is automatically resized to the dimensions of the linked
+ * element when a drag is initiated, unless resizeFrame is set to false
+ * @method _resizeProxy
+ * @private
+ */
+ _resizeProxy: function() {
+ if (this.resizeFrame) {
+ var el = this.getEl();
+ Ext.fly(this.getDragEl()).setSize(el.offsetWidth, el.offsetHeight);
+ }
+ },
+
+ // overrides Ext.dd.DragDrop
+ b4MouseDown: function(e) {
+ var x = e.getPageX();
+ var y = e.getPageY();
+ this.autoOffset(x, y);
+ this.setDragElPos(x, y);
+ },
+
+ // overrides Ext.dd.DragDrop
+ b4StartDrag: function(x, y) {
+ // show the drag frame
+ this.showFrame(x, y);
+ },
+
+ // overrides Ext.dd.DragDrop
+ b4EndDrag: function(e) {
+ Ext.fly(this.getDragEl()).hide();
+ },
+
+ // overrides Ext.dd.DragDrop
+ // By default we try to move the element to the last location of the frame.
+ // This is so that the default behavior mirrors that of Ext.dd.DD.
+ endDrag: function(e) {
+
+ var lel = this.getEl();
+ var del = this.getDragEl();
+
+ // Show the drag frame briefly so we can get its position
+ del.style.visibility = "";
+
+ this.beforeMove();
+ // Hide the linked element before the move to get around a Safari
+ // rendering bug.
+ lel.style.visibility = "hidden";
+ Ext.dd.DDM.moveToEl(lel, del);
+ del.style.visibility = "hidden";
+ lel.style.visibility = "";
+
+ this.afterDrag();
+ },
+
+ beforeMove : function(){
+
+ },
+
+ afterDrag : function(){
+
+ },
+
+ toString: function() {
+ return ("DDProxy " + this.id);
+ }
+
+});
+/**
+ * @class Ext.dd.DDTarget
+ * A DragDrop implementation that does not move, but can be a drop
+ * target. You would get the same result by simply omitting implementation
+ * for the event callbacks, but this way we reduce the processing cost of the
+ * event listener and the callbacks.
+ * @extends Ext.dd.DragDrop
+ * @constructor
+ * @param {String} id the id of the element that is a drop target
+ * @param {String} sGroup the group of related DragDrop objects
+ * @param {object} config an object containing configurable attributes
+ * Valid properties for DDTarget in addition to those in
+ * DragDrop:
+ * none
+ */
+Ext.dd.DDTarget = function(id, sGroup, config) {
+ if (id) {
+ this.initTarget(id, sGroup, config);
+ }
+};
+
+// Ext.dd.DDTarget.prototype = new Ext.dd.DragDrop();
+Ext.extend(Ext.dd.DDTarget, Ext.dd.DragDrop, {
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ getDragEl: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ isValidHandleChild: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ startDrag: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ endDrag: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ onDrag: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ onDragDrop: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ onDragEnter: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ onDragOut: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ onDragOver: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ onInvalidDrop: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ onMouseDown: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ onMouseUp: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ setXConstraint: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ setYConstraint: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ resetConstraints: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ clearConstraints: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ clearTicks: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ setInitPosition: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ setDragElId: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ setHandleElId: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ setOuterHandleElId: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ addInvalidHandleClass: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ addInvalidHandleId: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ addInvalidHandleType: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ removeInvalidHandleClass: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ removeInvalidHandleId: Ext.emptyFn,
+ /**
+ * @hide
+ * Overridden and disabled. A DDTarget does not support being dragged.
+ * @method
+ */
+ removeInvalidHandleType: Ext.emptyFn,
+
+ toString: function() {
+ return ("DDTarget " + this.id);
+ }
+});/**
+ * @class Ext.dd.DragTracker
+ * @extends Ext.util.Observable
+ * A DragTracker listens for drag events on an Element and fires events at the start and end of the drag,
+ * as well as during the drag. This is useful for components such as {@link Ext.Slider}, where there is
+ * an element that can be dragged around to change the Slider's value.
+ * DragTracker provides a series of template methods that should be overridden to provide functionality
+ * in response to detected drag operations. These are onBeforeStart, onStart, onDrag and onEnd.
+ * See {@link Ext.Slider}'s initEvents function for an example implementation.
+ */
+Ext.dd.DragTracker = Ext.extend(Ext.util.Observable, {
+ /**
+ * @cfg {Boolean} active
+ * Defaults to <tt>false</tt>.
+ */
+ active: false,
+ /**
+ * @cfg {Number} tolerance
+ * Number of pixels the drag target must be moved before dragging is considered to have started. Defaults to <tt>5</tt>.
+ */
+ tolerance: 5,
+ /**
+ * @cfg {Boolean/Number} autoStart
+ * Defaults to <tt>false</tt>. Specify <tt>true</tt> to defer trigger start by 1000 ms.
+ * Specify a Number for the number of milliseconds to defer trigger start.
+ */
+ autoStart: false,
+
+ constructor : function(config){
+ Ext.apply(this, config);
+ this.addEvents(
+ /**
+ * @event mousedown
+ * @param {Object} this
+ * @param {Object} e event object
+ */
+ 'mousedown',
+ /**
+ * @event mouseup
+ * @param {Object} this
+ * @param {Object} e event object
+ */
+ 'mouseup',
+ /**
+ * @event mousemove
+ * @param {Object} this
+ * @param {Object} e event object
+ */
+ 'mousemove',
+ /**
+ * @event dragstart
+ * @param {Object} this
+ * @param {Object} startXY the page coordinates of the event
+ */
+ 'dragstart',
+ /**
+ * @event dragend
+ * @param {Object} this
+ * @param {Object} e event object
+ */
+ 'dragend',
+ /**
+ * @event drag
+ * @param {Object} this
+ * @param {Object} e event object
+ */
+ 'drag'
+ );
+
+ this.dragRegion = new Ext.lib.Region(0,0,0,0);
+
+ if(this.el){
+ this.initEl(this.el);
+ }
+ Ext.dd.DragTracker.superclass.constructor.call(this, config);
+ },
+
+ initEl: function(el){
+ this.el = Ext.get(el);
+ el.on('mousedown', this.onMouseDown, this,
+ this.delegate ? {delegate: this.delegate} : undefined);
+ },
+
+ destroy : function(){
+ this.el.un('mousedown', this.onMouseDown, this);
+ },
+
+ onMouseDown: function(e, target){
+ if(this.fireEvent('mousedown', this, e) !== false && this.onBeforeStart(e) !== false){
+ this.startXY = this.lastXY = e.getXY();
+ this.dragTarget = this.delegate ? target : this.el.dom;
+ if(this.preventDefault !== false){
+ e.preventDefault();
+ }
+ var doc = Ext.getDoc();
+ doc.on('mouseup', this.onMouseUp, this);
+ doc.on('mousemove', this.onMouseMove, this);
+ doc.on('selectstart', this.stopSelect, this);
+ if(this.autoStart){
+ this.timer = this.triggerStart.defer(this.autoStart === true ? 1000 : this.autoStart, this);
+ }
+ }
+ },
+
+ onMouseMove: function(e, target){
+ // HACK: IE hack to see if button was released outside of window. */
+ if(this.active && Ext.isIE && !e.browserEvent.button){
+ e.preventDefault();
+ this.onMouseUp(e);
+ return;
+ }
+
+ e.preventDefault();
+ var xy = e.getXY(), s = this.startXY;
+ this.lastXY = xy;
+ if(!this.active){
+ if(Math.abs(s[0]-xy[0]) > this.tolerance || Math.abs(s[1]-xy[1]) > this.tolerance){
+ this.triggerStart();
+ }else{
+ return;
+ }
+ }
+ this.fireEvent('mousemove', this, e);
+ this.onDrag(e);
+ this.fireEvent('drag', this, e);
+ },
+
+ onMouseUp: function(e) {
+ var doc = Ext.getDoc();
+ doc.un('mousemove', this.onMouseMove, this);
+ doc.un('mouseup', this.onMouseUp, this);
+ doc.un('selectstart', this.stopSelect, this);
+ e.preventDefault();
+ this.clearStart();
+ var wasActive = this.active;
+ this.active = false;
+ delete this.elRegion;
+ this.fireEvent('mouseup', this, e);
+ if(wasActive){
+ this.onEnd(e);
+ this.fireEvent('dragend', this, e);
+ }
+ },
+
+ triggerStart: function(isTimer) {
+ this.clearStart();
+ this.active = true;
+ this.onStart(this.startXY);
+ this.fireEvent('dragstart', this, this.startXY);
+ },
+
+ clearStart : function() {
+ if(this.timer){
+ clearTimeout(this.timer);
+ delete this.timer;
+ }
+ },
+
+ stopSelect : function(e) {
+ e.stopEvent();
+ return false;
+ },
+
+ /**
+ * Template method which should be overridden by each DragTracker instance. Called when the user first clicks and
+ * holds the mouse button down. Return false to disallow the drag
+ * @param {Ext.EventObject} e The event object
+ */
+ onBeforeStart : function(e) {
+
+ },
+
+ /**
+ * Template method which should be overridden by each DragTracker instance. Called when a drag operation starts
+ * (e.g. the user has moved the tracked element beyond the specified tolerance)
+ * @param {Array} xy x and y co-ordinates of the original location of the tracked element
+ */
+ onStart : function(xy) {
+
+ },
+
+ /**
+ * Template method which should be overridden by each DragTracker instance. Called whenever a drag has been detected.
+ * @param {Ext.EventObject} e The event object
+ */
+ onDrag : function(e) {
+
+ },
+
+ /**
+ * Template method which should be overridden by each DragTracker instance. Called when a drag operation has been completed
+ * (e.g. the user clicked and held the mouse down, dragged the element and then released the mouse button)
+ * @param {Ext.EventObject} e The event object
+ */
+ onEnd : function(e) {
+
+ },
+
+ /**
+ * Returns the drag target
+ * @return {Ext.Element} The element currently being tracked
+ */
+ getDragTarget : function(){
+ return this.dragTarget;
+ },
+
+ getDragCt : function(){
+ return this.el;
+ },
+
+ getXY : function(constrain){
+ return constrain ?
+ this.constrainModes[constrain].call(this, this.lastXY) : this.lastXY;
+ },
+
+ getOffset : function(constrain){
+ var xy = this.getXY(constrain);
+ var s = this.startXY;
+ return [s[0]-xy[0], s[1]-xy[1]];
+ },
+
+ constrainModes: {
+ 'point' : function(xy){
+
+ if(!this.elRegion){
+ this.elRegion = this.getDragCt().getRegion();
+ }
+
+ var dr = this.dragRegion;
+
+ dr.left = xy[0];
+ dr.top = xy[1];
+ dr.right = xy[0];
+ dr.bottom = xy[1];
+
+ dr.constrainTo(this.elRegion);
+
+ return [dr.left, dr.top];
+ }
+ }
+});/**
+ * @class Ext.dd.ScrollManager
+ * <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
+ * <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
+ * but you can also override most of the configs per scroll container by adding a
+ * <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
+ * {@link #vthresh}, {@link #increment} and {@link #frequency}. Example usage:
+ * <pre><code>
+var el = Ext.get('scroll-ct');
+el.ddScrollConfig = {
+ vthresh: 50,
+ hthresh: -1,
+ frequency: 100,
+ increment: 200
+};
+Ext.dd.ScrollManager.register(el);
+</code></pre>
+ * <b>Note: This class uses "Point Mode" and is untested in "Intersect Mode".</b>
+ * @singleton
+ */
+Ext.dd.ScrollManager = function(){
+ var ddm = Ext.dd.DragDropMgr;
+ var els = {};
+ var dragEl = null;
+ var proc = {};
+
+ var onStop = function(e){
+ dragEl = null;
+ clearProc();
+ };
+
+ var triggerRefresh = function(){
+ if(ddm.dragCurrent){
+ ddm.refreshCache(ddm.dragCurrent.groups);
+ }
+ };
+
+ var doScroll = function(){
+ if(ddm.dragCurrent){
+ var dds = Ext.dd.ScrollManager;
+ var inc = proc.el.ddScrollConfig ?
+ proc.el.ddScrollConfig.increment : dds.increment;
+ if(!dds.animate){
+ if(proc.el.scroll(proc.dir, inc)){
+ triggerRefresh();
+ }
+ }else{
+ proc.el.scroll(proc.dir, inc, true, dds.animDuration, triggerRefresh);
+ }
+ }
+ };
+
+ var clearProc = function(){
+ if(proc.id){
+ clearInterval(proc.id);
+ }
+ proc.id = 0;
+ proc.el = null;
+ proc.dir = "";
+ };
+
+ var startProc = function(el, dir){
+ clearProc();
+ proc.el = el;
+ proc.dir = dir;
+ var freq = (el.ddScrollConfig && el.ddScrollConfig.frequency) ?
+ el.ddScrollConfig.frequency : Ext.dd.ScrollManager.frequency;
+ proc.id = setInterval(doScroll, freq);
+ };
+
+ var onFire = function(e, isDrop){
+ if(isDrop || !ddm.dragCurrent){ return; }
+ var dds = Ext.dd.ScrollManager;
+ if(!dragEl || dragEl != ddm.dragCurrent){
+ dragEl = ddm.dragCurrent;
+ // refresh regions on drag start
+ dds.refreshCache();
+ }
+
+ var xy = Ext.lib.Event.getXY(e);
+ var pt = new Ext.lib.Point(xy[0], xy[1]);
+ for(var id in els){
+ var el = els[id], r = el._region;
+ var c = el.ddScrollConfig ? el.ddScrollConfig : dds;
+ if(r && r.contains(pt) && el.isScrollable()){
+ if(r.bottom - pt.y <= c.vthresh){
+ if(proc.el != el){
+ startProc(el, "down");
+ }
+ return;
+ }else if(r.right - pt.x <= c.hthresh){
+ if(proc.el != el){
+ startProc(el, "left");
+ }
+ return;
+ }else if(pt.y - r.top <= c.vthresh){
+ if(proc.el != el){
+ startProc(el, "up");
+ }
+ return;
+ }else if(pt.x - r.left <= c.hthresh){
+ if(proc.el != el){
+ startProc(el, "right");
+ }
+ return;
+ }
+ }
+ }
+ clearProc();
+ };
+
+ ddm.fireEvents = ddm.fireEvents.createSequence(onFire, ddm);
+ ddm.stopDrag = ddm.stopDrag.createSequence(onStop, ddm);
+
+ return {
+ /**
+ * Registers new overflow element(s) to auto scroll
+ * @param {Mixed/Array} el The id of or the element to be scrolled or an array of either
+ */
+ register : function(el){
+ if(Ext.isArray(el)){
+ for(var i = 0, len = el.length; i < len; i++) {
+ this.register(el[i]);
+ }
+ }else{
+ el = Ext.get(el);
+ els[el.id] = el;
+ }
+ },
+
+ /**
+ * Unregisters overflow element(s) so they are no longer scrolled
+ * @param {Mixed/Array} el The id of or the element to be removed or an array of either
+ */
+ unregister : function(el){
+ if(Ext.isArray(el)){
+ for(var i = 0, len = el.length; i < len; i++) {
+ this.unregister(el[i]);
+ }
+ }else{
+ el = Ext.get(el);
+ delete els[el.id];
+ }
+ },
+
+ /**
+ * The number of pixels from the top or bottom edge of a container the pointer needs to be to
+ * trigger scrolling (defaults to 25)
+ * @type Number
+ */
+ vthresh : 25,
+ /**
+ * The number of pixels from the right or left edge of a container the pointer needs to be to
+ * trigger scrolling (defaults to 25)
+ * @type Number
+ */
+ hthresh : 25,
+
+ /**
+ * The number of pixels to scroll in each scroll increment (defaults to 50)
+ * @type Number
+ */
+ increment : 100,
+
+ /**
+ * The frequency of scrolls in milliseconds (defaults to 500)
+ * @type Number
+ */
+ frequency : 500,
+
+ /**
+ * True to animate the scroll (defaults to true)
+ * @type Boolean
+ */
+ animate: true,
+
+ /**
+ * The animation duration in seconds -
+ * MUST BE less than Ext.dd.ScrollManager.frequency! (defaults to .4)
+ * @type Number
+ */
+ animDuration: .4,
+
+ /**
+ * Manually trigger a cache refresh.
+ */
+ refreshCache : function(){
+ for(var id in els){
+ if(typeof els[id] == 'object'){ // for people extending the object prototype
+ els[id]._region = els[id].getRegion();
+ }
+ }
+ }
+ };
+}();/**
+ * @class Ext.dd.Registry
+ * Provides easy access to all drag drop components that are registered on a page. Items can be retrieved either
+ * directly by DOM node id, or by passing in the drag drop event that occurred and looking up the event target.
+ * @singleton
+ */
+Ext.dd.Registry = function(){
+ var elements = {};
+ var handles = {};
+ var autoIdSeed = 0;
+
+ var getId = function(el, autogen){
+ if(typeof el == "string"){
+ return el;
+ }
+ var id = el.id;
+ if(!id && autogen !== false){
+ id = "extdd-" + (++autoIdSeed);
+ el.id = id;
+ }
+ return id;
+ };
+
+ return {
+ /**
+ * Resgister a drag drop element
+ * @param {String/HTMLElement} element The id or DOM node to register
+ * @param {Object} data (optional) An custom data object that will be passed between the elements that are involved
+ * in drag drop operations. You can populate this object with any arbitrary properties that your own code
+ * knows how to interpret, plus there are some specific properties known to the Registry that should be
+ * populated in the data object (if applicable):
+ * <pre>
+Value Description<br />
+--------- ------------------------------------------<br />
+handles Array of DOM nodes that trigger dragging<br />
+ for the element being registered<br />
+isHandle True if the element passed in triggers<br />
+ dragging itself, else false
+</pre>
+ */
+ register : function(el, data){
+ data = data || {};
+ if(typeof el == "string"){
+ el = document.getElementById(el);
+ }
+ data.ddel = el;
+ elements[getId(el)] = data;
+ if(data.isHandle !== false){
+ handles[data.ddel.id] = data;
+ }
+ if(data.handles){
+ var hs = data.handles;
+ for(var i = 0, len = hs.length; i < len; i++){
+ handles[getId(hs[i])] = data;
+ }
+ }
+ },
+
+ /**
+ * Unregister a drag drop element
+ * @param {String/HTMLElement} element The id or DOM node to unregister
+ */
+ unregister : function(el){
+ var id = getId(el, false);
+ var data = elements[id];
+ if(data){
+ delete elements[id];
+ if(data.handles){
+ var hs = data.handles;
+ for(var i = 0, len = hs.length; i < len; i++){
+ delete handles[getId(hs[i], false)];
+ }
+ }
+ }
+ },
+
+ /**
+ * Returns the handle registered for a DOM Node by id
+ * @param {String/HTMLElement} id The DOM node or id to look up
+ * @return {Object} handle The custom handle data
+ */
+ getHandle : function(id){
+ if(typeof id != "string"){ // must be element?
+ id = id.id;
+ }
+ return handles[id];
+ },
+
+ /**
+ * Returns the handle that is registered for the DOM node that is the target of the event
+ * @param {Event} e The event
+ * @return {Object} handle The custom handle data
+ */
+ getHandleFromEvent : function(e){
+ var t = Ext.lib.Event.getTarget(e);
+ return t ? handles[t.id] : null;
+ },
+
+ /**
+ * Returns a custom data object that is registered for a DOM node by id
+ * @param {String/HTMLElement} id The DOM node or id to look up
+ * @return {Object} data The custom data
+ */
+ getTarget : function(id){
+ if(typeof id != "string"){ // must be element?
+ id = id.id;
+ }
+ return elements[id];
+ },
+
+ /**
+ * Returns a custom data object that is registered for the DOM node that is the target of the event
+ * @param {Event} e The event
+ * @return {Object} data The custom data
+ */
+ getTargetFromEvent : function(e){
+ var t = Ext.lib.Event.getTarget(e);
+ return t ? elements[t.id] || handles[t.id] : null;
+ }
+ };
+}();/**
+ * @class Ext.dd.StatusProxy
+ * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair. This is the
+ * default drag proxy used by all Ext.dd components.
+ * @constructor
+ * @param {Object} config
+ */
+Ext.dd.StatusProxy = function(config){
+ Ext.apply(this, config);
+ this.id = this.id || Ext.id();
+ this.el = new Ext.Layer({
+ dh: {
+ id: this.id, tag: "div", cls: "x-dd-drag-proxy "+this.dropNotAllowed, children: [
+ {tag: "div", cls: "x-dd-drop-icon"},
+ {tag: "div", cls: "x-dd-drag-ghost"}
+ ]
+ },
+ shadow: !config || config.shadow !== false
+ });
+ this.ghost = Ext.get(this.el.dom.childNodes[1]);
+ this.dropStatus = this.dropNotAllowed;
+};
+
+Ext.dd.StatusProxy.prototype = {
+ /**
+ * @cfg {String} dropAllowed
+ * The CSS class to apply to the status element when drop is allowed (defaults to "x-dd-drop-ok").
+ */
+ dropAllowed : "x-dd-drop-ok",
+ /**
+ * @cfg {String} dropNotAllowed
+ * The CSS class to apply to the status element when drop is not allowed (defaults to "x-dd-drop-nodrop").
+ */
+ dropNotAllowed : "x-dd-drop-nodrop",
+
+ /**
+ * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
+ * over the current target element.
+ * @param {String} cssClass The css class for the new drop status indicator image
+ */
+ setStatus : function(cssClass){
+ cssClass = cssClass || this.dropNotAllowed;
+ if(this.dropStatus != cssClass){
+ this.el.replaceClass(this.dropStatus, cssClass);
+ this.dropStatus = cssClass;
+ }
+ },
+
+ /**
+ * Resets the status indicator to the default dropNotAllowed value
+ * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
+ */
+ reset : function(clearGhost){
+ this.el.dom.className = "x-dd-drag-proxy " + this.dropNotAllowed;
+ this.dropStatus = this.dropNotAllowed;
+ if(clearGhost){
+ this.ghost.update("");
+ }
+ },
+
+ /**
+ * Updates the contents of the ghost element
+ * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
+ * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
+ */
+ update : function(html){
+ if(typeof html == "string"){
+ this.ghost.update(html);
+ }else{
+ this.ghost.update("");
+ html.style.margin = "0";
+ this.ghost.dom.appendChild(html);
+ }
+ var el = this.ghost.dom.firstChild;
+ if(el){
+ Ext.fly(el).setStyle('float', 'none');
+ }
+ },
+
+ /**
+ * Returns the underlying proxy {@link Ext.Layer}
+ * @return {Ext.Layer} el
+ */
+ getEl : function(){
+ return this.el;
+ },
+
+ /**
+ * Returns the ghost element
+ * @return {Ext.Element} el
+ */
+ getGhost : function(){
+ return this.ghost;
+ },
+
+ /**
+ * Hides the proxy
+ * @param {Boolean} clear True to reset the status and clear the ghost contents, false to preserve them
+ */
+ hide : function(clear){
+ this.el.hide();
+ if(clear){
+ this.reset(true);
+ }
+ },
+
+ /**
+ * Stops the repair animation if it's currently running
+ */
+ stop : function(){
+ if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
+ this.anim.stop();
+ }
+ },
+
+ /**
+ * Displays this proxy
+ */
+ show : function(){
+ this.el.show();
+ },
+
+ /**
+ * Force the Layer to sync its shadow and shim positions to the element
+ */
+ sync : function(){
+ this.el.sync();
+ },
+
+ /**
+ * Causes the proxy to return to its position of origin via an animation. Should be called after an
+ * invalid drop operation by the item being dragged.
+ * @param {Array} xy The XY position of the element ([x, y])
+ * @param {Function} callback The function to call after the repair is complete.
+ * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
+ */
+ repair : function(xy, callback, scope){
+ this.callback = callback;
+ this.scope = scope;
+ if(xy && this.animRepair !== false){
+ this.el.addClass("x-dd-drag-repair");
+ this.el.hideUnders(true);
+ this.anim = this.el.shift({
+ duration: this.repairDuration || .5,
+ easing: 'easeOut',
+ xy: xy,
+ stopFx: true,
+ callback: this.afterRepair,
+ scope: this
+ });
+ }else{
+ this.afterRepair();
+ }
+ },
+
+ // private
+ afterRepair : function(){
+ this.hide(true);
+ if(typeof this.callback == "function"){
+ this.callback.call(this.scope || this);
+ }
+ this.callback = null;
+ this.scope = null;
+ },
+
+ destroy: function(){
+ Ext.destroy(this.ghost, this.el);
+ }
+};/**
+ * @class Ext.dd.DragSource
+ * @extends Ext.dd.DDProxy
+ * A simple class that provides the basic implementation needed to make any element draggable.
+ * @constructor
+ * @param {Mixed} el The container element
+ * @param {Object} config
+ */
+Ext.dd.DragSource = function(el, config){
+ this.el = Ext.get(el);
+ if(!this.dragData){
+ this.dragData = {};
+ }
+
+ Ext.apply(this, config);
+
+ if(!this.proxy){
+ this.proxy = new Ext.dd.StatusProxy();
+ }
+ Ext.dd.DragSource.superclass.constructor.call(this, this.el.dom, this.ddGroup || this.group,
+ {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true});
+
+ this.dragging = false;
+};
+
+Ext.extend(Ext.dd.DragSource, Ext.dd.DDProxy, {
+ /**
+ * @cfg {String} ddGroup
+ * A named drag drop group to which this object belongs. If a group is specified, then this object will only
+ * interact with other drag drop objects in the same group (defaults to undefined).
+ */
+ /**
+ * @cfg {String} dropAllowed
+ * The CSS class returned to the drag source when drop is allowed (defaults to "x-dd-drop-ok").
+ */
+ dropAllowed : "x-dd-drop-ok",
+ /**
+ * @cfg {String} dropNotAllowed
+ * The CSS class returned to the drag source when drop is not allowed (defaults to "x-dd-drop-nodrop").
+ */
+ dropNotAllowed : "x-dd-drop-nodrop",
+
+ /**
+ * Returns the data object associated with this drag source
+ * @return {Object} data An object containing arbitrary data
+ */
+ getDragData : function(e){
+ return this.dragData;
+ },
+
+ // private
+ onDragEnter : function(e, id){
+ var target = Ext.dd.DragDropMgr.getDDById(id);
+ this.cachedTarget = target;
+ if(this.beforeDragEnter(target, e, id) !== false){
+ if(target.isNotifyTarget){
+ var status = target.notifyEnter(this, e, this.dragData);
+ this.proxy.setStatus(status);
+ }else{
+ this.proxy.setStatus(this.dropAllowed);
+ }