Upgrade to ExtJS 3.0.0 - Released 07/06/2009
[extjs.git] / examples / ux / ux-all-debug.js
diff --git a/examples/ux/ux-all-debug.js b/examples/ux/ux-all-debug.js
new file mode 100644 (file)
index 0000000..6ef28d4
--- /dev/null
@@ -0,0 +1,6251 @@
+/*!
+ * Ext JS Library 3.0.0
+ * Copyright(c) 2006-2009 Ext JS, LLC
+ * licensing@extjs.com
+ * http://www.extjs.com/license
+ */
+Ext.ns('Ext.ux.grid');
+
+/**
+ * @class Ext.ux.grid.BufferView
+ * @extends Ext.grid.GridView
+ * A custom GridView which renders rows on an as-needed basis.
+ */
+Ext.ux.grid.BufferView = Ext.extend(Ext.grid.GridView, {
+       /**
+        * @cfg {Number} rowHeight
+        * The height of a row in the grid.
+        */
+       rowHeight: 19,
+
+       /**
+        * @cfg {Number} borderHeight
+        * The combined height of border-top and border-bottom of a row.
+        */
+       borderHeight: 2,
+
+       /**
+        * @cfg {Boolean/Number} scrollDelay
+        * The number of milliseconds before rendering rows out of the visible
+        * viewing area. Defaults to 100. Rows will render immediately with a config
+        * of false.
+        */
+       scrollDelay: 100,
+
+       /**
+        * @cfg {Number} cacheSize
+        * The number of rows to look forward and backwards from the currently viewable
+        * area.  The cache applies only to rows that have been rendered already.
+        */
+       cacheSize: 20,
+
+       /**
+        * @cfg {Number} cleanDelay
+        * The number of milliseconds to buffer cleaning of extra rows not in the
+        * cache.
+        */
+       cleanDelay: 500,
+
+       initTemplates : function(){
+               Ext.ux.grid.BufferView.superclass.initTemplates.call(this);
+               var ts = this.templates;
+               // empty div to act as a place holder for a row
+               ts.rowHolder = new Ext.Template(
+                       '<div class="x-grid3-row {alt}" style="{tstyle}"></div>'
+               );
+               ts.rowHolder.disableFormats = true;
+               ts.rowHolder.compile();
+
+               ts.rowBody = new Ext.Template(
+                       '<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
+                       '<tbody><tr>{cells}</tr>',
+                       (this.enableRowBody ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>' : ''),
+                       '</tbody></table>'
+               );
+               ts.rowBody.disableFormats = true;
+               ts.rowBody.compile();
+       },
+
+       getStyleRowHeight : function(){
+               return Ext.isBorderBox ? (this.rowHeight + this.borderHeight) : this.rowHeight;
+       },
+
+       getCalculatedRowHeight : function(){
+               return this.rowHeight + this.borderHeight;
+       },
+
+       getVisibleRowCount : function(){
+               var rh = this.getCalculatedRowHeight();
+               var visibleHeight = this.scroller.dom.clientHeight;
+               return (visibleHeight < 1) ? 0 : Math.ceil(visibleHeight / rh);
+       },
+
+       getVisibleRows: function(){
+               var count = this.getVisibleRowCount();
+               var sc = this.scroller.dom.scrollTop;
+               var start = (sc == 0 ? 0 : Math.floor(sc/this.getCalculatedRowHeight())-1);
+               return {
+                       first: Math.max(start, 0),
+                       last: Math.min(start + count + 2, this.ds.getCount()-1)
+               };
+       },
+
+       doRender : function(cs, rs, ds, startRow, colCount, stripe, onlyBody){
+               var ts = this.templates, ct = ts.cell, rt = ts.row, rb = ts.rowBody, last = colCount-1;
+               var rh = this.getStyleRowHeight();
+               var vr = this.getVisibleRows();
+               var tstyle = 'width:'+this.getTotalWidth()+';height:'+rh+'px;';
+               // buffers
+               var buf = [], cb, c, p = {}, rp = {tstyle: tstyle}, r;
+               for (var j = 0, len = rs.length; j < len; j++) {
+                       r = rs[j]; cb = [];
+                       var rowIndex = (j+startRow);
+                       var visible = rowIndex >= vr.first && rowIndex <= vr.last;
+                       if (visible) {
+                               for (var i = 0; i < colCount; i++) {
+                                       c = cs[i];
+                                       p.id = c.id;
+                                       p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
+                                       p.attr = p.cellAttr = "";
+                                       p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
+                                       p.style = c.style;
+                                       if (p.value == undefined || p.value === "") {
+                                               p.value = "&#160;";
+                                       }
+                                       if (r.dirty && typeof r.modified[c.name] !== 'undefined') {
+                                               p.css += ' x-grid3-dirty-cell';
+                                       }
+                                       cb[cb.length] = ct.apply(p);
+                               }
+                       }
+                       var alt = [];
+                       if(stripe && ((rowIndex+1) % 2 == 0)){
+                           alt[0] = "x-grid3-row-alt";
+                       }
+                       if(r.dirty){
+                           alt[1] = " x-grid3-dirty-row";
+                       }
+                       rp.cols = colCount;
+                       if(this.getRowClass){
+                           alt[2] = this.getRowClass(r, rowIndex, rp, ds);
+                       }
+                       rp.alt = alt.join(" ");
+                       rp.cells = cb.join("");
+                       buf[buf.length] =  !visible ? ts.rowHolder.apply(rp) : (onlyBody ? rb.apply(rp) : rt.apply(rp));
+               }
+               return buf.join("");
+       },
+
+       isRowRendered: function(index){
+               var row = this.getRow(index);
+               return row && row.childNodes.length > 0;
+       },
+
+       syncScroll: function(){
+               Ext.ux.grid.BufferView.superclass.syncScroll.apply(this, arguments);
+               this.update();
+       },
+
+       // a (optionally) buffered method to update contents of gridview
+       update: function(){
+               if (this.scrollDelay) {
+                       if (!this.renderTask) {
+                               this.renderTask = new Ext.util.DelayedTask(this.doUpdate, this);
+                       }
+                       this.renderTask.delay(this.scrollDelay);
+               }else{
+                       this.doUpdate();
+               }
+       },
+
+       doUpdate: function(){
+               if (this.getVisibleRowCount() > 0) {
+                       var g = this.grid, cm = g.colModel, ds = g.store;
+                       var cs = this.getColumnData();
+
+                       var vr = this.getVisibleRows();
+                       for (var i = vr.first; i <= vr.last; i++) {
+                               // if row is NOT rendered and is visible, render it
+                               if(!this.isRowRendered(i)){
+                                       var html = this.doRender(cs, [ds.getAt(i)], ds, i, cm.getColumnCount(), g.stripeRows, true);
+                                       this.getRow(i).innerHTML = html;
+                               }
+                       }
+                       this.clean();
+               }
+       },
+
+       // a buffered method to clean rows
+       clean : function(){
+               if(!this.cleanTask){
+                       this.cleanTask = new Ext.util.DelayedTask(this.doClean, this);
+               }
+               this.cleanTask.delay(this.cleanDelay);
+       },
+
+       doClean: function(){
+               if (this.getVisibleRowCount() > 0) {
+                       var vr = this.getVisibleRows();
+                       vr.first -= this.cacheSize;
+                       vr.last += this.cacheSize;
+
+                       var i = 0, rows = this.getRows();
+                       // if first is less than 0, all rows have been rendered
+                       // so lets clean the end...
+                       if(vr.first <= 0){
+                               i = vr.last + 1;
+                       }
+                       for(var len = this.ds.getCount(); i < len; i++){
+                               // if current row is outside of first and last and
+                               // has content, update the innerHTML to nothing
+                               if ((i < vr.first || i > vr.last) && rows[i].innerHTML) {
+                                       rows[i].innerHTML = '';
+                               }
+                       }
+               }
+       },
+
+       layout: function(){
+               Ext.ux.grid.BufferView.superclass.layout.call(this);
+               this.update();
+       }
+});
+// We are adding these custom layouts to a namespace that does not
+// exist by default in Ext, so we have to add the namespace first:
+Ext.ns('Ext.ux.layout');
+
+/**
+ * @class Ext.ux.layout.CenterLayout
+ * @extends Ext.layout.FitLayout
+ * <p>This is a very simple layout style used to center contents within a container.  This layout works within
+ * nested containers and can also be used as expected as a Viewport layout to center the page layout.</p>
+ * <p>As a subclass of FitLayout, CenterLayout expects to have a single child panel of the container that uses
+ * the layout.  The layout does not require any config options, although the child panel contained within the
+ * layout must provide a fixed or percentage width.  The child panel's height will fit to the container by
+ * default, but you can specify <tt>autoHeight:true</tt> to allow it to autosize based on its content height.
+ * Example usage:</p>
+ * <pre><code>
+// The content panel is centered in the container
+var p = new Ext.Panel({
+    title: 'Center Layout',
+    layout: 'ux.center',
+    items: [{
+        title: 'Centered Content',
+        width: '75%',
+        html: 'Some content'
+    }]
+});
+
+// If you leave the title blank and specify no border
+// you'll create a non-visual, structural panel just
+// for centering the contents in the main container.
+var p = new Ext.Panel({
+    layout: 'ux.center',
+    border: false,
+    items: [{
+        title: 'Centered Content',
+        width: 300,
+        autoHeight: true,
+        html: 'Some content'
+    }]
+});
+</code></pre>
+ */
+Ext.ux.layout.CenterLayout = Ext.extend(Ext.layout.FitLayout, {
+       // private
+    setItemSize : function(item, size){
+        this.container.addClass('ux-layout-center');
+        item.addClass('ux-layout-center-item');
+        if(item && size.height > 0){
+            if(item.width){
+                size.width = item.width;
+            }
+            item.setSize(size);
+        }
+    }
+});
+
+Ext.Container.LAYOUTS['ux.center'] = Ext.ux.layout.CenterLayout;
+Ext.ns('Ext.ux.grid');\r
+\r
+/**\r
+ * @class Ext.ux.grid.CheckColumn\r
+ * @extends Object\r
+ * GridPanel plugin to add a column with check boxes to a grid.\r
+ * <p>Example usage:</p>\r
+ * <pre><code>\r
+// create the column\r
+var checkColumn = new Ext.grid.CheckColumn({\r
+   header: 'Indoor?',\r
+   dataIndex: 'indoor',\r
+   id: 'check',\r
+   width: 55\r
+});\r
+\r
+// add the column to the column model\r
+var cm = new Ext.grid.ColumnModel([{\r
+       header: 'Foo',\r
+       ...\r
+    },\r
+    checkColumn\r
+]);\r
+\r
+// create the grid\r
+var grid = new Ext.grid.EditorGridPanel({\r
+    ...\r
+    cm: cm,\r
+    plugins: [checkColumn], // include plugin\r
+    ...\r
+});\r
+ * </code></pre>\r
+ * In addition to storing a Boolean value within the record data, this\r
+ * class toggles a css class between <tt>'x-grid3-check-col'</tt> and\r
+ * <tt>'x-grid3-check-col-on'</tt> to alter the background image used for\r
+ * a column.\r
+ */\r
+Ext.ux.grid.CheckColumn = function(config){\r
+    Ext.apply(this, config);\r
+    if(!this.id){\r
+        this.id = Ext.id();\r
+    }\r
+    this.renderer = this.renderer.createDelegate(this);\r
+};\r
+\r
+Ext.ux.grid.CheckColumn.prototype ={\r
+    init : function(grid){\r
+        this.grid = grid;\r
+        this.grid.on('render', function(){\r
+            var view = this.grid.getView();\r
+            view.mainBody.on('mousedown', this.onMouseDown, this);\r
+        }, this);\r
+    },\r
+\r
+    onMouseDown : function(e, t){\r
+        if(t.className && t.className.indexOf('x-grid3-cc-'+this.id) != -1){\r
+            e.stopEvent();\r
+            var index = this.grid.getView().findRowIndex(t);\r
+            var record = this.grid.store.getAt(index);\r
+            record.set(this.dataIndex, !record.data[this.dataIndex]);\r
+        }\r
+    },\r
+\r
+    renderer : function(v, p, record){\r
+        p.css += ' x-grid3-check-col-td'; \r
+        return '<div class="x-grid3-check-col'+(v?'-on':'')+' x-grid3-cc-'+this.id+'">&#160;</div>';\r
+    }\r
+};\r
+\r
+// register ptype\r
+Ext.preg('checkcolumn', Ext.ux.grid.CheckColumn);\r
+\r
+// backwards compat\r
+Ext.grid.CheckColumn = Ext.ux.grid.CheckColumn;Ext.ns('Ext.ux.tree');\r
+\r
+/**\r
+ * @class Ext.ux.tree.ColumnTree\r
+ * @extends Ext.tree.TreePanel\r
+ * \r
+ * @xtype columntree\r
+ */\r
+Ext.ux.tree.ColumnTree = Ext.extend(Ext.tree.TreePanel, {\r
+    lines : false,\r
+    borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell\r
+    cls : 'x-column-tree',\r
+\r
+    onRender : function(){\r
+        Ext.tree.ColumnTree.superclass.onRender.apply(this, arguments);\r
+        this.headers = this.header.createChild({cls:'x-tree-headers'});\r
+\r
+        var cols = this.columns, c;\r
+        var totalWidth = 0;\r
+        var scrollOffset = 19; // similar to Ext.grid.GridView default\r
+\r
+        for(var i = 0, len = cols.length; i < len; i++){\r
+             c = cols[i];\r
+             totalWidth += c.width;\r
+             this.headers.createChild({\r
+                 cls:'x-tree-hd ' + (c.cls?c.cls+'-hd':''),\r
+                 cn: {\r
+                     cls:'x-tree-hd-text',\r
+                     html: c.header\r
+                 },\r
+                 style:'width:'+(c.width-this.borderWidth)+'px;'\r
+             });\r
+        }\r
+        this.headers.createChild({cls:'x-clear'});\r
+        // prevent floats from wrapping when clipped\r
+        this.headers.setWidth(totalWidth+scrollOffset);\r
+        this.innerCt.setWidth(totalWidth);\r
+    }\r
+});\r
+\r
+Ext.reg('columntree', Ext.ux.tree.ColumnTree);\r
+\r
+//backwards compat\r
+Ext.tree.ColumnTree = Ext.ux.tree.ColumnTree;\r
+\r
+\r
+/**\r
+ * @class Ext.ux.tree.ColumnNodeUI\r
+ * @extends Ext.tree.TreeNodeUI\r
+ */\r
+Ext.ux.tree.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {\r
+    focus: Ext.emptyFn, // prevent odd scrolling behavior\r
+\r
+    renderElements : function(n, a, targetNode, bulkRender){\r
+        this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';\r
+\r
+        var t = n.getOwnerTree();\r
+        var cols = t.columns;\r
+        var bw = t.borderWidth;\r
+        var c = cols[0];\r
+\r
+        var buf = [\r
+             '<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf ', a.cls,'">',\r
+                '<div class="x-tree-col" style="width:',c.width-bw,'px;">',\r
+                    '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",\r
+                    '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow">',\r
+                    '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on">',\r
+                    '<a hidefocus="on" class="x-tree-node-anchor" href="',a.href ? a.href : "#",'" tabIndex="1" ',\r
+                    a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '>',\r
+                    '<span unselectable="on">', n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</span></a>",\r
+                "</div>"];\r
+         for(var i = 1, len = cols.length; i < len; i++){\r
+             c = cols[i];\r
+\r
+             buf.push('<div class="x-tree-col ',(c.cls?c.cls:''),'" style="width:',c.width-bw,'px;">',\r
+                        '<div class="x-tree-col-text">',(c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</div>",\r
+                      "</div>");\r
+         }\r
+         buf.push(\r
+            '<div class="x-clear"></div></div>',\r
+            '<ul class="x-tree-node-ct" style="display:none;"></ul>',\r
+            "</li>");\r
+\r
+        if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){\r
+            this.wrap = Ext.DomHelper.insertHtml("beforeBegin",\r
+                                n.nextSibling.ui.getEl(), buf.join(""));\r
+        }else{\r
+            this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));\r
+        }\r
+\r
+        this.elNode = this.wrap.childNodes[0];\r
+        this.ctNode = this.wrap.childNodes[1];\r
+        var cs = this.elNode.firstChild.childNodes;\r
+        this.indentNode = cs[0];\r
+        this.ecNode = cs[1];\r
+        this.iconNode = cs[2];\r
+        this.anchor = cs[3];\r
+        this.textNode = cs[3].firstChild;\r
+    }\r
+});\r
+\r
+//backwards compat\r
+Ext.tree.ColumnNodeUI = Ext.ux.tree.ColumnNodeUI;\r
+/**\r
+ * @class Ext.DataView.LabelEditor\r
+ * @extends Ext.Editor\r
+ * \r
+ */\r
+Ext.DataView.LabelEditor = Ext.extend(Ext.Editor, {\r
+    alignment: "tl-tl",\r
+    hideEl : false,\r
+    cls: "x-small-editor",\r
+    shim: false,\r
+    completeOnEnter: true,\r
+    cancelOnEsc: true,\r
+    labelSelector: 'span.x-editable',\r
+    \r
+    constructor: function(cfg, field){\r
+        Ext.DataView.LabelEditor.superclass.constructor.call(this,\r
+            field || new Ext.form.TextField({\r
+                allowBlank: false,\r
+                growMin:90,\r
+                growMax:240,\r
+                grow:true,\r
+                selectOnFocus:true\r
+            }), cfg\r
+        );\r
+    },\r
+    \r
+    init : function(view){\r
+        this.view = view;\r
+        view.on('render', this.initEditor, this);\r
+        this.on('complete', this.onSave, this);\r
+    },\r
+\r
+    initEditor : function(){\r
+        this.view.on({\r
+            scope: this,\r
+            containerclick: this.doBlur,\r
+            click: this.doBlur\r
+        });\r
+        this.view.getEl().on('mousedown', this.onMouseDown, this, {delegate: this.labelSelector});\r
+    },\r
+    \r
+    doBlur: function(){\r
+        if(this.editing){\r
+            this.field.blur();\r
+        }\r
+    },\r
+\r
+    onMouseDown : function(e, target){\r
+        if(!e.ctrlKey && !e.shiftKey){\r
+            var item = this.view.findItemFromChild(target);\r
+            e.stopEvent();\r
+            var record = this.view.store.getAt(this.view.indexOf(item));\r
+            this.startEdit(target, record.data[this.dataIndex]);\r
+            this.activeRecord = record;\r
+        }else{\r
+            e.preventDefault();\r
+        }\r
+    },\r
+\r
+    onSave : function(ed, value){\r
+        this.activeRecord.set(this.dataIndex, value);\r
+    }\r
+});\r
+\r
+\r
+Ext.DataView.DragSelector = function(cfg){\r
+    cfg = cfg || {};\r
+    var view, proxy, tracker;\r
+    var rs, bodyRegion, dragRegion = new Ext.lib.Region(0,0,0,0);\r
+    var dragSafe = cfg.dragSafe === true;\r
+\r
+    this.init = function(dataView){\r
+        view = dataView;\r
+        view.on('render', onRender);\r
+    };\r
+\r
+    function fillRegions(){\r
+        rs = [];\r
+        view.all.each(function(el){\r
+            rs[rs.length] = el.getRegion();\r
+        });\r
+        bodyRegion = view.el.getRegion();\r
+    }\r
+\r
+    function cancelClick(){\r
+        return false;\r
+    }\r
+\r
+    function onBeforeStart(e){\r
+        return !dragSafe || e.target == view.el.dom;\r
+    }\r
+\r
+    function onStart(e){\r
+        view.on('containerclick', cancelClick, view, {single:true});\r
+        if(!proxy){\r
+            proxy = view.el.createChild({cls:'x-view-selector'});\r
+        }else{\r
+            proxy.setDisplayed('block');\r
+        }\r
+        fillRegions();\r
+        view.clearSelections();\r
+    }\r
+\r
+    function onDrag(e){\r
+        var startXY = tracker.startXY;\r
+        var xy = tracker.getXY();\r
+\r
+        var x = Math.min(startXY[0], xy[0]);\r
+        var y = Math.min(startXY[1], xy[1]);\r
+        var w = Math.abs(startXY[0] - xy[0]);\r
+        var h = Math.abs(startXY[1] - xy[1]);\r
+\r
+        dragRegion.left = x;\r
+        dragRegion.top = y;\r
+        dragRegion.right = x+w;\r
+        dragRegion.bottom = y+h;\r
+\r
+        dragRegion.constrainTo(bodyRegion);\r
+        proxy.setRegion(dragRegion);\r
+\r
+        for(var i = 0, len = rs.length; i < len; i++){\r
+            var r = rs[i], sel = dragRegion.intersect(r);\r
+            if(sel && !r.selected){\r
+                r.selected = true;\r
+                view.select(i, true);\r
+            }else if(!sel && r.selected){\r
+                r.selected = false;\r
+                view.deselect(i);\r
+            }\r
+        }\r
+    }\r
+\r
+    function onEnd(e){\r
+        if (!Ext.isIE) {\r
+            view.un('containerclick', cancelClick, view);    \r
+        }        \r
+        if(proxy){\r
+            proxy.setDisplayed(false);\r
+        }\r
+    }\r
+\r
+    function onRender(view){\r
+        tracker = new Ext.dd.DragTracker({\r
+            onBeforeStart: onBeforeStart,\r
+            onStart: onStart,\r
+            onDrag: onDrag,\r
+            onEnd: onEnd\r
+        });\r
+        tracker.initEl(view.el);\r
+    }\r
+};Ext.ns('Ext.ux.form');
+
+/**
+ * @class Ext.ux.form.FileUploadField
+ * @extends Ext.form.TextField
+ * Creates a file upload field.
+ * @xtype fileuploadfield
+ */
+Ext.ux.form.FileUploadField = Ext.extend(Ext.form.TextField,  {
+    /**
+     * @cfg {String} buttonText The button text to display on the upload button (defaults to
+     * 'Browse...').  Note that if you supply a value for {@link #buttonCfg}, the buttonCfg.text
+     * value will be used instead if available.
+     */
+    buttonText: 'Browse...',
+    /**
+     * @cfg {Boolean} buttonOnly True to display the file upload field as a button with no visible
+     * text field (defaults to false).  If true, all inherited TextField members will still be available.
+     */
+    buttonOnly: false,
+    /**
+     * @cfg {Number} buttonOffset The number of pixels of space reserved between the button and the text field
+     * (defaults to 3).  Note that this only applies if {@link #buttonOnly} = false.
+     */
+    buttonOffset: 3,
+    /**
+     * @cfg {Object} buttonCfg A standard {@link Ext.Button} config object.
+     */
+
+    // private
+    readOnly: true,
+
+    /**
+     * @hide
+     * @method autoSize
+     */
+    autoSize: Ext.emptyFn,
+
+    // private
+    initComponent: function(){
+        Ext.ux.form.FileUploadField.superclass.initComponent.call(this);
+
+        this.addEvents(
+            /**
+             * @event fileselected
+             * Fires when the underlying file input field's value has changed from the user
+             * selecting a new file from the system file selection dialog.
+             * @param {Ext.ux.form.FileUploadField} this
+             * @param {String} value The file value returned by the underlying file input field
+             */
+            'fileselected'
+        );
+    },
+
+    // private
+    onRender : function(ct, position){
+        Ext.ux.form.FileUploadField.superclass.onRender.call(this, ct, position);
+
+        this.wrap = this.el.wrap({cls:'x-form-field-wrap x-form-file-wrap'});
+        this.el.addClass('x-form-file-text');
+        this.el.dom.removeAttribute('name');
+
+        this.fileInput = this.wrap.createChild({
+            id: this.getFileInputId(),
+            name: this.name||this.getId(),
+            cls: 'x-form-file',
+            tag: 'input',
+            type: 'file',
+            size: 1
+        });
+
+        var btnCfg = Ext.applyIf(this.buttonCfg || {}, {
+            text: this.buttonText
+        });
+        this.button = new Ext.Button(Ext.apply(btnCfg, {
+            renderTo: this.wrap,
+            cls: 'x-form-file-btn' + (btnCfg.iconCls ? ' x-btn-icon' : '')
+        }));
+
+        if(this.buttonOnly){
+            this.el.hide();
+            this.wrap.setWidth(this.button.getEl().getWidth());
+        }
+
+        this.fileInput.on('change', function(){
+            var v = this.fileInput.dom.value;
+            this.setValue(v);
+            this.fireEvent('fileselected', this, v);
+        }, this);
+    },
+
+    // private
+    getFileInputId: function(){
+        return this.id + '-file';
+    },
+
+    // private
+    onResize : function(w, h){
+        Ext.ux.form.FileUploadField.superclass.onResize.call(this, w, h);
+
+        this.wrap.setWidth(w);
+
+        if(!this.buttonOnly){
+            var w = this.wrap.getWidth() - this.button.getEl().getWidth() - this.buttonOffset;
+            this.el.setWidth(w);
+        }
+    },
+
+    // private
+    onDestroy: function(){
+        Ext.ux.form.FileUploadField.superclass.onDestroy.call(this);
+        Ext.destroy(this.fileInput, this.button, this.wrap);
+    },
+
+
+    // private
+    preFocus : Ext.emptyFn,
+
+    // private
+    getResizeEl : function(){
+        return this.wrap;
+    },
+
+    // private
+    getPositionEl : function(){
+        return this.wrap;
+    },
+
+    // private
+    alignErrorIcon : function(){
+        this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
+    }
+
+});
+
+Ext.reg('fileuploadfield', Ext.ux.form.FileUploadField);
+
+// backwards compat
+Ext.form.FileUploadField = Ext.ux.form.FileUploadField;
+(function(){\r
+Ext.ns('Ext.a11y');\r
+\r
+Ext.a11y.Frame = Ext.extend(Object, {\r
+    initialized: false,\r
+    \r
+    constructor: function(size, color){\r
+        this.setSize(size || 1);\r
+        this.setColor(color || '15428B');\r
+    },\r
+    \r
+    init: function(){\r
+        if (!this.initialized) {\r
+            this.sides = [];\r
+            \r
+            var s, i;\r
+            \r
+            this.ct = Ext.DomHelper.append(document.body, {\r
+                cls: 'x-a11y-focusframe'\r
+            }, true);\r
+            \r
+            for (i = 0; i < 4; i++) {\r
+                s = Ext.DomHelper.append(this.ct, {\r
+                    cls: 'x-a11y-focusframe-side',\r
+                    style: 'background-color: #' + this.color\r
+                }, true);\r
+                s.visibilityMode = Ext.Element.DISPLAY;\r
+                this.sides.push(s);\r
+            }\r
+            \r
+            this.frameTask = new Ext.util.DelayedTask(function(el){\r
+                var newEl = Ext.get(el);\r
+                if (newEl != this.curEl) {\r
+                    var w = newEl.getWidth();\r
+                    var h = newEl.getHeight();\r
+                    this.sides[0].show().setSize(w, this.size).anchorTo(el, 'tl', [0, -1]);\r
+                    this.sides[2].show().setSize(w, this.size).anchorTo(el, 'bl', [0, -1]);\r
+                    this.sides[1].show().setSize(this.size, h).anchorTo(el, 'tr', [-1, 0]);\r
+                    this.sides[3].show().setSize(this.size, h).anchorTo(el, 'tl', [-1, 0]);\r
+                    this.curEl = newEl;\r
+                }\r
+            }, this);\r
+            \r
+            this.unframeTask = new Ext.util.DelayedTask(function(){\r
+                if (this.initialized) {\r
+                    this.sides[0].hide();\r
+                    this.sides[1].hide();\r
+                    this.sides[2].hide();\r
+                    this.sides[3].hide();\r
+                    this.curEl = null;\r
+                }\r
+            }, this);\r
+            this.initialized = true;\r
+        }\r
+    },\r
+    \r
+    frame: function(el){\r
+        this.init();\r
+        this.unframeTask.cancel();\r
+        this.frameTask.delay(2, false, false, [el]);\r
+    },\r
+    \r
+    unframe: function(){\r
+        this.init();\r
+        this.unframeTask.delay(2);\r
+    },\r
+    \r
+    setSize: function(size){\r
+        this.size = size;\r
+    },\r
+    \r
+    setColor: function(color){\r
+        this.color = color;\r
+    }\r
+});\r
+\r
+Ext.a11y.FocusFrame = new Ext.a11y.Frame(2, '15428B');\r
+Ext.a11y.RelayFrame = new Ext.a11y.Frame(1, '6B8CBF');\r
+\r
+Ext.a11y.Focusable = Ext.extend(Ext.util.Observable, {\r
+    constructor: function(el, relayTo, noFrame, frameEl){\r
+        Ext.a11y.Focusable.superclass.constructor.call(this);\r
+        \r
+        this.addEvents('focus', 'blur', 'left', 'right', 'up', 'down', 'esc', 'enter', 'space');\r
+        \r
+        if (el instanceof Ext.Component) {\r
+            this.el = el.el;\r
+            this.setComponent(el);\r
+        }\r
+        else {\r
+            this.el = Ext.get(el);\r
+            this.setComponent(null);\r
+        }\r
+        \r
+        this.setRelayTo(relayTo)\r
+        this.setNoFrame(noFrame);\r
+        this.setFrameEl(frameEl);\r
+        \r
+        this.init();\r
+        \r
+        Ext.a11y.FocusMgr.register(this);\r
+    },\r
+    \r
+    init: function(){\r
+        this.el.dom.tabIndex = '1';\r
+        this.el.addClass('x-a11y-focusable');\r
+        this.el.on({\r
+            focus: this.onFocus,\r
+            blur: this.onBlur,\r
+            keydown: this.onKeyDown,\r
+            scope: this\r
+        });\r
+    },\r
+    \r
+    setRelayTo: function(relayTo){\r
+        this.relayTo = relayTo ? Ext.a11y.FocusMgr.get(relayTo) : null;\r
+    },\r
+    \r
+    setNoFrame: function(noFrame){\r
+        this.noFrame = (noFrame === true) ? true : false;\r
+    },\r
+    \r
+    setFrameEl: function(frameEl){\r
+        this.frameEl = frameEl && Ext.get(frameEl) || this.el;\r
+    },\r
+    \r
+    setComponent: function(cmp){\r
+        this.component = cmp || null;\r
+    },\r
+    \r
+    onKeyDown: function(e, t){\r
+        var k = e.getKey(), SK = Ext.a11y.Focusable.SpecialKeys, ret, tf;\r
+        \r
+        tf = (t !== this.el.dom) ? Ext.a11y.FocusMgr.get(t, true) : this;\r
+        if (!tf) {\r
+            // this can happen when you are on a focused item within a panel body\r
+            // that is not a Ext.a11y.Focusable\r
+            tf = Ext.a11y.FocusMgr.get(Ext.fly(t).parent('.x-a11y-focusable'));\r
+        }\r
+        \r
+        if (SK[k] !== undefined) {\r
+            ret = this.fireEvent(SK[k], e, t, tf, this);\r
+        }\r
+        if (ret === false || this.fireEvent('keydown', e, t, tf, this) === false) {\r
+            e.stopEvent();\r
+        }\r
+    },\r
+    \r
+    focus: function(){\r
+        this.el.dom.focus();\r
+    },\r
+    \r
+    blur: function(){\r
+        this.el.dom.blur();\r
+    },\r
+    \r
+    onFocus: function(e, t){\r
+        this.el.addClass('x-a11y-focused');\r
+        if (this.relayTo) {\r
+            this.relayTo.el.addClass('x-a11y-focused-relay');\r
+            if (!this.relayTo.noFrame) {\r
+                Ext.a11y.FocusFrame.frame(this.relayTo.frameEl);\r
+            }\r
+            if (!this.noFrame) {\r
+                Ext.a11y.RelayFrame.frame(this.frameEl);\r
+            }\r
+        }\r
+        else {\r
+            if (!this.noFrame) {\r
+                Ext.a11y.FocusFrame.frame(this.frameEl);\r
+            }\r
+        }\r
+        \r
+        this.fireEvent('focus', e, t, this);\r
+    },\r
+    \r
+    onBlur: function(e, t){\r
+        if (this.relayTo) {\r
+            this.relayTo.el.removeClass('x-a11y-focused-relay');\r
+            Ext.a11y.RelayFrame.unframe();\r
+        }\r
+        this.el.removeClass('x-a11y-focused');\r
+        Ext.a11y.FocusFrame.unframe();\r
+        this.fireEvent('blur', e, t, this);\r
+    },\r
+    \r
+    destroy: function(){\r
+        this.el.un('keydown', this.onKeyDown);\r
+        this.el.un('focus', this.onFocus);\r
+        this.el.un('blur', this.onBlur);\r
+        this.el.removeClass('x-a11y-focusable');\r
+        this.el.removeClass('x-a11y-focused');\r
+        if (this.relayTo) {\r
+            this.relayTo.el.removeClass('x-a11y-focused-relay');\r
+        }\r
+    }\r
+});\r
+\r
+Ext.a11y.FocusItem = Ext.extend(Object, {\r
+    constructor: function(el, enableTabbing){\r
+        Ext.a11y.FocusItem.superclass.constructor.call(this);\r
+        \r
+        this.el = Ext.get(el);\r
+        this.fi = new Ext.a11y.Focusable(el);\r
+        this.fi.setComponent(this);\r
+        \r
+        this.fi.on('tab', this.onTab, this);\r
+        \r
+        this.enableTabbing = enableTabbing === true ? true : false;\r
+    },\r
+    \r
+    getEnterItem: function(){\r
+        if (this.enableTabbing) {\r
+            var items = this.getFocusItems();\r
+            if (items && items.length) {\r
+                return items[0];\r
+            }\r
+        }\r
+    },\r
+    \r
+    getFocusItems: function(){\r
+        if (this.enableTabbing) {\r
+            return this.el.query('a, button, input, select');\r
+        }\r
+        return null;\r
+    },\r
+    \r
+    onTab: function(e, t){\r
+        var items = this.getFocusItems(), i;\r
+        \r
+        if (items && items.length && (i = items.indexOf(t)) !== -1) {\r
+            if (e.shiftKey && i > 0) {\r
+                e.stopEvent();\r
+                items[i - 1].focus();\r
+                Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);\r
+                return;\r
+            }\r
+            else \r
+                if (!e.shiftKey && i < items.length - 1) {\r
+                    e.stopEvent();\r
+                    items[i + 1].focus();\r
+                    Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);\r
+                    return;\r
+                }\r
+        }\r
+    },\r
+    \r
+    focus: function(){\r
+        if (this.enableTabbing) {\r
+            var items = this.getFocusItems();\r
+            if (items && items.length) {\r
+                items[0].focus();\r
+                Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);\r
+                return;\r
+            }\r
+        }\r
+        this.fi.focus();\r
+    },\r
+    \r
+    blur: function(){\r
+        this.fi.blur();\r
+    }\r
+});\r
+\r
+Ext.a11y.FocusMgr = function(){\r
+    var all = new Ext.util.MixedCollection();\r
+    \r
+    return {\r
+        register: function(f){\r
+            all.add(f.el && Ext.id(f.el), f);\r
+        },\r
+        \r
+        unregister: function(f){\r
+            all.remove(f);\r
+        },\r
+        \r
+        get: function(el, noCreate){\r
+            return all.get(Ext.id(el)) || (noCreate ? false : new Ext.a11y.Focusable(el));\r
+        },\r
+        \r
+        all: all\r
+    }\r
+}();\r
+\r
+Ext.a11y.Focusable.SpecialKeys = {};\r
+Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.LEFT] = 'left';\r
+Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.RIGHT] = 'right';\r
+Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.DOWN] = 'down';\r
+Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.UP] = 'up';\r
+Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.ESC] = 'esc';\r
+Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.ENTER] = 'enter';\r
+Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.SPACE] = 'space';\r
+Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.TAB] = 'tab';\r
+\r
+// we use the new observeClass method to fire our new initFocus method on components\r
+Ext.util.Observable.observeClass(Ext.Component);\r
+Ext.Component.on('render', function(cmp){\r
+    cmp.initFocus();\r
+    cmp.initARIA();\r
+});\r
+Ext.override(Ext.Component, {\r
+    initFocus: Ext.emptyFn,\r
+    initARIA: Ext.emptyFn\r
+});\r
+\r
+Ext.override(Ext.Container, {\r
+    isFocusable: true,\r
+    noFocus: false,\r
+    \r
+    // private\r
+    initFocus: function(){\r
+        if (!this.fi && !this.noFocus) {\r
+            this.fi = new Ext.a11y.Focusable(this);\r
+        }\r
+        this.mon(this.fi, {\r
+            focus: this.onFocus,\r
+            blur: this.onBlur,\r
+            tab: this.onTab,\r
+            enter: this.onEnter,\r
+            esc: this.onEsc,\r
+            scope: this\r
+        });\r
+        \r
+        if (this.hidden) {\r
+            this.isFocusable = false;\r
+        }\r
+        \r
+        this.on('show', function(){\r
+            this.isFocusable = true;\r
+        }, this);\r
+        this.on('hide', function(){\r
+            this.isFocusable = false;\r
+        }, this);\r
+    },\r
+    \r
+    focus: function(){\r
+        this.fi.focus();\r
+    },\r
+    \r
+    blur: function(){\r
+        this.fi.blur();\r
+    },\r
+    \r
+    enter: function(){\r
+        var eitem = this.getEnterItem();\r
+        if (eitem) {\r
+            eitem.focus();\r
+        }\r
+    },\r
+    \r
+    onFocus: Ext.emptyFn,\r
+    onBlur: Ext.emptyFn,\r
+    \r
+    onTab: function(e, t, tf){\r
+        var rf = tf.relayTo || tf;\r
+        if (rf.component && rf.component !== this) {\r
+            e.stopEvent();\r
+            var item = e.shiftKey ? this.getPreviousFocus(rf.component) : this.getNextFocus(rf.component);\r
+            item.focus();\r
+        }\r
+    },\r
+    \r
+    onEnter: function(e, t, tf){\r
+        // check to see if enter is pressed while "on" the panel\r
+        if (tf.component && tf.component === this) {\r
+            e.stopEvent();\r
+            this.enter();\r
+        }\r
+        e.stopPropagation();\r
+    },\r
+    \r
+    onEsc: function(e, t){\r
+        e.preventDefault();\r
+        \r
+        // check to see if esc is pressed while "inside" the panel\r
+        // or while "on" the panel\r
+        if (t === this.el.dom) {\r
+            // "on" the panel, check if this panel has an owner panel and focus that\r
+            // we dont stop the event in this case so that this same check will be\r
+            // done for this ownerCt\r
+            if (this.ownerCt) {\r
+                this.ownerCt.focus();\r
+            }\r
+        }\r
+        else {\r
+            // we were inside the panel when esc was pressed,\r
+            // so go back "on" the panel\r
+            if (this.ownerCt && this.ownerCt.isFocusable) {\r
+                var si = this.ownerCt.getFocusItems();\r
+                \r
+                if (si && si.getCount() > 1) {\r
+                    e.stopEvent();\r
+                }\r
+            }\r
+            this.focus();\r
+        }\r
+    },\r
+    \r
+    getFocusItems: function(){\r
+        return this.items &&\r
+        this.items.filterBy(function(o){\r
+            return o.isFocusable;\r
+        }) ||\r
+        null;\r
+    },\r
+    \r
+    getEnterItem: function(){\r
+        var ci = this.getFocusItems(), length = ci ? ci.getCount() : 0;\r
+        \r
+        if (length === 1) {\r
+            return ci.first().getEnterItem && ci.first().getEnterItem() || ci.first();\r
+        }\r
+        else \r
+            if (length > 1) {\r
+                return ci.first();\r
+            }\r
+    },\r
+    \r
+    getNextFocus: function(current){\r
+        var items = this.getFocusItems(), next = current, i = items.indexOf(current), length = items.getCount();\r
+        \r
+        if (i === length - 1) {\r
+            next = items.first();\r
+        }\r
+        else {\r
+            next = items.get(i + 1);\r
+        }\r
+        return next;\r
+    },\r
+    \r
+    getPreviousFocus: function(current){\r
+        var items = this.getFocusItems(), prev = current, i = items.indexOf(current), length = items.getCount();\r
+        \r
+        if (i === 0) {\r
+            prev = items.last();\r
+        }\r
+        else {\r
+            prev = items.get(i - 1);\r
+        }\r
+        return prev;\r
+    },\r
+    \r
+    getFocusable : function() {\r
+        return this.fi;\r
+    }\r
+});\r
+\r
+Ext.override(Ext.Panel, {\r
+    /**\r
+     * @cfg {Boolean} enableTabbing <tt>true</tt> to enable tabbing. Default is <tt>false</tt>.\r
+     */        \r
+    getFocusItems: function(){\r
+        // items gets all the items inside the body\r
+        var items = Ext.Panel.superclass.getFocusItems.call(this), bodyFocus = null;\r
+        \r
+        if (!items) {\r
+            items = new Ext.util.MixedCollection();\r
+            this.bodyFocus = this.bodyFocus || new Ext.a11y.FocusItem(this.body, this.enableTabbing);\r
+            items.add('body', this.bodyFocus);\r
+        }\r
+        // but panels can also have tbar, bbar, fbar\r
+        if (this.tbar && this.topToolbar) {\r
+            items.insert(0, this.topToolbar);\r
+        }\r
+        if (this.bbar && this.bottomToolbar) {\r
+            items.add(this.bottomToolbar);\r
+        }\r
+        if (this.fbar) {\r
+            items.add(this.fbar);\r
+        }\r
+        \r
+        return items;\r
+    }\r
+});\r
+\r
+Ext.override(Ext.TabPanel, {\r
+    // private\r
+    initFocus: function(){\r
+        Ext.TabPanel.superclass.initFocus.call(this);\r
+        this.mon(this.fi, {\r
+            left: this.onLeft,\r
+            right: this.onRight,\r
+            scope: this\r
+        });\r
+    },\r
+    \r
+    onLeft: function(e){\r
+        if (!this.activeTab) {\r
+            return;\r
+        }\r
+        e.stopEvent();\r
+        var prev = this.items.itemAt(this.items.indexOf(this.activeTab) - 1);\r
+        if (prev) {\r
+            this.setActiveTab(prev);\r
+        }\r
+        return false;\r
+    },\r
+    \r
+    onRight: function(e){\r
+        if (!this.activeTab) {\r
+            return;\r
+        }\r
+        e.stopEvent();\r
+        var next = this.items.itemAt(this.items.indexOf(this.activeTab) + 1);\r
+        if (next) {\r
+            this.setActiveTab(next);\r
+        }\r
+        return false;\r
+    }\r
+});\r
+\r
+Ext.override(Ext.tree.TreeNodeUI, {\r
+    // private\r
+    focus: function(){\r
+        this.node.getOwnerTree().bodyFocus.focus();\r
+    }\r
+});\r
+\r
+Ext.override(Ext.tree.TreePanel, {\r
+    // private\r
+    afterRender : function(){\r
+        Ext.tree.TreePanel.superclass.afterRender.call(this);\r
+        this.root.render();\r
+        if(!this.rootVisible){\r
+            this.root.renderChildren();\r
+        }\r
+        this.bodyFocus = new Ext.a11y.FocusItem(this.body.down('.x-tree-root-ct'));\r
+        this.bodyFocus.fi.setFrameEl(this.body);\r
+    } \r
+});\r
+\r
+Ext.override(Ext.grid.GridPanel, {\r
+    initFocus: function(){\r
+        Ext.grid.GridPanel.superclass.initFocus.call(this);\r
+        this.bodyFocus = new Ext.a11y.FocusItem(this.view.focusEl);\r
+        this.bodyFocus.fi.setFrameEl(this.body);\r
+    }\r
+});\r
+\r
+Ext.override(Ext.Button, {\r
+    isFocusable: true,\r
+    noFocus: false,\r
+    \r
+    initFocus: function(){\r
+        Ext.Button.superclass.initFocus.call(this);\r
+        this.fi = this.fi || new Ext.a11y.Focusable(this.btnEl, null, null, this.el);\r
+        this.fi.setComponent(this);\r
+        \r
+        this.mon(this.fi, {\r
+            focus: this.onFocus,\r
+            blur: this.onBlur,\r
+            scope: this\r
+        });\r
+        \r
+        if (this.menu) {\r
+            this.mon(this.fi, 'down', this.showMenu, this);\r
+            this.on('menuhide', this.focus, this);\r
+        }\r
+        \r
+        if (this.hidden) {\r
+            this.isFocusable = false;\r
+        }\r
+        \r
+        this.on('show', function(){\r
+            this.isFocusable = true;\r
+        }, this);\r
+        this.on('hide', function(){\r
+            this.isFocusable = false;\r
+        }, this);\r
+    },\r
+    \r
+    focus: function(){\r
+        this.fi.focus();\r
+    },\r
+    \r
+    blur: function(){\r
+        this.fi.blur();\r
+    },\r
+    \r
+    onFocus: function(){\r
+        if (!this.disabled) {\r
+            this.el.addClass("x-btn-focus");\r
+        }\r
+    },\r
+    \r
+    onBlur: function(){\r
+        this.el.removeClass("x-btn-focus");\r
+    }\r
+});\r
+\r
+Ext.override(Ext.Toolbar, {\r
+    initFocus: function(){\r
+        Ext.Toolbar.superclass.initFocus.call(this);\r
+        this.mon(this.fi, {\r
+            left: this.onLeft,\r
+            right: this.onRight,\r
+            scope: this\r
+        });\r
+        \r
+        this.on('focus', this.onButtonFocus, this, {\r
+            stopEvent: true\r
+        });\r
+    },\r
+    \r
+    addItem: function(item){\r
+        Ext.Toolbar.superclass.add.apply(this, arguments);\r
+        if (item.rendered && item.fi !== undefined) {\r
+            item.fi.setRelayTo(this.el);\r
+            this.relayEvents(item.fi, ['focus']);\r
+        }\r
+        else {\r
+            item.on('render', function(){\r
+                if (item.fi !== undefined) {\r
+                    item.fi.setRelayTo(this.el);\r
+                    this.relayEvents(item.fi, ['focus']);\r
+                }\r
+            }, this, {\r
+                single: true\r
+            });\r
+        }\r
+        return item;\r
+    },\r
+    \r
+    onFocus: function(){\r
+        var items = this.getFocusItems();\r
+        if (items && items.getCount() > 0) {\r
+            if (this.lastFocus && items.indexOf(this.lastFocus) !== -1) {\r
+                this.lastFocus.focus();\r
+            }\r
+            else {\r
+                items.first().focus();\r
+            }\r
+        }\r
+    },\r
+    \r
+    onButtonFocus: function(e, t, tf){\r
+        this.lastFocus = tf.component || null;\r
+    },\r
+    \r
+    onLeft: function(e, t, tf){\r
+        e.stopEvent();\r
+        this.getPreviousFocus(tf.component).focus();\r
+    },\r
+    \r
+    onRight: function(e, t, tf){\r
+        e.stopEvent();\r
+        this.getNextFocus(tf.component).focus();\r
+    },\r
+    \r
+    getEnterItem: Ext.emptyFn,\r
+    onTab: Ext.emptyFn,\r
+    onEsc: Ext.emptyFn\r
+});\r
+\r
+Ext.override(Ext.menu.BaseItem, {\r
+    initFocus: function(){\r
+        this.fi = new Ext.a11y.Focusable(this, this.parentMenu && this.parentMenu.el || null, true);\r
+    }\r
+});\r
+\r
+Ext.override(Ext.menu.Menu, {\r
+    initFocus: function(){\r
+        this.fi = new Ext.a11y.Focusable(this);\r
+        this.focusEl = this.fi;\r
+    }\r
+});\r
+\r
+Ext.a11y.WindowMgr = new Ext.WindowGroup();\r
+\r
+Ext.apply(Ext.WindowMgr, {\r
+    bringToFront: function(win){\r
+        Ext.a11y.WindowMgr.bringToFront.call(this, win);\r
+        if (win.modal) {\r
+            win.enter();\r
+        }\r
+        else {\r
+            win.focus();\r
+        }\r
+    }\r
+});\r
+\r
+Ext.override(Ext.Window, {\r
+    initFocus: function(){\r
+        Ext.Window.superclass.initFocus.call(this);\r
+        this.on('beforehide', function(){\r
+            Ext.a11y.RelayFrame.unframe();\r
+            Ext.a11y.FocusFrame.unframe();\r
+        });\r
+    }\r
+});\r
+\r
+Ext.override(Ext.form.Field, {\r
+    isFocusable: true,\r
+    noFocus: false,\r
+    \r
+    initFocus: function(){\r
+        this.fi = this.fi || new Ext.a11y.Focusable(this, null, true);\r
+        \r
+        Ext.form.Field.superclass.initFocus.call(this);\r
+        \r
+        if (this.hidden) {\r
+            this.isFocusable = false;\r
+        }\r
+        \r
+        this.on('show', function(){\r
+            this.isFocusable = true;\r
+        }, this);\r
+        this.on('hide', function(){\r
+            this.isFocusable = false;\r
+        }, this);\r
+    }\r
+});\r
+\r
+Ext.override(Ext.FormPanel, {\r
+    initFocus: function(){\r
+        Ext.FormPanel.superclass.initFocus.call(this);\r
+        this.on('focus', this.onFieldFocus, this, {\r
+            stopEvent: true\r
+        });\r
+    },\r
+    \r
+    // private\r
+    createForm: function(){\r
+        delete this.initialConfig.listeners;\r
+        var form = new Ext.form.BasicForm(null, this.initialConfig);\r
+        form.afterMethod('add', this.formItemAdd, this);\r
+        return form;\r
+    },\r
+    \r
+    formItemAdd: function(item){\r
+        item.on('render', function(field){\r
+            field.fi.setRelayTo(this.el);\r
+            this.relayEvents(field.fi, ['focus']);\r
+        }, this, {\r
+            single: true\r
+        });\r
+    },\r
+    \r
+    onFocus: function(){\r
+        var items = this.getFocusItems();\r
+        if (items && items.getCount() > 0) {\r
+            if (this.lastFocus && items.indexOf(this.lastFocus) !== -1) {\r
+                this.lastFocus.focus();\r
+            }\r
+            else {\r
+                items.first().focus();\r
+            }\r
+        }\r
+    },\r
+    \r
+    onFieldFocus: function(e, t, tf){\r
+        this.lastFocus = tf.component || null;\r
+    },\r
+    \r
+    onTab: function(e, t, tf){\r
+        if (tf.relayTo.component === this) {\r
+            var item = e.shiftKey ? this.getPreviousFocus(tf.component) : this.getNextFocus(tf.component);\r
+            \r
+            if (item) {\r
+                ev.stopEvent();\r
+                item.focus();\r
+                return;\r
+            }\r
+        }\r
+        Ext.FormPanel.superclass.onTab.apply(this, arguments);\r
+    },\r
+    \r
+    getNextFocus: function(current){\r
+        var items = this.getFocusItems(), i = items.indexOf(current), length = items.getCount();\r
+        \r
+        return (i < length - 1) ? items.get(i + 1) : false;\r
+    },\r
+    \r
+    getPreviousFocus: function(current){\r
+        var items = this.getFocusItems(), i = items.indexOf(current), length = items.getCount();\r
+        \r
+        return (i > 0) ? items.get(i - 1) : false;\r
+    }\r
+});\r
+\r
+Ext.override(Ext.Viewport, {\r
+    initFocus: function(){\r
+        Ext.Viewport.superclass.initFocus.apply(this);\r
+        this.mon(Ext.get(document), 'focus', this.focus, this);\r
+        this.mon(Ext.get(document), 'blur', this.blur, this);\r
+        this.fi.setNoFrame(true);\r
+    },\r
+    \r
+    onTab: function(e, t, tf, f){\r
+        e.stopEvent();\r
+        \r
+        if (tf === f) {\r
+            items = this.getFocusItems();\r
+            if (items && items.getCount() > 0) {\r
+                items.first().focus();\r
+            }\r
+        }\r
+        else {\r
+            var rf = tf.relayTo || tf;\r
+            var item = e.shiftKey ? this.getPreviousFocus(rf.component) : this.getNextFocus(rf.component);\r
+            item.focus();\r
+        }\r
+    }\r
+});\r
+    \r
+})();/**\r
+ * @class Ext.ux.GMapPanel\r
+ * @extends Ext.Panel\r
+ * @author Shea Frederick\r
+ */\r
+Ext.ux.GMapPanel = Ext.extend(Ext.Panel, {\r
+    initComponent : function(){\r
+        \r
+        var defConfig = {\r
+            plain: true,\r
+            zoomLevel: 3,\r
+            yaw: 180,\r
+            pitch: 0,\r
+            zoom: 0,\r
+            gmapType: 'map',\r
+            border: false\r
+        };\r
+        \r
+        Ext.applyIf(this,defConfig);\r
+        \r
+        Ext.ux.GMapPanel.superclass.initComponent.call(this);        \r
+\r
+    },\r
+    afterRender : function(){\r
+        \r
+        var wh = this.ownerCt.getSize();\r
+        Ext.applyIf(this, wh);\r
+        \r
+        Ext.ux.GMapPanel.superclass.afterRender.call(this);    \r
+        \r
+        if (this.gmapType === 'map'){\r
+            this.gmap = new GMap2(this.body.dom);\r
+        }\r
+        \r
+        if (this.gmapType === 'panorama'){\r
+            this.gmap = new GStreetviewPanorama(this.body.dom);\r
+        }\r
+        \r
+        if (typeof this.addControl == 'object' && this.gmapType === 'map') {\r
+            this.gmap.addControl(this.addControl);\r
+        }\r
+        \r
+        if (typeof this.setCenter === 'object') {\r
+            if (typeof this.setCenter.geoCodeAddr === 'string'){\r
+                this.geoCodeLookup(this.setCenter.geoCodeAddr);\r
+            }else{\r
+                if (this.gmapType === 'map'){\r
+                    var point = new GLatLng(this.setCenter.lat,this.setCenter.lng);\r
+                    this.gmap.setCenter(point, this.zoomLevel);    \r
+                }\r
+                if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){\r
+                    this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear);\r
+                }\r
+            }\r
+            if (this.gmapType === 'panorama'){\r
+                this.gmap.setLocationAndPOV(new GLatLng(this.setCenter.lat,this.setCenter.lng), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoom});\r
+            }\r
+        }\r
+\r
+        GEvent.bind(this.gmap, 'load', this, function(){\r
+            this.onMapReady();\r
+        });\r
+\r
+    },\r
+    onMapReady : function(){\r
+        this.addMarkers(this.markers);\r
+        this.addMapControls();\r
+        this.addOptions();  \r
+    },\r
+    onResize : function(w, h){\r
+\r
+        if (typeof this.getMap() == 'object') {\r
+            this.gmap.checkResize();\r
+        }\r
+        \r
+        Ext.ux.GMapPanel.superclass.onResize.call(this, w, h);\r
+\r
+    },\r
+    setSize : function(width, height, animate){\r
+        \r
+        if (typeof this.getMap() == 'object') {\r
+            this.gmap.checkResize();\r
+        }\r
+        \r
+        Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate);\r
+        \r
+    },\r
+    getMap : function(){\r
+        \r
+        return this.gmap;\r
+        \r
+    },\r
+    getCenter : function(){\r
+        \r
+        return this.getMap().getCenter();\r
+        \r
+    },\r
+    getCenterLatLng : function(){\r
+        \r
+        var ll = this.getCenter();\r
+        return {lat: ll.lat(), lng: ll.lng()};\r
+        \r
+    },\r
+    addMarkers : function(markers) {\r
+        \r
+        if (Ext.isArray(markers)){\r
+            for (var i = 0; i < markers.length; i++) {\r
+                var mkr_point = new GLatLng(markers[i].lat,markers[i].lng);\r
+                this.addMarker(mkr_point,markers[i].marker,false,markers[i].setCenter, markers[i].listeners);\r
+            }\r
+        }\r
+        \r
+    },\r
+    addMarker : function(point, marker, clear, center, listeners){\r
+        \r
+        Ext.applyIf(marker,G_DEFAULT_ICON);\r
+\r
+        if (clear === true){\r
+            this.getMap().clearOverlays();\r
+        }\r
+        if (center === true) {\r
+            this.getMap().setCenter(point, this.zoomLevel);\r
+        }\r
+\r
+        var mark = new GMarker(point,marker);\r
+        if (typeof listeners === 'object'){\r
+            for (evt in listeners) {\r
+                GEvent.bind(mark, evt, this, listeners[evt]);\r
+            }\r
+        }\r
+        this.getMap().addOverlay(mark);\r
+\r
+    },\r
+    addMapControls : function(){\r
+        \r
+        if (this.gmapType === 'map') {\r
+            if (Ext.isArray(this.mapControls)) {\r
+                for(i=0;i<this.mapControls.length;i++){\r
+                    this.addMapControl(this.mapControls[i]);\r
+                }\r
+            }else if(typeof this.mapControls === 'string'){\r
+                this.addMapControl(this.mapControls);\r
+            }else if(typeof this.mapControls === 'object'){\r
+                this.getMap().addControl(this.mapControls);\r
+            }\r
+        }\r
+        \r
+    },\r
+    addMapControl : function(mc){\r
+        \r
+        var mcf = window[mc];\r
+        if (typeof mcf === 'function') {\r
+            this.getMap().addControl(new mcf());\r
+        }    \r
+        \r
+    },\r
+    addOptions : function(){\r
+        \r
+        if (Ext.isArray(this.mapConfOpts)) {\r
+            var mc;\r
+            for(i=0;i<this.mapConfOpts.length;i++){\r
+                this.addOption(this.mapConfOpts[i]);\r
+            }\r
+        }else if(typeof this.mapConfOpts === 'string'){\r
+            this.addOption(this.mapConfOpts);\r
+        }        \r
+        \r
+    },\r
+    addOption : function(mc){\r
+        \r
+        var mcf = this.getMap()[mc];\r
+        if (typeof mcf === 'function') {\r
+            this.getMap()[mc]();\r
+        }    \r
+        \r
+    },\r
+    geoCodeLookup : function(addr) {\r
+        \r
+        this.geocoder = new GClientGeocoder();\r
+        this.geocoder.getLocations(addr, this.addAddressToMap.createDelegate(this));\r
+        \r
+    },\r
+    addAddressToMap : function(response) {\r
+        \r
+        if (!response || response.Status.code != 200) {\r
+            Ext.MessageBox.alert('Error', 'Code '+response.Status.code+' Error Returned');\r
+        }else{\r
+            place = response.Placemark[0];\r
+            addressinfo = place.AddressDetails;\r
+            accuracy = addressinfo.Accuracy;\r
+            if (accuracy === 0) {\r
+                Ext.MessageBox.alert('Unable to Locate Address', 'Unable to Locate the Address you provided');\r
+            }else{\r
+                if (accuracy < 7) {\r
+                    Ext.MessageBox.alert('Address Accuracy', 'The address provided has a low accuracy.<br><br>Level '+accuracy+' Accuracy (8 = Exact Match, 1 = Vague Match)');\r
+                }else{\r
+                    point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);\r
+                    if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){\r
+                        this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear,true, this.setCenter.listeners);\r
+                    }\r
+                }\r
+            }\r
+        }\r
+        \r
+    }\r
\r
+});\r
+\r
+Ext.reg('gmappanel', Ext.ux.GMapPanel); Ext.ns('Ext.ux.grid');\r
+\r
+/**\r
+ * @class Ext.ux.grid.GroupSummary\r
+ * @extends Ext.util.Observable\r
+ * A GridPanel plugin that enables dynamic column calculations and a dynamically\r
+ * updated grouped summary row.\r
+ */\r
+Ext.ux.grid.GroupSummary = Ext.extend(Ext.util.Observable, {\r
+    /**\r
+     * @cfg {Function} summaryRenderer Renderer example:<pre><code>\r
+summaryRenderer: function(v, params, data){\r
+    return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');\r
+},\r
+     * </code></pre>\r
+     */\r
+    /**\r
+     * @cfg {String} summaryType (Optional) The type of\r
+     * calculation to be used for the column.  For options available see\r
+     * {@link #Calculations}.\r
+     */\r
+\r
+    constructor : function(config){\r
+        Ext.apply(this, config);\r
+        Ext.ux.grid.GroupSummary.superclass.constructor.call(this);\r
+    },\r
+    init : function(grid){\r
+        this.grid = grid;\r
+        this.cm = grid.getColumnModel();\r
+        this.view = grid.getView();\r
+\r
+        var v = this.view;\r
+        v.doGroupEnd = this.doGroupEnd.createDelegate(this);\r
+\r
+        v.afterMethod('onColumnWidthUpdated', this.doWidth, this);\r
+        v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);\r
+        v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);\r
+        v.afterMethod('onUpdate', this.doUpdate, this);\r
+        v.afterMethod('onRemove', this.doRemove, this);\r
+\r
+        if(!this.rowTpl){\r
+            this.rowTpl = new Ext.Template(\r
+                '<div class="x-grid3-summary-row" style="{tstyle}">',\r
+                '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',\r
+                    '<tbody><tr>{cells}</tr></tbody>',\r
+                '</table></div>'\r
+            );\r
+            this.rowTpl.disableFormats = true;\r
+        }\r
+        this.rowTpl.compile();\r
+\r
+        if(!this.cellTpl){\r
+            this.cellTpl = new Ext.Template(\r
+                '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',\r
+                '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on">{value}</div>',\r
+                "</td>"\r
+            );\r
+            this.cellTpl.disableFormats = true;\r
+        }\r
+        this.cellTpl.compile();\r
+    },\r
+\r
+    /**\r
+     * Toggle the display of the summary row on/off\r
+     * @param {Boolean} visible <tt>true</tt> to show the summary, <tt>false</tt> to hide the summary.\r
+     */\r
+    toggleSummaries : function(visible){\r
+        var el = this.grid.getGridEl();\r
+        if(el){\r
+            if(visible === undefined){\r
+                visible = el.hasClass('x-grid-hide-summary');\r
+            }\r
+            el[visible ? 'removeClass' : 'addClass']('x-grid-hide-summary');\r
+        }\r
+    },\r
+\r
+    renderSummary : function(o, cs){\r
+        cs = cs || this.view.getColumnData();\r
+        var cfg = this.cm.config;\r
+\r
+        var buf = [], c, p = {}, cf, last = cs.length-1;\r
+        for(var i = 0, len = cs.length; i < len; i++){\r
+            c = cs[i];\r
+            cf = cfg[i];\r
+            p.id = c.id;\r
+            p.style = c.style;\r
+            p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');\r
+            if(cf.summaryType || cf.summaryRenderer){\r
+                p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);\r
+            }else{\r
+                p.value = '';\r
+            }\r
+            if(p.value == undefined || p.value === "") p.value = "&#160;";\r
+            buf[buf.length] = this.cellTpl.apply(p);\r
+        }\r
+\r
+        return this.rowTpl.apply({\r
+            tstyle: 'width:'+this.view.getTotalWidth()+';',\r
+            cells: buf.join('')\r
+        });\r
+    },\r
+\r
+    /**\r
+     * @private\r
+     * @param {Object} rs\r
+     * @param {Object} cs\r
+     */\r
+    calculate : function(rs, cs){\r
+        var data = {}, r, c, cfg = this.cm.config, cf;\r
+        for(var j = 0, jlen = rs.length; j < jlen; j++){\r
+            r = rs[j];\r
+            for(var i = 0, len = cs.length; i < len; i++){\r
+                c = cs[i];\r
+                cf = cfg[i];\r
+                if(cf.summaryType){\r
+                    data[c.name] = Ext.ux.grid.GroupSummary.Calculations[cf.summaryType](data[c.name] || 0, r, c.name, data);\r
+                }\r
+            }\r
+        }\r
+        return data;\r
+    },\r
+\r
+    doGroupEnd : function(buf, g, cs, ds, colCount){\r
+        var data = this.calculate(g.rs, cs);\r
+        buf.push('</div>', this.renderSummary({data: data}, cs), '</div>');\r
+    },\r
+\r
+    doWidth : function(col, w, tw){\r
+        var gs = this.view.getGroups(), s;\r
+        for(var i = 0, len = gs.length; i < len; i++){\r
+            s = gs[i].childNodes[2];\r
+            s.style.width = tw;\r
+            s.firstChild.style.width = tw;\r
+            s.firstChild.rows[0].childNodes[col].style.width = w;\r
+        }\r
+    },\r
+\r
+    doAllWidths : function(ws, tw){\r
+        var gs = this.view.getGroups(), s, cells, wlen = ws.length;\r
+        for(var i = 0, len = gs.length; i < len; i++){\r
+            s = gs[i].childNodes[2];\r
+            s.style.width = tw;\r
+            s.firstChild.style.width = tw;\r
+            cells = s.firstChild.rows[0].childNodes;\r
+            for(var j = 0; j < wlen; j++){\r
+                cells[j].style.width = ws[j];\r
+            }\r
+        }\r
+    },\r
+\r
+    doHidden : function(col, hidden, tw){\r
+        var gs = this.view.getGroups(), s, display = hidden ? 'none' : '';\r
+        for(var i = 0, len = gs.length; i < len; i++){\r
+            s = gs[i].childNodes[2];\r
+            s.style.width = tw;\r
+            s.firstChild.style.width = tw;\r
+            s.firstChild.rows[0].childNodes[col].style.display = display;\r
+        }\r
+    },\r
+\r
+    // Note: requires that all (or the first) record in the\r
+    // group share the same group value. Returns false if the group\r
+    // could not be found.\r
+    refreshSummary : function(groupValue){\r
+        return this.refreshSummaryById(this.view.getGroupId(groupValue));\r
+    },\r
+\r
+    getSummaryNode : function(gid){\r
+        var g = Ext.fly(gid, '_gsummary');\r
+        if(g){\r
+            return g.down('.x-grid3-summary-row', true);\r
+        }\r
+        return null;\r
+    },\r
+\r
+    refreshSummaryById : function(gid){\r
+        var g = document.getElementById(gid);\r
+        if(!g){\r
+            return false;\r
+        }\r
+        var rs = [];\r
+        this.grid.store.each(function(r){\r
+            if(r._groupId == gid){\r
+                rs[rs.length] = r;\r
+            }\r
+        });\r
+        var cs = this.view.getColumnData();\r
+        var data = this.calculate(rs, cs);\r
+        var markup = this.renderSummary({data: data}, cs);\r
+\r
+        var existing = this.getSummaryNode(gid);\r
+        if(existing){\r
+            g.removeChild(existing);\r
+        }\r
+        Ext.DomHelper.append(g, markup);\r
+        return true;\r
+    },\r
+\r
+    doUpdate : function(ds, record){\r
+        this.refreshSummaryById(record._groupId);\r
+    },\r
+\r
+    doRemove : function(ds, record, index, isUpdate){\r
+        if(!isUpdate){\r
+            this.refreshSummaryById(record._groupId);\r
+        }\r
+    },\r
+\r
+    /**\r
+     * Show a message in the summary row.\r
+     * <pre><code>\r
+grid.on('afteredit', function(){\r
+    var groupValue = 'Ext Forms: Field Anchoring';\r
+    summary.showSummaryMsg(groupValue, 'Updating Summary...');\r
+});\r
+     * </code></pre>\r
+     * @param {String} groupValue\r
+     * @param {String} msg Text to use as innerHTML for the summary row.\r
+     */\r
+    showSummaryMsg : function(groupValue, msg){\r
+        var gid = this.view.getGroupId(groupValue);\r
+        var node = this.getSummaryNode(gid);\r
+        if(node){\r
+            node.innerHTML = '<div class="x-grid3-summary-msg">' + msg + '</div>';\r
+        }\r
+    }\r
+});\r
+\r
+//backwards compat\r
+Ext.grid.GroupSummary = Ext.ux.grid.GroupSummary;\r
+\r
+\r
+/**\r
+ * Calculation types for summary row:</p><div class="mdetail-params"><ul>\r
+ * <li><b><tt>sum</tt></b> : <div class="sub-desc"></div></li>\r
+ * <li><b><tt>count</tt></b> : <div class="sub-desc"></div></li>\r
+ * <li><b><tt>max</tt></b> : <div class="sub-desc"></div></li>\r
+ * <li><b><tt>min</tt></b> : <div class="sub-desc"></div></li>\r
+ * <li><b><tt>average</tt></b> : <div class="sub-desc"></div></li>\r
+ * </ul></div>\r
+ * <p>Custom calculations may be implemented.  An example of\r
+ * custom <code>summaryType=totalCost</code>:</p><pre><code>\r
+// define a custom summary function\r
+Ext.ux.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){\r
+    return v + (record.data.estimate * record.data.rate);\r
+};\r
+ * </code></pre>\r
+ * @property Calculations\r
+ */\r
+\r
+Ext.ux.grid.GroupSummary.Calculations = {\r
+    'sum' : function(v, record, field){\r
+        return v + (record.data[field]||0);\r
+    },\r
+\r
+    'count' : function(v, record, field, data){\r
+        return data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
+    },\r
+\r
+    'max' : function(v, record, field, data){\r
+        var v = record.data[field];\r
+        var max = data[field+'max'] === undefined ? (data[field+'max'] = v) : data[field+'max'];\r
+        return v > max ? (data[field+'max'] = v) : max;\r
+    },\r
+\r
+    'min' : function(v, record, field, data){\r
+        var v = record.data[field];\r
+        var min = data[field+'min'] === undefined ? (data[field+'min'] = v) : data[field+'min'];\r
+        return v < min ? (data[field+'min'] = v) : min;\r
+    },\r
+\r
+    'average' : function(v, record, field, data){\r
+        var c = data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
+        var t = (data[field+'total'] = ((data[field+'total']||0) + (record.data[field]||0)));\r
+        return t === 0 ? 0 : t / c;\r
+    }\r
+};\r
+Ext.grid.GroupSummary.Calculations = Ext.ux.grid.GroupSummary.Calculations;\r
+\r
+/**\r
+ * @class Ext.ux.grid.HybridSummary\r
+ * @extends Ext.ux.grid.GroupSummary\r
+ * Adds capability to specify the summary data for the group via json as illustrated here:\r
+ * <pre><code>\r
+{\r
+    data: [\r
+        {\r
+            projectId: 100,     project: 'House',\r
+            taskId:    112, description: 'Paint',\r
+            estimate:    6,        rate:     150,\r
+            due:'06/24/2007'\r
+        },\r
+        ...\r
+    ],\r
+\r
+    summaryData: {\r
+        'House': {\r
+            description: 14, estimate: 9,\r
+                   rate: 99, due: new Date(2009, 6, 29),\r
+                   cost: 999\r
+        }\r
+    }\r
+}\r
+ * </code></pre>\r
+ *\r
+ */\r
+Ext.ux.grid.HybridSummary = Ext.extend(Ext.ux.grid.GroupSummary, {\r
+    /**\r
+     * @private\r
+     * @param {Object} rs\r
+     * @param {Object} cs\r
+     */\r
+    calculate : function(rs, cs){\r
+        var gcol = this.view.getGroupField();\r
+        var gvalue = rs[0].data[gcol];\r
+        var gdata = this.getSummaryData(gvalue);\r
+        return gdata || Ext.ux.grid.HybridSummary.superclass.calculate.call(this, rs, cs);\r
+    },\r
+\r
+    /**\r
+     * <pre><code>\r
+grid.on('afteredit', function(){\r
+    var groupValue = 'Ext Forms: Field Anchoring';\r
+    summary.showSummaryMsg(groupValue, 'Updating Summary...');\r
+    setTimeout(function(){ // simulate server call\r
+        // HybridSummary class implements updateSummaryData\r
+        summary.updateSummaryData(groupValue,\r
+            // create data object based on configured dataIndex\r
+            {description: 22, estimate: 888, rate: 888, due: new Date(), cost: 8});\r
+    }, 2000);\r
+});\r
+     * </code></pre>\r
+     * @param {String} groupValue\r
+     * @param {Object} data data object\r
+     * @param {Boolean} skipRefresh (Optional) Defaults to false\r
+     */\r
+    updateSummaryData : function(groupValue, data, skipRefresh){\r
+        var json = this.grid.store.reader.jsonData;\r
+        if(!json.summaryData){\r
+            json.summaryData = {};\r
+        }\r
+        json.summaryData[groupValue] = data;\r
+        if(!skipRefresh){\r
+            this.refreshSummary(groupValue);\r
+        }\r
+    },\r
+\r
+    /**\r
+     * Returns the summaryData for the specified groupValue or null.\r
+     * @param {String} groupValue\r
+     * @return {Object} summaryData\r
+     */\r
+    getSummaryData : function(groupValue){\r
+        var json = this.grid.store.reader.jsonData;\r
+        if(json && json.summaryData){\r
+            return json.summaryData[groupValue];\r
+        }\r
+        return null;\r
+    }\r
+});\r
+\r
+//backwards compat\r
+Ext.grid.HybridSummary = Ext.ux.grid.HybridSummary;\r
+Ext.ux.GroupTab = Ext.extend(Ext.Container, {\r
+    mainItem: 0,\r
+    \r
+    expanded: true,\r
+    \r
+    deferredRender: true,\r
+    \r
+    activeTab: null,\r
+    \r
+    idDelimiter: '__',\r
+    \r
+    headerAsText: false,\r
+    \r
+    frame: false,\r
+    \r
+    hideBorders: true,\r
+    \r
+    initComponent: function(config){\r
+        Ext.apply(this, config);\r
+        this.frame = false;\r
+        \r
+        Ext.ux.GroupTab.superclass.initComponent.call(this);\r
+        \r
+        this.addEvents('activate', 'deactivate', 'changemainitem', 'beforetabchange', 'tabchange');\r
+        \r
+        this.setLayout(new Ext.layout.CardLayout({\r
+            deferredRender: this.deferredRender\r
+        }));\r
+        \r
+        if (!this.stack) {\r
+            this.stack = Ext.TabPanel.AccessStack();\r
+        }\r
+        \r
+        this.initItems();\r
+        \r
+        this.on('beforerender', function(){\r
+            this.groupEl = this.ownerCt.getGroupEl(this);\r
+        }, this);\r
+        \r
+        this.on('add', this.onAdd, this, {\r
+            target: this\r
+        });\r
+        this.on('remove', this.onRemove, this, {\r
+            target: this\r
+        });\r
+        \r
+        if (this.mainItem !== undefined) {\r
+            var item = (typeof this.mainItem == 'object') ? this.mainItem : this.items.get(this.mainItem);\r
+            delete this.mainItem;\r
+            this.setMainItem(item);\r
+        }\r
+    },\r
+    \r
+    /**\r
+     * Sets the specified tab as the active tab. This method fires the {@link #beforetabchange} event which\r
+     * can return false to cancel the tab change.\r
+     * @param {String/Panel} tab The id or tab Panel to activate\r
+     */\r
+    setActiveTab : function(item){\r
+        item = this.getComponent(item);\r
+        if(!item || this.fireEvent('beforetabchange', this, item, this.activeTab) === false){\r
+            return;\r
+        }\r
+        if(!this.rendered){\r
+            this.activeTab = item;\r
+            return;\r
+        }\r
+        if(this.activeTab != item){\r
+            if(this.activeTab && this.activeTab != this.mainItem){\r
+                var oldEl = this.getTabEl(this.activeTab);\r
+                if(oldEl){\r
+                    Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');\r
+                }\r
+                this.activeTab.fireEvent('deactivate', this.activeTab);\r
+            }\r
+            var el = this.getTabEl(item);\r
+            Ext.fly(el).addClass('x-grouptabs-strip-active');\r
+            this.activeTab = item;\r
+            this.stack.add(item);\r
+\r
+            this.layout.setActiveItem(item);\r
+            if(this.layoutOnTabChange && item.doLayout){\r
+                item.doLayout();\r
+            }\r
+            if(this.scrolling){\r
+                this.scrollToTab(item, this.animScroll);\r
+            }\r
+\r
+            item.fireEvent('activate', item);\r
+            this.fireEvent('tabchange', this, item);\r
+        }\r
+    },\r
+    \r
+    getTabEl: function(item){\r
+        if (item == this.mainItem) {\r
+            return this.groupEl;\r
+        }\r
+        return Ext.TabPanel.prototype.getTabEl.call(this, item);\r
+    },\r
+    \r
+    onRender: function(ct, position){\r
+        Ext.ux.GroupTab.superclass.onRender.call(this, ct, position);\r
+        \r
+        this.strip = Ext.fly(this.groupEl).createChild({\r
+            tag: 'ul',\r
+            cls: 'x-grouptabs-sub'\r
+        });\r
+\r
+        this.tooltip = new Ext.ToolTip({\r
+           target: this.groupEl,\r
+           delegate: 'a.x-grouptabs-text',\r
+           trackMouse: true,\r
+           renderTo: document.body,\r
+           listeners: {\r
+               beforeshow: function(tip) {\r
+                   var item = (tip.triggerElement.parentNode === this.mainItem.tabEl)\r
+                       ? this.mainItem\r
+                       : this.findById(tip.triggerElement.parentNode.id.split(this.idDelimiter)[1]);\r
+\r
+                   if(!item.tabTip) {\r
+                       return false;\r
+                   }\r
+                   tip.body.dom.innerHTML = item.tabTip;\r
+               },\r
+               scope: this\r
+           }\r
+        });\r
+                \r
+        if (!this.itemTpl) {\r
+            var tt = new Ext.Template('<li class="{cls}" id="{id}">', '<a onclick="return false;" class="x-grouptabs-text {iconCls}">{text}</a>', '</li>');\r
+            tt.disableFormats = true;\r
+            tt.compile();\r
+            Ext.ux.GroupTab.prototype.itemTpl = tt;\r
+        }\r
+        \r
+        this.items.each(this.initTab, this);\r
+    },\r
+    \r
+    afterRender: function(){\r
+        Ext.ux.GroupTab.superclass.afterRender.call(this);\r
+        \r
+        if (this.activeTab !== undefined) {\r
+            var item = (typeof this.activeTab == 'object') ? this.activeTab : this.items.get(this.activeTab);\r
+            delete this.activeTab;\r
+            this.setActiveTab(item);\r
+        }\r
+    },\r
+    \r
+    // private\r
+    initTab: function(item, index){\r
+        var before = this.strip.dom.childNodes[index];\r
+        var p = Ext.TabPanel.prototype.getTemplateArgs.call(this, item);\r
+        \r
+        if (item === this.mainItem) {\r
+            item.tabEl = this.groupEl;\r
+            p.cls += ' x-grouptabs-main-item';\r
+        }\r
+        \r
+        var el = before ? this.itemTpl.insertBefore(before, p) : this.itemTpl.append(this.strip, p);\r
+        \r
+        item.tabEl = item.tabEl || el;\r
+                \r
+        item.on('disable', this.onItemDisabled, this);\r
+        item.on('enable', this.onItemEnabled, this);\r
+        item.on('titlechange', this.onItemTitleChanged, this);\r
+        item.on('iconchange', this.onItemIconChanged, this);\r
+        item.on('beforeshow', this.onBeforeShowItem, this);\r
+    },\r
+    \r
+    setMainItem: function(item){\r
+        item = this.getComponent(item);\r
+        if (!item || this.fireEvent('changemainitem', this, item, this.mainItem) === false) {\r
+            return;\r
+        }\r
+        \r
+        this.mainItem = item;\r
+    },\r
+    \r
+    getMainItem: function(){\r
+        return this.mainItem || null;\r
+    },\r
+    \r
+    // private\r
+    onBeforeShowItem: function(item){\r
+        if (item != this.activeTab) {\r
+            this.setActiveTab(item);\r
+            return false;\r
+        }\r
+    },\r
+    \r
+    // private\r
+    onAdd: function(gt, item, index){\r
+        if (this.rendered) {\r
+            this.initTab.call(this, item, index);\r
+        }\r
+    },\r
+    \r
+    // private\r
+    onRemove: function(tp, item){\r
+        Ext.destroy(Ext.get(this.getTabEl(item)));\r
+        this.stack.remove(item);\r
+        item.un('disable', this.onItemDisabled, this);\r
+        item.un('enable', this.onItemEnabled, this);\r
+        item.un('titlechange', this.onItemTitleChanged, this);\r
+        item.un('iconchange', this.onItemIconChanged, this);\r
+        item.un('beforeshow', this.onBeforeShowItem, this);\r
+        if (item == this.activeTab) {\r
+            var next = this.stack.next();\r
+            if (next) {\r
+                this.setActiveTab(next);\r
+            }\r
+            else if (this.items.getCount() > 0) {\r
+                this.setActiveTab(0);\r
+            }\r
+            else {\r
+                this.activeTab = null;\r
+            }\r
+        }\r
+    },\r
+    \r
+    // private\r
+    onBeforeAdd: function(item){\r
+        var existing = item.events ? (this.items.containsKey(item.getItemId()) ? item : null) : this.items.get(item);\r
+        if (existing) {\r
+            this.setActiveTab(item);\r
+            return false;\r
+        }\r
+        Ext.TabPanel.superclass.onBeforeAdd.apply(this, arguments);\r
+        var es = item.elements;\r
+        item.elements = es ? es.replace(',header', '') : es;\r
+        item.border = (item.border === true);\r
+    },\r
+    \r
+    // private\r
+    onItemDisabled: Ext.TabPanel.prototype.onItemDisabled,\r
+    onItemEnabled: Ext.TabPanel.prototype.onItemEnabled,\r
+    \r
+    // private\r
+    onItemTitleChanged: function(item){\r
+        var el = this.getTabEl(item);\r
+        if (el) {\r
+            Ext.fly(el).child('a.x-grouptabs-text', true).innerHTML = item.title;\r
+        }\r
+    },\r
+    \r
+    //private\r
+    onItemIconChanged: function(item, iconCls, oldCls){\r
+        var el = this.getTabEl(item);\r
+        if (el) {\r
+            Ext.fly(el).child('a.x-grouptabs-text').replaceClass(oldCls, iconCls);\r
+        }\r
+    },\r
+    \r
+    beforeDestroy: function(){\r
+        Ext.TabPanel.prototype.beforeDestroy.call(this);\r
+        this.tooltip.destroy();\r
+    }\r
+});\r
+\r
+Ext.reg('grouptab', Ext.ux.GroupTab);\r
+Ext.ns('Ext.ux');\r
+\r
+Ext.ux.GroupTabPanel = Ext.extend(Ext.TabPanel, {\r
+    tabPosition: 'left',\r
+    \r
+    alternateColor: false,\r
+    \r
+    alternateCls: 'x-grouptabs-panel-alt',\r
+    \r
+    defaultType: 'grouptab',\r
+    \r
+    deferredRender: false,\r
+    \r
+    activeGroup : null,\r
+    \r
+    initComponent: function(){\r
+        Ext.ux.GroupTabPanel.superclass.initComponent.call(this);\r
+        \r
+        this.addEvents(\r
+            'beforegroupchange',\r
+            'groupchange'\r
+        );\r
+        this.elements = 'body,header';\r
+        this.stripTarget = 'header';\r
+        \r
+        this.tabPosition = this.tabPosition == 'right' ? 'right' : 'left';\r
+        \r
+        this.addClass('x-grouptabs-panel');\r
+        \r
+        if (this.tabStyle && this.tabStyle != '') {\r
+            this.addClass('x-grouptabs-panel-' + this.tabStyle);\r
+        }\r
+        \r
+        if (this.alternateColor) {\r
+            this.addClass(this.alternateCls);\r
+        }\r
+        \r
+        this.on('beforeadd', function(gtp, item, index){\r
+            this.initGroup(item, index);\r
+        });                 \r
+    },\r
+    \r
+    initEvents : function() {\r
+        this.mon(this.strip, 'mousedown', this.onStripMouseDown, this);\r
+    },\r
+        \r
+    onRender: function(ct, position){\r
+        Ext.TabPanel.superclass.onRender.call(this, ct, position);\r
+\r
+        if(this.plain){\r
+            var pos = this.tabPosition == 'top' ? 'header' : 'footer';\r
+            this[pos].addClass('x-tab-panel-'+pos+'-plain');\r
+        }\r
+\r
+        var st = this[this.stripTarget];\r
+\r
+        this.stripWrap = st.createChild({cls:'x-tab-strip-wrap ', cn:{\r
+            tag:'ul', cls:'x-grouptabs-strip x-grouptabs-tab-strip-'+this.tabPosition}});\r
+\r
+        var beforeEl = (this.tabPosition=='bottom' ? this.stripWrap : null);\r
+        this.strip = new Ext.Element(this.stripWrap.dom.firstChild);\r
+\r
+               this.header.addClass('x-grouptabs-panel-header');\r
+               this.bwrap.addClass('x-grouptabs-bwrap');\r
+        this.body.addClass('x-tab-panel-body-'+this.tabPosition + ' x-grouptabs-panel-body');\r
+\r
+        if (!this.itemTpl) {\r
+            var tt = new Ext.Template(\r
+                '<li class="{cls}" id="{id}">', \r
+                '<a class="x-grouptabs-expand" onclick="return false;"></a>', \r
+                '<a class="x-grouptabs-text {iconCls}" href="#" onclick="return false;">',\r
+                '<span>{text}</span></a>', \r
+                '</li>'\r
+            );\r
+            tt.disableFormats = true;\r
+            tt.compile();\r
+            Ext.ux.GroupTabPanel.prototype.itemTpl = tt;\r
+        }\r
+\r
+        this.items.each(this.initGroup, this);\r
+    },\r
+    \r
+    afterRender: function(){\r
+        Ext.ux.GroupTabPanel.superclass.afterRender.call(this);\r
+        \r
+        this.tabJoint = Ext.fly(this.body.dom.parentNode).createChild({\r
+            cls: 'x-tab-joint'\r
+        });\r
+        \r
+        this.addClass('x-tab-panel-' + this.tabPosition);\r
+        this.header.setWidth(this.tabWidth);\r
+        \r
+        if (this.activeGroup !== undefined) {\r
+            var group = (typeof this.activeGroup == 'object') ? this.activeGroup : this.items.get(this.activeGroup);\r
+            delete this.activeGroup;\r
+            this.setActiveGroup(group);\r
+            group.setActiveTab(group.getMainItem());\r
+        }\r
+    },\r
+\r
+    getGroupEl : Ext.TabPanel.prototype.getTabEl,\r
+        \r
+    // private\r
+    findTargets: function(e){\r
+        var item = null;\r
+        var itemEl = e.getTarget('li', this.strip);\r
+        if (itemEl) {\r
+            item = this.findById(itemEl.id.split(this.idDelimiter)[1]);\r
+            if (item.disabled) {\r
+                return {\r
+                    expand: null,\r
+                    item: null,\r
+                    el: null\r
+                };\r
+            }\r
+        }\r
+        return {\r
+            expand: e.getTarget('.x-grouptabs-expand', this.strip),\r
+            isGroup: !e.getTarget('ul.x-grouptabs-sub', this.strip),\r
+            item: item,\r
+            el: itemEl\r
+        };\r
+    },\r
+    \r
+    // private\r
+    onStripMouseDown: function(e){\r
+        if (e.button != 0) {\r
+            return;\r
+        }\r
+        e.preventDefault();\r
+        var t = this.findTargets(e);\r
+        if (t.expand) {\r
+            this.toggleGroup(t.el);\r
+        }\r
+        else if (t.item) {\r
+            if(t.isGroup) {\r
+                t.item.setActiveTab(t.item.getMainItem());\r
+            }\r
+            else {\r
+                t.item.ownerCt.setActiveTab(t.item);\r
+            }\r
+        }\r
+    },\r
+    \r
+    expandGroup: function(groupEl){\r
+        if(groupEl.isXType) {\r
+            groupEl = this.getGroupEl(groupEl);\r
+        }\r
+        Ext.fly(groupEl).addClass('x-grouptabs-expanded');\r
+    },\r
+    \r
+    toggleGroup: function(groupEl){\r
+        if(groupEl.isXType) {\r
+            groupEl = this.getGroupEl(groupEl);\r
+        }        \r
+        Ext.fly(groupEl).toggleClass('x-grouptabs-expanded');\r
+               this.syncTabJoint();\r
+    },    \r
+    \r
+    syncTabJoint: function(groupEl){\r
+        if (!this.tabJoint) {\r
+            return;\r
+        }\r
+        \r
+        groupEl = groupEl || this.getGroupEl(this.activeGroup);\r
+        if(groupEl) {\r
+            this.tabJoint.setHeight(Ext.fly(groupEl).getHeight() - 2); \r
+                       \r
+            var y = Ext.isGecko2 ? 0 : 1;\r
+            if (this.tabPosition == 'left'){\r
+                this.tabJoint.alignTo(groupEl, 'tl-tr', [-2,y]);\r
+            }\r
+            else {\r
+                this.tabJoint.alignTo(groupEl, 'tr-tl', [1,y]);\r
+            }           \r
+        }\r
+        else {\r
+            this.tabJoint.hide();\r
+        }\r
+    },\r
+    \r
+    getActiveTab : function() {\r
+        if(!this.activeGroup) return null;\r
+        return this.activeGroup.getTabEl(this.activeGroup.activeTab) || null;  \r
+    },\r
+    \r
+    onResize: function(){\r
+        Ext.ux.GroupTabPanel.superclass.onResize.apply(this, arguments);\r
+        this.syncTabJoint();\r
+    },\r
+    \r
+    createCorner: function(el, pos){\r
+        return Ext.fly(el).createChild({\r
+            cls: 'x-grouptabs-corner x-grouptabs-corner-' + pos\r
+        });\r
+    },\r
+    \r
+    initGroup: function(group, index){\r
+        var before = this.strip.dom.childNodes[index];        \r
+        var p = this.getTemplateArgs(group);\r
+        if (index === 0) {\r
+            p.cls += ' x-tab-first';\r
+        }\r
+        p.cls += ' x-grouptabs-main';\r
+        p.text = group.getMainItem().title;\r
+        \r
+        var el = before ? this.itemTpl.insertBefore(before, p) : this.itemTpl.append(this.strip, p);\r
+        \r
+        var tl = this.createCorner(el, 'top-' + this.tabPosition);\r
+        var bl = this.createCorner(el, 'bottom-' + this.tabPosition);\r
+\r
+        if (group.expanded) {\r
+            this.expandGroup(el);\r
+        }\r
+\r
+        if (Ext.isIE6 || (Ext.isIE && !Ext.isStrict)){\r
+            bl.setLeft('-10px');\r
+            bl.setBottom('-5px');\r
+            tl.setLeft('-10px');\r
+            tl.setTop('-5px');\r
+        }\r
+\r
+        this.mon(group, 'changemainitem', this.onGroupChangeMainItem, this);\r
+        this.mon(group, 'beforetabchange', this.onGroupBeforeTabChange, this);\r
+    },\r
+    \r
+    setActiveGroup : function(group) {\r
+        group = this.getComponent(group);\r
+        if(!group || this.fireEvent('beforegroupchange', this, group, this.activeGroup) === false){\r
+            return;\r
+        }\r
+        if(!this.rendered){\r
+            this.activeGroup = group;\r
+            return;\r
+        }\r
+        if(this.activeGroup != group){\r
+            if(this.activeGroup){\r
+                var oldEl = this.getGroupEl(this.activeGroup);\r
+                if(oldEl){\r
+                    Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');\r
+                }\r
+                this.activeGroup.fireEvent('deactivate', this.activeTab);\r
+            }\r
+\r
+            var groupEl = this.getGroupEl(group);\r
+            Ext.fly(groupEl).addClass('x-grouptabs-strip-active');\r
+                        \r
+            this.activeGroup = group;\r
+            this.stack.add(group);\r
+\r
+            this.layout.setActiveItem(group);\r
+            this.syncTabJoint(groupEl);\r
+\r
+            group.fireEvent('activate', group);\r
+            this.fireEvent('groupchange', this, group);\r
+        }        \r
+    },\r
+    \r
+    onGroupBeforeTabChange: function(group, newTab, oldTab){\r
+        if(group !== this.activeGroup || newTab !== oldTab) {\r
+            this.strip.select('.x-grouptabs-sub > li.x-grouptabs-strip-active', true).removeClass('x-grouptabs-strip-active');\r
+        } \r
+        \r
+        this.expandGroup(this.getGroupEl(group));\r
+        this.setActiveGroup(group);\r
+    },\r
+    \r
+    getFrameHeight: function(){\r
+        var h = this.el.getFrameWidth('tb');\r
+        h += (this.tbar ? this.tbar.getHeight() : 0) +\r
+        (this.bbar ? this.bbar.getHeight() : 0);\r
+        \r
+        return h;\r
+    },\r
+    \r
+    adjustBodyWidth: function(w){\r
+        return w - this.tabWidth;\r
+    }\r
+});\r
+\r
+Ext.reg('grouptabpanel', Ext.ux.GroupTabPanel);/*\r
+ * Note that this control will most likely remain as an example, and not as a core Ext form\r
+ * control.  However, the API will be changing in a future release and so should not yet be\r
+ * treated as a final, stable API at this time.\r
+ */\r
+\r
+/**\r
+ * @class Ext.ux.form.ItemSelector\r
+ * @extends Ext.form.Field\r
+ * A control that allows selection of between two Ext.ux.form.MultiSelect controls.\r
+ *\r
+ *  @history\r
+ *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)\r
+ *\r
+ * @constructor\r
+ * Create a new ItemSelector\r
+ * @param {Object} config Configuration options\r
+ * @xtype itemselector \r
+ */\r
+Ext.ux.form.ItemSelector = Ext.extend(Ext.form.Field,  {\r
+    hideNavIcons:false,\r
+    imagePath:"",\r
+    iconUp:"up2.gif",\r
+    iconDown:"down2.gif",\r
+    iconLeft:"left2.gif",\r
+    iconRight:"right2.gif",\r
+    iconTop:"top2.gif",\r
+    iconBottom:"bottom2.gif",\r
+    drawUpIcon:true,\r
+    drawDownIcon:true,\r
+    drawLeftIcon:true,\r
+    drawRightIcon:true,\r
+    drawTopIcon:true,\r
+    drawBotIcon:true,\r
+    delimiter:',',\r
+    bodyStyle:null,\r
+    border:false,\r
+    defaultAutoCreate:{tag: "div"},\r
+    /**\r
+     * @cfg {Array} multiselects An array of {@link Ext.ux.form.MultiSelect} config objects, with at least all required parameters (e.g., store)\r
+     */\r
+    multiselects:null,\r
+\r
+    initComponent: function(){\r
+        Ext.ux.form.ItemSelector.superclass.initComponent.call(this);\r
+        this.addEvents({\r
+            'rowdblclick' : true,\r
+            'change' : true\r
+        });\r
+    },\r
+\r
+    onRender: function(ct, position){\r
+        Ext.ux.form.ItemSelector.superclass.onRender.call(this, ct, position);\r
+\r
+        // Internal default configuration for both multiselects\r
+        var msConfig = [{\r
+            legend: 'Available',\r
+            draggable: true,\r
+            droppable: true,\r
+            width: 100,\r
+            height: 100\r
+        },{\r
+            legend: 'Selected',\r
+            droppable: true,\r
+            draggable: true,\r
+            width: 100,\r
+            height: 100\r
+        }];\r
+\r
+        this.fromMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[0], msConfig[0]));\r
+        this.fromMultiselect.on('dblclick', this.onRowDblClick, this);\r
+\r
+        this.toMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[1], msConfig[1]));\r
+        this.toMultiselect.on('dblclick', this.onRowDblClick, this);\r
+\r
+        var p = new Ext.Panel({\r
+            bodyStyle:this.bodyStyle,\r
+            border:this.border,\r
+            layout:"table",\r
+            layoutConfig:{columns:3}\r
+        });\r
+\r
+        p.add(this.fromMultiselect);\r
+        var icons = new Ext.Panel({header:false});\r
+        p.add(icons);\r
+        p.add(this.toMultiselect);\r
+        p.render(this.el);\r
+        icons.el.down('.'+icons.bwrapCls).remove();\r
+\r
+        // ICON HELL!!!\r
+        if (this.imagePath!="" && this.imagePath.charAt(this.imagePath.length-1)!="/")\r
+            this.imagePath+="/";\r
+        this.iconUp = this.imagePath + (this.iconUp || 'up2.gif');\r
+        this.iconDown = this.imagePath + (this.iconDown || 'down2.gif');\r
+        this.iconLeft = this.imagePath + (this.iconLeft || 'left2.gif');\r
+        this.iconRight = this.imagePath + (this.iconRight || 'right2.gif');\r
+        this.iconTop = this.imagePath + (this.iconTop || 'top2.gif');\r
+        this.iconBottom = this.imagePath + (this.iconBottom || 'bottom2.gif');\r
+        var el=icons.getEl();\r
+        this.toTopIcon = el.createChild({tag:'img', src:this.iconTop, style:{cursor:'pointer', margin:'2px'}});\r
+        el.createChild({tag: 'br'});\r
+        this.upIcon = el.createChild({tag:'img', src:this.iconUp, style:{cursor:'pointer', margin:'2px'}});\r
+        el.createChild({tag: 'br'});\r
+        this.addIcon = el.createChild({tag:'img', src:this.iconRight, style:{cursor:'pointer', margin:'2px'}});\r
+        el.createChild({tag: 'br'});\r
+        this.removeIcon = el.createChild({tag:'img', src:this.iconLeft, style:{cursor:'pointer', margin:'2px'}});\r
+        el.createChild({tag: 'br'});\r
+        this.downIcon = el.createChild({tag:'img', src:this.iconDown, style:{cursor:'pointer', margin:'2px'}});\r
+        el.createChild({tag: 'br'});\r
+        this.toBottomIcon = el.createChild({tag:'img', src:this.iconBottom, style:{cursor:'pointer', margin:'2px'}});\r
+        this.toTopIcon.on('click', this.toTop, this);\r
+        this.upIcon.on('click', this.up, this);\r
+        this.downIcon.on('click', this.down, this);\r
+        this.toBottomIcon.on('click', this.toBottom, this);\r
+        this.addIcon.on('click', this.fromTo, this);\r
+        this.removeIcon.on('click', this.toFrom, this);\r
+        if (!this.drawUpIcon || this.hideNavIcons) { this.upIcon.dom.style.display='none'; }\r
+        if (!this.drawDownIcon || this.hideNavIcons) { this.downIcon.dom.style.display='none'; }\r
+        if (!this.drawLeftIcon || this.hideNavIcons) { this.addIcon.dom.style.display='none'; }\r
+        if (!this.drawRightIcon || this.hideNavIcons) { this.removeIcon.dom.style.display='none'; }\r
+        if (!this.drawTopIcon || this.hideNavIcons) { this.toTopIcon.dom.style.display='none'; }\r
+        if (!this.drawBotIcon || this.hideNavIcons) { this.toBottomIcon.dom.style.display='none'; }\r
+\r
+        var tb = p.body.first();\r
+        this.el.setWidth(p.body.first().getWidth());\r
+        p.body.removeClass();\r
+\r
+        this.hiddenName = this.name;\r
+        var hiddenTag = {tag: "input", type: "hidden", value: "", name: this.name};\r
+        this.hiddenField = this.el.createChild(hiddenTag);\r
+    },\r
+    \r
+    doLayout: function(){\r
+        if(this.rendered){\r
+            this.fromMultiselect.fs.doLayout();\r
+            this.toMultiselect.fs.doLayout();\r
+        }\r
+    },\r
+\r
+    afterRender: function(){\r
+        Ext.ux.form.ItemSelector.superclass.afterRender.call(this);\r
+\r
+        this.toStore = this.toMultiselect.store;\r
+        this.toStore.on('add', this.valueChanged, this);\r
+        this.toStore.on('remove', this.valueChanged, this);\r
+        this.toStore.on('load', this.valueChanged, this);\r
+        this.valueChanged(this.toStore);\r
+    },\r
+\r
+    toTop : function() {\r
+        var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
+        var records = [];\r
+        if (selectionsArray.length > 0) {\r
+            selectionsArray.sort();\r
+            for (var i=0; i<selectionsArray.length; i++) {\r
+                record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
+                records.push(record);\r
+            }\r
+            selectionsArray = [];\r
+            for (var i=records.length-1; i>-1; i--) {\r
+                record = records[i];\r
+                this.toMultiselect.view.store.remove(record);\r
+                this.toMultiselect.view.store.insert(0, record);\r
+                selectionsArray.push(((records.length - 1) - i));\r
+            }\r
+        }\r
+        this.toMultiselect.view.refresh();\r
+        this.toMultiselect.view.select(selectionsArray);\r
+    },\r
+\r
+    toBottom : function() {\r
+        var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
+        var records = [];\r
+        if (selectionsArray.length > 0) {\r
+            selectionsArray.sort();\r
+            for (var i=0; i<selectionsArray.length; i++) {\r
+                record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
+                records.push(record);\r
+            }\r
+            selectionsArray = [];\r
+            for (var i=0; i<records.length; i++) {\r
+                record = records[i];\r
+                this.toMultiselect.view.store.remove(record);\r
+                this.toMultiselect.view.store.add(record);\r
+                selectionsArray.push((this.toMultiselect.view.store.getCount()) - (records.length - i));\r
+            }\r
+        }\r
+        this.toMultiselect.view.refresh();\r
+        this.toMultiselect.view.select(selectionsArray);\r
+    },\r
+\r
+    up : function() {\r
+        var record = null;\r
+        var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
+        selectionsArray.sort();\r
+        var newSelectionsArray = [];\r
+        if (selectionsArray.length > 0) {\r
+            for (var i=0; i<selectionsArray.length; i++) {\r
+                record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
+                if ((selectionsArray[i] - 1) >= 0) {\r
+                    this.toMultiselect.view.store.remove(record);\r
+                    this.toMultiselect.view.store.insert(selectionsArray[i] - 1, record);\r
+                    newSelectionsArray.push(selectionsArray[i] - 1);\r
+                }\r
+            }\r
+            this.toMultiselect.view.refresh();\r
+            this.toMultiselect.view.select(newSelectionsArray);\r
+        }\r
+    },\r
+\r
+    down : function() {\r
+        var record = null;\r
+        var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
+        selectionsArray.sort();\r
+        selectionsArray.reverse();\r
+        var newSelectionsArray = [];\r
+        if (selectionsArray.length > 0) {\r
+            for (var i=0; i<selectionsArray.length; i++) {\r
+                record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
+                if ((selectionsArray[i] + 1) < this.toMultiselect.view.store.getCount()) {\r
+                    this.toMultiselect.view.store.remove(record);\r
+                    this.toMultiselect.view.store.insert(selectionsArray[i] + 1, record);\r
+                    newSelectionsArray.push(selectionsArray[i] + 1);\r
+                }\r
+            }\r
+            this.toMultiselect.view.refresh();\r
+            this.toMultiselect.view.select(newSelectionsArray);\r
+        }\r
+    },\r
+\r
+    fromTo : function() {\r
+        var selectionsArray = this.fromMultiselect.view.getSelectedIndexes();\r
+        var records = [];\r
+        if (selectionsArray.length > 0) {\r
+            for (var i=0; i<selectionsArray.length; i++) {\r
+                record = this.fromMultiselect.view.store.getAt(selectionsArray[i]);\r
+                records.push(record);\r
+            }\r
+            if(!this.allowDup)selectionsArray = [];\r
+            for (var i=0; i<records.length; i++) {\r
+                record = records[i];\r
+                if(this.allowDup){\r
+                    var x=new Ext.data.Record();\r
+                    record.id=x.id;\r
+                    delete x;\r
+                    this.toMultiselect.view.store.add(record);\r
+                }else{\r
+                    this.fromMultiselect.view.store.remove(record);\r
+                    this.toMultiselect.view.store.add(record);\r
+                    selectionsArray.push((this.toMultiselect.view.store.getCount() - 1));\r
+                }\r
+            }\r
+        }\r
+        this.toMultiselect.view.refresh();\r
+        this.fromMultiselect.view.refresh();\r
+        var si = this.toMultiselect.store.sortInfo;\r
+        if(si){\r
+            this.toMultiselect.store.sort(si.field, si.direction);\r
+        }\r
+        this.toMultiselect.view.select(selectionsArray);\r
+    },\r
+\r
+    toFrom : function() {\r
+        var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
+        var records = [];\r
+        if (selectionsArray.length > 0) {\r
+            for (var i=0; i<selectionsArray.length; i++) {\r
+                record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
+                records.push(record);\r
+            }\r
+            selectionsArray = [];\r
+            for (var i=0; i<records.length; i++) {\r
+                record = records[i];\r
+                this.toMultiselect.view.store.remove(record);\r
+                if(!this.allowDup){\r
+                    this.fromMultiselect.view.store.add(record);\r
+                    selectionsArray.push((this.fromMultiselect.view.store.getCount() - 1));\r
+                }\r
+            }\r
+        }\r
+        this.fromMultiselect.view.refresh();\r
+        this.toMultiselect.view.refresh();\r
+        var si = this.fromMultiselect.store.sortInfo;\r
+        if (si){\r
+            this.fromMultiselect.store.sort(si.field, si.direction);\r
+        }\r
+        this.fromMultiselect.view.select(selectionsArray);\r
+    },\r
+\r
+    valueChanged: function(store) {\r
+        var record = null;\r
+        var values = [];\r
+        for (var i=0; i<store.getCount(); i++) {\r
+            record = store.getAt(i);\r
+            values.push(record.get(this.toMultiselect.valueField));\r
+        }\r
+        this.hiddenField.dom.value = values.join(this.delimiter);\r
+        this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);\r
+    },\r
+\r
+    getValue : function() {\r
+        return this.hiddenField.dom.value;\r
+    },\r
+\r
+    onRowDblClick : function(vw, index, node, e) {\r
+        if (vw == this.toMultiselect.view){\r
+            this.toFrom();\r
+        } else if (vw == this.fromMultiselect.view) {\r
+            this.fromTo();\r
+        }\r
+        return this.fireEvent('rowdblclick', vw, index, node, e);\r
+    },\r
+\r
+    reset: function(){\r
+        range = this.toMultiselect.store.getRange();\r
+        this.toMultiselect.store.removeAll();\r
+        this.fromMultiselect.store.add(range);\r
+        var si = this.fromMultiselect.store.sortInfo;\r
+        if (si){\r
+            this.fromMultiselect.store.sort(si.field, si.direction);\r
+        }\r
+        this.valueChanged(this.toMultiselect.store);\r
+    }\r
+});\r
+\r
+Ext.reg('itemselector', Ext.ux.form.ItemSelector);\r
+\r
+//backwards compat\r
+Ext.ux.ItemSelector = Ext.ux.form.ItemSelector;\r
+Ext.ns('Ext.ux.form');\r
+\r
+/**\r
+ * @class Ext.ux.form.MultiSelect\r
+ * @extends Ext.form.Field\r
+ * A control that allows selection and form submission of multiple list items.\r
+ *\r
+ *  @history\r
+ *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)\r
+ *    2008-06-19 bpm Docs and demo code clean up\r
+ *\r
+ * @constructor\r
+ * Create a new MultiSelect\r
+ * @param {Object} config Configuration options\r
+ * @xtype multiselect \r
+ */\r
+Ext.ux.form.MultiSelect = Ext.extend(Ext.form.Field,  {\r
+    /**\r
+     * @cfg {String} legend Wraps the object with a fieldset and specified legend.\r
+     */\r
+    /**\r
+     * @cfg {Ext.ListView} view The {@link Ext.ListView} used to render the multiselect list.\r
+     */\r
+    /**\r
+     * @cfg {String/Array} dragGroup The ddgroup name(s) for the MultiSelect DragZone (defaults to undefined).\r
+     */\r
+    /**\r
+     * @cfg {String/Array} dropGroup The ddgroup name(s) for the MultiSelect DropZone (defaults to undefined).\r
+     */\r
+    /**\r
+     * @cfg {Boolean} ddReorder Whether the items in the MultiSelect list are drag/drop reorderable (defaults to false).\r
+     */\r
+    ddReorder:false,\r
+    /**\r
+     * @cfg {Object/Array} tbar The top toolbar of the control. This can be a {@link Ext.Toolbar} object, a\r
+     * toolbar config, or an array of buttons/button configs to be added to the toolbar.\r
+     */\r
+    /**\r
+     * @cfg {String} appendOnly True if the list should only allow append drops when drag/drop is enabled\r
+     * (use for lists which are sorted, defaults to false).\r
+     */\r
+    appendOnly:false,\r
+    /**\r
+     * @cfg {Number} width Width in pixels of the control (defaults to 100).\r
+     */\r
+    width:100,\r
+    /**\r
+     * @cfg {Number} height Height in pixels of the control (defaults to 100).\r
+     */\r
+    height:100,\r
+    /**\r
+     * @cfg {String/Number} displayField Name/Index of the desired display field in the dataset (defaults to 0).\r
+     */\r
+    displayField:0,\r
+    /**\r
+     * @cfg {String/Number} valueField Name/Index of the desired value field in the dataset (defaults to 1).\r
+     */\r
+    valueField:1,\r
+    /**\r
+     * @cfg {Boolean} allowBlank False to require at least one item in the list to be selected, true to allow no\r
+     * selection (defaults to true).\r
+     */\r
+    allowBlank:true,\r
+    /**\r
+     * @cfg {Number} minSelections Minimum number of selections allowed (defaults to 0).\r
+     */\r
+    minSelections:0,\r
+    /**\r
+     * @cfg {Number} maxSelections Maximum number of selections allowed (defaults to Number.MAX_VALUE).\r
+     */\r
+    maxSelections:Number.MAX_VALUE,\r
+    /**\r
+     * @cfg {String} blankText Default text displayed when the control contains no items (defaults to the same value as\r
+     * {@link Ext.form.TextField#blankText}.\r
+     */\r
+    blankText:Ext.form.TextField.prototype.blankText,\r
+    /**\r
+     * @cfg {String} minSelectionsText Validation message displayed when {@link #minSelections} is not met (defaults to 'Minimum {0}\r
+     * item(s) required').  The {0} token will be replaced by the value of {@link #minSelections}.\r
+     */\r
+    minSelectionsText:'Minimum {0} item(s) required',\r
+    /**\r
+     * @cfg {String} maxSelectionsText Validation message displayed when {@link #maxSelections} is not met (defaults to 'Maximum {0}\r
+     * item(s) allowed').  The {0} token will be replaced by the value of {@link #maxSelections}.\r
+     */\r
+    maxSelectionsText:'Maximum {0} item(s) allowed',\r
+    /**\r
+     * @cfg {String} delimiter The string used to delimit between items when set or returned as a string of values\r
+     * (defaults to ',').\r
+     */\r
+    delimiter:',',\r
+    /**\r
+     * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to <tt>undefined</tt>).\r
+     * Acceptable values for this property are:\r
+     * <div class="mdetail-params"><ul>\r
+     * <li><b>any {@link Ext.data.Store Store} subclass</b></li>\r
+     * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.\r
+     * <div class="mdetail-params"><ul>\r
+     * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">\r
+     * A 1-dimensional array will automatically be expanded (each array item will be the combo\r
+     * {@link #valueField value} and {@link #displayField text})</div></li>\r
+     * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">\r
+     * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo\r
+     * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.\r
+     * </div></li></ul></div></li></ul></div>\r
+     */\r
+\r
+    // private\r
+    defaultAutoCreate : {tag: "div"},\r
+\r
+    // private\r
+    initComponent: function(){\r
+        Ext.ux.form.MultiSelect.superclass.initComponent.call(this);\r
+\r
+        if(Ext.isArray(this.store)){\r
+            if (Ext.isArray(this.store[0])){\r
+                this.store = new Ext.data.ArrayStore({\r
+                    fields: ['value','text'],\r
+                    data: this.store\r
+                });\r
+                this.valueField = 'value';\r
+            }else{\r
+                this.store = new Ext.data.ArrayStore({\r
+                    fields: ['text'],\r
+                    data: this.store,\r
+                    expandData: true\r
+                });\r
+                this.valueField = 'text';\r
+            }\r
+            this.displayField = 'text';\r
+        } else {\r
+            this.store = Ext.StoreMgr.lookup(this.store);\r
+        }\r
+\r
+        this.addEvents({\r
+            'dblclick' : true,\r
+            'click' : true,\r
+            'change' : true,\r
+            'drop' : true\r
+        });\r
+    },\r
+\r
+    // private\r
+    onRender: function(ct, position){\r
+        Ext.ux.form.MultiSelect.superclass.onRender.call(this, ct, position);\r
+\r
+        var fs = this.fs = new Ext.form.FieldSet({\r
+            renderTo: this.el,\r
+            title: this.legend,\r
+            height: this.height,\r
+            width: this.width,\r
+            style: "padding:0;",\r
+            tbar: this.tbar,\r
+            bodyStyle: 'overflow: auto;'\r
+        });\r
+\r
+        this.view = new Ext.ListView({\r
+            multiSelect: true,\r
+            store: this.store,\r
+            columns: [{ header: 'Value', width: 1, dataIndex: this.displayField }],\r
+            hideHeaders: true\r
+        });\r
+\r
+        fs.add(this.view);\r
+\r
+        this.view.on('click', this.onViewClick, this);\r
+        this.view.on('beforeclick', this.onViewBeforeClick, this);\r
+        this.view.on('dblclick', this.onViewDblClick, this);\r
+\r
+        this.hiddenName = this.name || Ext.id();\r
+        var hiddenTag = { tag: "input", type: "hidden", value: "", name: this.hiddenName };\r
+        this.hiddenField = this.el.createChild(hiddenTag);\r
+        this.hiddenField.dom.disabled = this.hiddenName != this.name;\r
+        fs.doLayout();\r
+    },\r
+\r
+    // private\r
+    afterRender: function(){\r
+        Ext.ux.form.MultiSelect.superclass.afterRender.call(this);\r
+\r
+        if (this.ddReorder && !this.dragGroup && !this.dropGroup){\r
+            this.dragGroup = this.dropGroup = 'MultiselectDD-' + Ext.id();\r
+        }\r
+\r
+        if (this.draggable || this.dragGroup){\r
+            this.dragZone = new Ext.ux.form.MultiSelect.DragZone(this, {\r
+                ddGroup: this.dragGroup\r
+            });\r
+        }\r
+        if (this.droppable || this.dropGroup){\r
+            this.dropZone = new Ext.ux.form.MultiSelect.DropZone(this, {\r
+                ddGroup: this.dropGroup\r
+            });\r
+        }\r
+    },\r
+\r
+    // private\r
+    onViewClick: function(vw, index, node, e) {\r
+        this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);\r
+        this.hiddenField.dom.value = this.getValue();\r
+        this.fireEvent('click', this, e);\r
+        this.validate();\r
+    },\r
+\r
+    // private\r
+    onViewBeforeClick: function(vw, index, node, e) {\r
+        if (this.disabled) {return false;}\r
+    },\r
+\r
+    // private\r
+    onViewDblClick : function(vw, index, node, e) {\r
+        return this.fireEvent('dblclick', vw, index, node, e);\r
+    },\r
+\r
+    /**\r
+     * Returns an array of data values for the selected items in the list. The values will be separated\r
+     * by {@link #delimiter}.\r
+     * @return {Array} value An array of string data values\r
+     */\r
+    getValue: function(valueField){\r
+        var returnArray = [];\r
+        var selectionsArray = this.view.getSelectedIndexes();\r
+        if (selectionsArray.length == 0) {return '';}\r
+        for (var i=0; i<selectionsArray.length; i++) {\r
+            returnArray.push(this.store.getAt(selectionsArray[i]).get((valueField != null) ? valueField : this.valueField));\r
+        }\r
+        return returnArray.join(this.delimiter);\r
+    },\r
+\r
+    /**\r
+     * Sets a delimited string (using {@link #delimiter}) or array of data values into the list.\r
+     * @param {String/Array} values The values to set\r
+     */\r
+    setValue: function(values) {\r
+        var index;\r
+        var selections = [];\r
+        this.view.clearSelections();\r
+        this.hiddenField.dom.value = '';\r
+\r
+        if (!values || (values == '')) { return; }\r
+\r
+        if (!Ext.isArray(values)) { values = values.split(this.delimiter); }\r
+        for (var i=0; i<values.length; i++) {\r
+            index = this.view.store.indexOf(this.view.store.query(this.valueField,\r
+                new RegExp('^' + values[i] + '$', "i")).itemAt(0));\r
+            selections.push(index);\r
+        }\r
+        this.view.select(selections);\r
+        this.hiddenField.dom.value = this.getValue();\r
+        this.validate();\r
+    },\r
+\r
+    // inherit docs\r
+    reset : function() {\r
+        this.setValue('');\r
+    },\r
+\r
+    // inherit docs\r
+    getRawValue: function(valueField) {\r
+        var tmp = this.getValue(valueField);\r
+        if (tmp.length) {\r
+            tmp = tmp.split(this.delimiter);\r
+        }\r
+        else {\r
+            tmp = [];\r
+        }\r
+        return tmp;\r
+    },\r
+\r
+    // inherit docs\r
+    setRawValue: function(values){\r
+        setValue(values);\r
+    },\r
+\r
+    // inherit docs\r
+    validateValue : function(value){\r
+        if (value.length < 1) { // if it has no value\r
+             if (this.allowBlank) {\r
+                 this.clearInvalid();\r
+                 return true;\r
+             } else {\r
+                 this.markInvalid(this.blankText);\r
+                 return false;\r
+             }\r
+        }\r
+        if (value.length < this.minSelections) {\r
+            this.markInvalid(String.format(this.minSelectionsText, this.minSelections));\r
+            return false;\r
+        }\r
+        if (value.length > this.maxSelections) {\r
+            this.markInvalid(String.format(this.maxSelectionsText, this.maxSelections));\r
+            return false;\r
+        }\r
+        return true;\r
+    },\r
+\r
+    // inherit docs\r
+    disable: function(){\r
+        this.disabled = true;\r
+        this.hiddenField.dom.disabled = true;\r
+        this.fs.disable();\r
+    },\r
+\r
+    // inherit docs\r
+    enable: function(){\r
+        this.disabled = false;\r
+        this.hiddenField.dom.disabled = false;\r
+        this.fs.enable();\r
+    },\r
+\r
+    // inherit docs\r
+    destroy: function(){\r
+        Ext.destroy(this.fs, this.dragZone, this.dropZone);\r
+        Ext.ux.form.MultiSelect.superclass.destroy.call(this);\r
+    }\r
+});\r
+\r
+\r
+Ext.reg('multiselect', Ext.ux.form.MultiSelect);\r
+\r
+//backwards compat\r
+Ext.ux.Multiselect = Ext.ux.form.MultiSelect;\r
+\r
+\r
+Ext.ux.form.MultiSelect.DragZone = function(ms, config){\r
+    this.ms = ms;\r
+    this.view = ms.view;\r
+    var ddGroup = config.ddGroup || 'MultiselectDD';\r
+    var dd;\r
+    if (Ext.isArray(ddGroup)){\r
+        dd = ddGroup.shift();\r
+    } else {\r
+        dd = ddGroup;\r
+        ddGroup = null;\r
+    }\r
+    Ext.ux.form.MultiSelect.DragZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });\r
+    this.setDraggable(ddGroup);\r
+};\r
+\r
+Ext.extend(Ext.ux.form.MultiSelect.DragZone, Ext.dd.DragZone, {\r
+    onInitDrag : function(x, y){\r
+        var el = Ext.get(this.dragData.ddel.cloneNode(true));\r
+        this.proxy.update(el.dom);\r
+        el.setWidth(el.child('em').getWidth());\r
+        this.onStartDrag(x, y);\r
+        return true;\r
+    },\r
+    \r
+    // private\r
+    collectSelection: function(data) {\r
+        data.repairXY = Ext.fly(this.view.getSelectedNodes()[0]).getXY();\r
+        var i = 0;\r
+        this.view.store.each(function(rec){\r
+            if (this.view.isSelected(i)) {\r
+                var n = this.view.getNode(i);\r
+                var dragNode = n.cloneNode(true);\r
+                dragNode.id = Ext.id();\r
+                data.ddel.appendChild(dragNode);\r
+                data.records.push(this.view.store.getAt(i));\r
+                data.viewNodes.push(n);\r
+            }\r
+            i++;\r
+        }, this);\r
+    },\r
+\r
+    // override\r
+    onEndDrag: function(data, e) {\r
+        var d = Ext.get(this.dragData.ddel);\r
+        if (d && d.hasClass("multi-proxy")) {\r
+            d.remove();\r
+        }\r
+    },\r
+\r
+    // override\r
+    getDragData: function(e){\r
+        var target = this.view.findItemFromChild(e.getTarget());\r
+        if(target) {\r
+            if (!this.view.isSelected(target) && !e.ctrlKey && !e.shiftKey) {\r
+                this.view.select(target);\r
+                this.ms.setValue(this.ms.getValue());\r
+            }\r
+            if (this.view.getSelectionCount() == 0 || e.ctrlKey || e.shiftKey) return false;\r
+            var dragData = {\r
+                sourceView: this.view,\r
+                viewNodes: [],\r
+                records: []\r
+            };\r
+            if (this.view.getSelectionCount() == 1) {\r
+                var i = this.view.getSelectedIndexes()[0];\r
+                var n = this.view.getNode(i);\r
+                dragData.viewNodes.push(dragData.ddel = n);\r
+                dragData.records.push(this.view.store.getAt(i));\r
+                dragData.repairXY = Ext.fly(n).getXY();\r
+            } else {\r
+                dragData.ddel = document.createElement('div');\r
+                dragData.ddel.className = 'multi-proxy';\r
+                this.collectSelection(dragData);\r
+            }\r
+            return dragData;\r
+        }\r
+        return false;\r
+    },\r
+\r
+    // override the default repairXY.\r
+    getRepairXY : function(e){\r
+        return this.dragData.repairXY;\r
+    },\r
+\r
+    // private\r
+    setDraggable: function(ddGroup){\r
+        if (!ddGroup) return;\r
+        if (Ext.isArray(ddGroup)) {\r
+            Ext.each(ddGroup, this.setDraggable, this);\r
+            return;\r
+        }\r
+        this.addToGroup(ddGroup);\r
+    }\r
+});\r
+\r
+Ext.ux.form.MultiSelect.DropZone = function(ms, config){\r
+    this.ms = ms;\r
+    this.view = ms.view;\r
+    var ddGroup = config.ddGroup || 'MultiselectDD';\r
+    var dd;\r
+    if (Ext.isArray(ddGroup)){\r
+        dd = ddGroup.shift();\r
+    } else {\r
+        dd = ddGroup;\r
+        ddGroup = null;\r
+    }\r
+    Ext.ux.form.MultiSelect.DropZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });\r
+    this.setDroppable(ddGroup);\r
+};\r
+\r
+Ext.extend(Ext.ux.form.MultiSelect.DropZone, Ext.dd.DropZone, {\r
+    /**\r
+        * Part of the Ext.dd.DropZone interface. If no target node is found, the\r
+        * whole Element becomes the target, and this causes the drop gesture to append.\r
+        */\r
+    getTargetFromEvent : function(e) {\r
+        var target = e.getTarget();\r
+        return target;\r
+    },\r
+\r
+    // private\r
+    getDropPoint : function(e, n, dd){\r
+        if (n == this.ms.fs.body.dom) { return "below"; }\r
+        var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight;\r
+        var c = t + (b - t) / 2;\r
+        var y = Ext.lib.Event.getPageY(e);\r
+        if(y <= c) {\r
+            return "above";\r
+        }else{\r
+            return "below";\r
+        }\r
+    },\r
+\r
+    // private\r
+    isValidDropPoint: function(pt, n, data) {\r
+        if (!data.viewNodes || (data.viewNodes.length != 1)) {\r
+            return true;\r
+        }\r
+        var d = data.viewNodes[0];\r
+        if (d == n) {\r
+            return false;\r
+        }\r
+        if ((pt == "below") && (n.nextSibling == d)) {\r
+            return false;\r
+        }\r
+        if ((pt == "above") && (n.previousSibling == d)) {\r
+            return false;\r
+        }\r
+        return true;\r
+    },\r
+\r
+    // override\r
+    onNodeEnter : function(n, dd, e, data){\r
+        return false;\r
+    },\r
+\r
+    // override\r
+    onNodeOver : function(n, dd, e, data){\r
+        var dragElClass = this.dropNotAllowed;\r
+        var pt = this.getDropPoint(e, n, dd);\r
+        if (this.isValidDropPoint(pt, n, data)) {\r
+            if (this.ms.appendOnly) {\r
+                return "x-tree-drop-ok-below";\r
+            }\r
+\r
+            // set the insert point style on the target node\r
+            if (pt) {\r
+                var targetElClass;\r
+                if (pt == "above"){\r
+                    dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";\r
+                    targetElClass = "x-view-drag-insert-above";\r
+                } else {\r
+                    dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";\r
+                    targetElClass = "x-view-drag-insert-below";\r
+                }\r
+                if (this.lastInsertClass != targetElClass){\r
+                    Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass);\r
+                    this.lastInsertClass = targetElClass;\r
+                }\r
+            }\r
+        }\r
+        return dragElClass;\r
+    },\r
+\r
+    // private\r
+    onNodeOut : function(n, dd, e, data){\r
+        this.removeDropIndicators(n);\r
+    },\r
+\r
+    // private\r
+    onNodeDrop : function(n, dd, e, data){\r
+        if (this.ms.fireEvent("drop", this, n, dd, e, data) === false) {\r
+            return false;\r
+        }\r
+        var pt = this.getDropPoint(e, n, dd);\r
+        if (n != this.ms.fs.body.dom)\r
+            n = this.view.findItemFromChild(n);\r
+        var insertAt = (this.ms.appendOnly || (n == this.ms.fs.body.dom)) ? this.view.store.getCount() : this.view.indexOf(n);\r
+        if (pt == "below") {\r
+            insertAt++;\r
+        }\r
+\r
+        var dir = false;\r
+\r
+        // Validate if dragging within the same MultiSelect\r
+        if (data.sourceView == this.view) {\r
+            // If the first element to be inserted below is the target node, remove it\r
+            if (pt == "below") {\r
+                if (data.viewNodes[0] == n) {\r
+                    data.viewNodes.shift();\r
+                }\r
+            } else {  // If the last element to be inserted above is the target node, remove it\r
+                if (data.viewNodes[data.viewNodes.length - 1] == n) {\r
+                    data.viewNodes.pop();\r
+                }\r
+            }\r
+\r
+            // Nothing to drop...\r
+            if (!data.viewNodes.length) {\r
+                return false;\r
+            }\r
+\r
+            // If we are moving DOWN, then because a store.remove() takes place first,\r
+            // the insertAt must be decremented.\r
+            if (insertAt > this.view.store.indexOf(data.records[0])) {\r
+                dir = 'down';\r
+                insertAt--;\r
+            }\r
+        }\r
+\r
+        for (var i = 0; i < data.records.length; i++) {\r
+            var r = data.records[i];\r
+            if (data.sourceView) {\r
+                data.sourceView.store.remove(r);\r
+            }\r
+            this.view.store.insert(dir == 'down' ? insertAt : insertAt++, r);\r
+            var si = this.view.store.sortInfo;\r
+            if(si){\r
+                this.view.store.sort(si.field, si.direction);\r
+            }\r
+        }\r
+        return true;\r
+    },\r
+\r
+    // private\r
+    removeDropIndicators : function(n){\r
+        if(n){\r
+            Ext.fly(n).removeClass([\r
+                "x-view-drag-insert-above",\r
+                "x-view-drag-insert-left",\r
+                "x-view-drag-insert-right",\r
+                "x-view-drag-insert-below"]);\r
+            this.lastInsertClass = "_noclass";\r
+        }\r
+    },\r
+\r
+    // private\r
+    setDroppable: function(ddGroup){\r
+        if (!ddGroup) return;\r
+        if (Ext.isArray(ddGroup)) {\r
+            Ext.each(ddGroup, this.setDroppable, this);\r
+            return;\r
+        }\r
+        this.addToGroup(ddGroup);\r
+    }\r
+});\r
+
+/* Fix for Opera, which does not seem to include the map function on Array's */
+if (!Array.prototype.map) {
+    Array.prototype.map = function(fun){
+        var len = this.length;
+        if (typeof fun != 'function') {
+            throw new TypeError();
+        }
+        var res = new Array(len);
+        var thisp = arguments[1];
+        for (var i = 0; i < len; i++) {
+            if (i in this) {
+                res[i] = fun.call(thisp, this[i], i, this);
+            }
+        }
+        return res;
+    };
+}
+
+Ext.ns('Ext.ux.data');
+
+/**
+ * @class Ext.ux.data.PagingMemoryProxy
+ * @extends Ext.data.MemoryProxy
+ * <p>Paging Memory Proxy, allows to use paging grid with in memory dataset</p>
+ */
+Ext.ux.data.PagingMemoryProxy = Ext.extend(Ext.data.MemoryProxy, {
+    constructor : function(data){
+        Ext.ux.data.PagingMemoryProxy.superclass.constructor.call(this);
+        this.data = data;
+    },
+    doRequest : function(action, rs, params, reader, callback, scope, options){
+        params = params ||
+        {};
+        var result;
+        try {
+            result = reader.readRecords(this.data);
+        } 
+        catch (e) {
+            this.fireEvent('loadexception', this, options, null, e);
+            callback.call(scope, null, options, false);
+            return;
+        }
+        
+        // filtering
+        if (params.filter !== undefined) {
+            result.records = result.records.filter(function(el){
+                if (typeof(el) == 'object') {
+                    var att = params.filterCol || 0;
+                    return String(el.data[att]).match(params.filter) ? true : false;
+                }
+                else {
+                    return String(el).match(params.filter) ? true : false;
+                }
+            });
+            result.totalRecords = result.records.length;
+        }
+        
+        // sorting
+        if (params.sort !== undefined) {
+            // use integer as params.sort to specify column, since arrays are not named
+            // params.sort=0; would also match a array without columns
+            var dir = String(params.dir).toUpperCase() == 'DESC' ? -1 : 1;
+            var fn = function(r1, r2){
+                return r1 < r2;
+            };
+            result.records.sort(function(a, b){
+                var v = 0;
+                if (typeof(a) == 'object') {
+                    v = fn(a.data[params.sort], b.data[params.sort]) * dir;
+                }
+                else {
+                    v = fn(a, b) * dir;
+                }
+                if (v == 0) {
+                    v = (a.index < b.index ? -1 : 1);
+                }
+                return v;
+            });
+        }
+        // paging (use undefined cause start can also be 0 (thus false))
+        if (params.start !== undefined && params.limit !== undefined) {
+            result.records = result.records.slice(params.start, params.start + params.limit);
+        }
+        callback.call(scope, result, options, true);
+    }
+});
+
+//backwards compat.
+Ext.data.PagingMemoryProxy = Ext.ux.data.PagingMemoryProxy;
+Ext.ux.PanelResizer = Ext.extend(Ext.util.Observable, {\r
+    minHeight: 0,\r
+    maxHeight:10000000,\r
+\r
+    constructor: function(config){\r
+        Ext.apply(this, config);\r
+        this.events = {};\r
+        Ext.ux.PanelResizer.superclass.constructor.call(this, config);\r
+    },\r
+\r
+    init : function(p){\r
+        this.panel = p;\r
+\r
+        if(this.panel.elements.indexOf('footer')==-1){\r
+            p.elements += ',footer';\r
+        }\r
+        p.on('render', this.onRender, this);\r
+    },\r
+\r
+    onRender : function(p){\r
+        this.handle = p.footer.createChild({cls:'x-panel-resize'});\r
+\r
+        this.tracker = new Ext.dd.DragTracker({\r
+            onStart: this.onDragStart.createDelegate(this),\r
+            onDrag: this.onDrag.createDelegate(this),\r
+            onEnd: this.onDragEnd.createDelegate(this),\r
+            tolerance: 3,\r
+            autoStart: 300\r
+        });\r
+        this.tracker.initEl(this.handle);\r
+        p.on('beforedestroy', this.tracker.destroy, this.tracker);\r
+    },\r
+\r
+       // private\r
+    onDragStart: function(e){\r
+        this.dragging = true;\r
+        this.startHeight = this.panel.el.getHeight();\r
+        this.fireEvent('dragstart', this, e);\r
+    },\r
+\r
+       // private\r
+    onDrag: function(e){\r
+        this.panel.setHeight((this.startHeight-this.tracker.getOffset()[1]).constrain(this.minHeight, this.maxHeight));\r
+        this.fireEvent('drag', this, e);\r
+    },\r
+\r
+       // private\r
+    onDragEnd: function(e){\r
+        this.dragging = false;\r
+        this.fireEvent('dragend', this, e);\r
+    }\r
+});\r
+Ext.preg('panelresizer', Ext.ux.PanelResizer);Ext.ux.Portal = Ext.extend(Ext.Panel, {\r
+    layout : 'column',\r
+    autoScroll : true,\r
+    cls : 'x-portal',\r
+    defaultType : 'portalcolumn',\r
+    \r
+    initComponent : function(){\r
+        Ext.ux.Portal.superclass.initComponent.call(this);\r
+        this.addEvents({\r
+            validatedrop:true,\r
+            beforedragover:true,\r
+            dragover:true,\r
+            beforedrop:true,\r
+            drop:true\r
+        });\r
+    },\r
+\r
+    initEvents : function(){\r
+        Ext.ux.Portal.superclass.initEvents.call(this);\r
+        this.dd = new Ext.ux.Portal.DropZone(this, this.dropConfig);\r
+    },\r
+    \r
+    beforeDestroy : function() {\r
+        if(this.dd){\r
+            this.dd.unreg();\r
+        }\r
+        Ext.ux.Portal.superclass.beforeDestroy.call(this);\r
+    }\r
+});\r
+\r
+Ext.reg('portal', Ext.ux.Portal);\r
+\r
+\r
+Ext.ux.Portal.DropZone = function(portal, cfg){\r
+    this.portal = portal;\r
+    Ext.dd.ScrollManager.register(portal.body);\r
+    Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg);\r
+    portal.body.ddScrollConfig = this.ddScrollConfig;\r
+};\r
+\r
+Ext.extend(Ext.ux.Portal.DropZone, Ext.dd.DropTarget, {\r
+    ddScrollConfig : {\r
+        vthresh: 50,\r
+        hthresh: -1,\r
+        animate: true,\r
+        increment: 200\r
+    },\r
+\r
+    createEvent : function(dd, e, data, col, c, pos){\r
+        return {\r
+            portal: this.portal,\r
+            panel: data.panel,\r
+            columnIndex: col,\r
+            column: c,\r
+            position: pos,\r
+            data: data,\r
+            source: dd,\r
+            rawEvent: e,\r
+            status: this.dropAllowed\r
+        };\r
+    },\r
+\r
+    notifyOver : function(dd, e, data){\r
+        var xy = e.getXY(), portal = this.portal, px = dd.proxy;\r
+\r
+        // case column widths\r
+        if(!this.grid){\r
+            this.grid = this.getGrid();\r
+        }\r
+\r
+        // handle case scroll where scrollbars appear during drag\r
+        var cw = portal.body.dom.clientWidth;\r
+        if(!this.lastCW){\r
+            this.lastCW = cw;\r
+        }else if(this.lastCW != cw){\r
+            this.lastCW = cw;\r
+            portal.doLayout();\r
+            this.grid = this.getGrid();\r
+        }\r
+\r
+        // determine column\r
+        var col = 0, xs = this.grid.columnX, cmatch = false;\r
+        for(var len = xs.length; col < len; col++){\r
+            if(xy[0] < (xs[col].x + xs[col].w)){\r
+                cmatch = true;\r
+                break;\r
+            }\r
+        }\r
+        // no match, fix last index\r
+        if(!cmatch){\r
+            col--;\r
+        }\r
+\r
+        // find insert position\r
+        var p, match = false, pos = 0,\r
+            c = portal.items.itemAt(col),\r
+            items = c.items.items, overSelf = false;\r
+\r
+        for(var len = items.length; pos < len; pos++){\r
+            p = items[pos];\r
+            var h = p.el.getHeight();\r
+            if(h === 0){\r
+                overSelf = true;\r
+            }\r
+            else if((p.el.getY()+(h/2)) > xy[1]){\r
+                match = true;\r
+                break;\r
+            }\r
+        }\r
+\r
+        pos = (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0);\r
+        var overEvent = this.createEvent(dd, e, data, col, c, pos);\r
+\r
+        if(portal.fireEvent('validatedrop', overEvent) !== false &&\r
+           portal.fireEvent('beforedragover', overEvent) !== false){\r
+\r
+            // make sure proxy width is fluid\r
+            px.getProxy().setWidth('auto');\r
+\r
+            if(p){\r
+                px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null);\r
+            }else{\r
+                px.moveProxy(c.el.dom, null);\r
+            }\r
+\r
+            this.lastPos = {c: c, col: col, p: overSelf || (match && p) ? pos : false};\r
+            this.scrollPos = portal.body.getScroll();\r
+\r
+            portal.fireEvent('dragover', overEvent);\r
+\r
+            return overEvent.status;\r
+        }else{\r
+            return overEvent.status;\r
+        }\r
+\r
+    },\r
+\r
+    notifyOut : function(){\r
+        delete this.grid;\r
+    },\r
+\r
+    notifyDrop : function(dd, e, data){\r
+        delete this.grid;\r
+        if(!this.lastPos){\r
+            return;\r
+        }\r
+        var c = this.lastPos.c, col = this.lastPos.col, pos = this.lastPos.p;\r
+\r
+        var dropEvent = this.createEvent(dd, e, data, col, c,\r
+            pos !== false ? pos : c.items.getCount());\r
+\r
+        if(this.portal.fireEvent('validatedrop', dropEvent) !== false &&\r
+           this.portal.fireEvent('beforedrop', dropEvent) !== false){\r
+\r
+            dd.proxy.getProxy().remove();\r
+            dd.panel.el.dom.parentNode.removeChild(dd.panel.el.dom);\r
+            \r
+            if(pos !== false){\r
+                if(c == dd.panel.ownerCt && (c.items.items.indexOf(dd.panel) <= pos)){\r
+                    pos++;\r
+                }\r
+                c.insert(pos, dd.panel);\r
+            }else{\r
+                c.add(dd.panel);\r
+            }\r
+            \r
+            c.doLayout();\r
+\r
+            this.portal.fireEvent('drop', dropEvent);\r
+\r
+            // scroll position is lost on drop, fix it\r
+            var st = this.scrollPos.top;\r
+            if(st){\r
+                var d = this.portal.body.dom;\r
+                setTimeout(function(){\r
+                    d.scrollTop = st;\r
+                }, 10);\r
+            }\r
+\r
+        }\r
+        delete this.lastPos;\r
+    },\r
+\r
+    // internal cache of body and column coords\r
+    getGrid : function(){\r
+        var box = this.portal.bwrap.getBox();\r
+        box.columnX = [];\r
+        this.portal.items.each(function(c){\r
+             box.columnX.push({x: c.el.getX(), w: c.el.getWidth()});\r
+        });\r
+        return box;\r
+    },\r
+\r
+    // unregister the dropzone from ScrollManager\r
+    unreg: function() {\r
+        //Ext.dd.ScrollManager.unregister(this.portal.body);\r
+        Ext.ux.Portal.DropZone.superclass.unreg.call(this);\r
+    }\r
+});\r
+Ext.ux.PortalColumn = Ext.extend(Ext.Container, {\r
+    layout : 'anchor',\r
+    //autoEl : 'div',//already defined by Ext.Component\r
+    defaultType : 'portlet',\r
+    cls : 'x-portal-column'\r
+});\r
+\r
+Ext.reg('portalcolumn', Ext.ux.PortalColumn);\r
+Ext.ux.Portlet = Ext.extend(Ext.Panel, {\r
+    anchor : '100%',\r
+    frame : true,\r
+    collapsible : true,\r
+    draggable : true,\r
+    cls : 'x-portlet'\r
+});\r
+\r
+Ext.reg('portlet', Ext.ux.Portlet);\r
+/**
+* @class Ext.ux.ProgressBarPager
+* @extends Object 
+* Plugin (ptype = 'tabclosemenu') for displaying a progressbar inside of a paging toolbar instead of plain text
+* 
+* @ptype progressbarpager 
+* @constructor
+* Create a new ItemSelector
+* @param {Object} config Configuration options
+* @xtype itemselector 
+*/
+Ext.ux.ProgressBarPager  = Ext.extend(Object, {
+       /**
+       * @cfg {Integer} progBarWidth
+       * <p>The default progress bar width.  Default is 225.</p>
+       */
+       progBarWidth   : 225,
+       /**
+       * @cfg {String} defaultText
+       * <p>The text to display while the store is loading.  Default is 'Loading...'</p>
+       */
+       defaultText    : 'Loading...',
+       /**
+       * @cfg {Object} defaultAnimCfg 
+       * <p>A {@link Ext.Fx Ext.Fx} configuration object.  Default is  { duration : 1, easing : 'bounceOut' }.</p>
+       */
+       defaultAnimCfg : {
+               duration   : 1,
+               easing     : 'bounceOut'        
+       },                                                                                                
+       constructor : function(config) {
+               if (config) {
+                       Ext.apply(this, config);
+               }
+       },
+       //public
+       init : function (parent) {
+               
+               if(parent.displayInfo){
+                       this.parent = parent;
+                       var ind  = parent.items.indexOf(parent.displayItem);
+                       parent.remove(parent.displayItem, true);
+                       this.progressBar = new Ext.ProgressBar({
+                               text    : this.defaultText,
+                               width   : this.progBarWidth,
+                               animate :  this.defaultAnimCfg
+                       });                                     
+                  
+                       parent.displayItem = this.progressBar;
+                       
+                       parent.add(parent.displayItem); 
+                       parent.doLayout();
+                       Ext.apply(parent, this.parentOverrides);                
+                       
+                       this.progressBar.on('render', function(pb) {
+                               pb.el.applyStyles('cursor:pointer');
+
+                               pb.el.on('click', this.handleProgressBarClick, this);
+                       }, this);
+                       
+               
+                       // Remove the click handler from the 
+                       this.progressBar.on({
+                               scope         : this,
+                               beforeDestroy : function() {
+                                       this.progressBar.el.un('click', this.handleProgressBarClick, this);     
+                               }
+                       });     
+                                               
+               }
+                 
+       },
+       // private
+       // This method handles the click for the progress bar
+       handleProgressBarClick : function(e){
+               var parent = this.parent;
+               var displayItem = parent.displayItem;
+               
+               var box = this.progressBar.getBox();
+               var xy = e.getXY();
+               var position = xy[0]-box.x;
+               var pages = Math.ceil(parent.store.getTotalCount()/parent.pageSize);
+               
+               var newpage = Math.ceil(position/(displayItem.width/pages));
+               parent.changePage(newpage);
+       },
+       
+       // private, overriddes
+       parentOverrides  : {
+               // private
+               // This method updates the information via the progress bar.
+               updateInfo : function(){
+                       if(this.displayItem){
+                               var count   = this.store.getCount();
+                               var pgData  = this.getPageData();
+                               var pageNum = this.readPage(pgData);
+                               
+                               var msg    = count == 0 ?
+                                       this.emptyMsg :
+                                       String.format(
+                                               this.displayMsg,
+                                               this.cursor+1, this.cursor+count, this.store.getTotalCount()
+                                       );
+                                       
+                               pageNum = pgData.activePage; ;  
+                               
+                               var pct = pageNum / pgData.pages;       
+                               
+                               this.displayItem.updateProgress(pct, msg, this.animate || this.defaultAnimConfig);
+                       }
+               }
+       }
+});
+Ext.preg('progressbarpager', Ext.ux.ProgressBarPager);
+
+Ext.ns('Ext.ux.grid');
+
+/**
+ * @class Ext.ux.grid.RowEditor
+ * @extends Ext.Panel 
+ * Plugin (ptype = 'roweditor') that adds the ability to rapidly edit full rows in a grid.
+ * A validation mode may be enabled which uses AnchorTips to notify the user of all
+ * validation errors at once.
+ * 
+ * @ptype roweditor
+ */
+Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
+    floating: true,
+    shadow: false,
+    layout: 'hbox',
+    cls: 'x-small-editor',
+    buttonAlign: 'center',
+    baseCls: 'x-row-editor',
+    elements: 'header,footer,body',
+    frameWidth: 5,
+    buttonPad: 3,
+    clicksToEdit: 'auto',
+    monitorValid: true,
+    focusDelay: 250,
+    errorSummary: true,
+
+    defaults: {
+        normalWidth: true
+    },
+
+    initComponent: function(){
+        Ext.ux.grid.RowEditor.superclass.initComponent.call(this);
+        this.addEvents(
+            /**
+             * @event beforeedit
+             * Fired before the row editor is activated.
+             * If the listener returns <tt>false</tt> the editor will not be activated.
+             * @param {Ext.ux.grid.RowEditor} roweditor This object
+             * @param {Number} rowIndex The rowIndex of the row just edited
+             */
+            'beforeedit',
+            /**
+             * @event validateedit
+             * Fired after a row is edited and passes validation.
+             * If the listener returns <tt>false</tt> changes to the record will not be set.
+             * @param {Ext.ux.grid.RowEditor} roweditor This object
+             * @param {Object} changes Object with changes made to the record.
+             * @param {Ext.data.Record} r The Record that was edited.
+             * @param {Number} rowIndex The rowIndex of the row just edited
+             */
+            'validateedit',
+            /**
+             * @event afteredit
+             * Fired after a row is edited and passes validation.  This event is fired
+             * after the store's update event is fired with this edit.
+             * @param {Ext.ux.grid.RowEditor} roweditor This object
+             * @param {Object} changes Object with changes made to the record.
+             * @param {Ext.data.Record} r The Record that was edited.
+             * @param {Number} rowIndex The rowIndex of the row just edited
+             */
+            'afteredit'
+        );
+    },
+
+    init: function(grid){
+        this.grid = grid;
+        this.ownerCt = grid;
+        if(this.clicksToEdit === 2){
+            grid.on('rowdblclick', this.onRowDblClick, this);
+        }else{
+            grid.on('rowclick', this.onRowClick, this);
+            if(Ext.isIE){
+                grid.on('rowdblclick', this.onRowDblClick, this);
+            }
+        }
+
+        // stopEditing without saving when a record is removed from Store.
+        grid.getStore().on('remove', function() {
+            this.stopEditing(false);
+        },this);
+
+        grid.on({
+            scope: this,
+            keydown: this.onGridKey,
+            columnresize: this.verifyLayout,
+            columnmove: this.refreshFields,
+            reconfigure: this.refreshFields,
+           destroy : this.destroy,
+            bodyscroll: {
+                buffer: 250,
+                fn: this.positionButtons
+            }
+        });
+        grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {delay:1});
+        grid.getView().on('refresh', this.stopEditing.createDelegate(this, []));
+    },
+
+    refreshFields: function(){
+        this.initFields();
+        this.verifyLayout();
+    },
+
+    isDirty: function(){
+        var dirty;
+        this.items.each(function(f){
+            if(String(this.values[f.id]) !== String(f.getValue())){
+                dirty = true;
+                return false;
+            }
+        }, this);
+        return dirty;
+    },
+
+    startEditing: function(rowIndex, doFocus){
+        if(this.editing && this.isDirty()){
+            this.showTooltip('You need to commit or cancel your changes');
+            return;
+        }
+        this.editing = true;
+        if(typeof rowIndex == 'object'){
+            rowIndex = this.grid.getStore().indexOf(rowIndex);
+        }
+        if(this.fireEvent('beforeedit', this, rowIndex) !== false){
+            var g = this.grid, view = g.getView();
+            var row = view.getRow(rowIndex);
+            var record = g.store.getAt(rowIndex);
+            this.record = record;
+            this.rowIndex = rowIndex;
+            this.values = {};
+            if(!this.rendered){
+                this.render(view.getEditorParent());
+            }
+            var w = Ext.fly(row).getWidth();
+            this.setSize(w);
+            if(!this.initialized){
+                this.initFields();
+            }
+            var cm = g.getColumnModel(), fields = this.items.items, f, val;
+            for(var i = 0, len = cm.getColumnCount(); i < len; i++){
+                val = this.preEditValue(record, cm.getDataIndex(i));
+                f = fields[i];
+                f.setValue(val);
+                this.values[f.id] = val || '';
+            }
+            this.verifyLayout(true);
+            if(!this.isVisible()){
+                this.setPagePosition(Ext.fly(row).getXY());
+            } else{
+                this.el.setXY(Ext.fly(row).getXY(), {duration:0.15});
+            }
+            if(!this.isVisible()){
+                this.show().doLayout();
+            }
+            if(doFocus !== false){
+                this.doFocus.defer(this.focusDelay, this);
+            }
+        }
+    },
+
+    stopEditing : function(saveChanges){
+        this.editing = false;
+        if(!this.isVisible()){
+            return;
+        }
+        if(saveChanges === false || !this.isValid()){
+            this.hide();
+            return;
+        }
+        var changes = {}, r = this.record, hasChange = false;
+        var cm = this.grid.colModel, fields = this.items.items;
+        for(var i = 0, len = cm.getColumnCount(); i < len; i++){
+            if(!cm.isHidden(i)){
+                var dindex = cm.getDataIndex(i);
+                if(!Ext.isEmpty(dindex)){
+                    var oldValue = r.data[dindex];
+                    var value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
+                    if(String(oldValue) !== String(value)){
+                        changes[dindex] = value;
+                        hasChange = true;
+                    }
+                }
+            }
+        }
+        if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){
+            r.beginEdit();
+            for(var k in changes){
+                if(changes.hasOwnProperty(k)){
+                    r.set(k, changes[k]);
+                }
+            }
+            r.endEdit();
+            this.fireEvent('afteredit', this, changes, r, this.rowIndex);
+        }
+        this.hide();
+    },
+
+    verifyLayout: function(force){
+        if(this.el && (this.isVisible() || force === true)){
+            var row = this.grid.getView().getRow(this.rowIndex);
+            this.setSize(Ext.fly(row).getWidth(), Ext.isIE ? Ext.fly(row).getHeight() + (Ext.isBorderBox ? 9 : 0) : undefined);
+            var cm = this.grid.colModel, fields = this.items.items;
+            for(var i = 0, len = cm.getColumnCount(); i < len; i++){
+                if(!cm.isHidden(i)){
+                    var adjust = 0;
+                    if(i === 0){
+                        adjust += 0; // outer padding
+                    }
+                    if(i === (len - 1)){
+                        adjust += 3; // outer padding
+                    } else{
+                        adjust += 1;
+                    }
+                    fields[i].show();
+                    fields[i].setWidth(cm.getColumnWidth(i) - adjust);
+                } else{
+                    fields[i].hide();
+                }
+            }
+            this.doLayout();
+            this.positionButtons();
+        }
+    },
+
+    slideHide : function(){
+        this.hide();
+    },
+
+    initFields: function(){
+        var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
+        this.removeAll(false);
+        for(var i = 0, len = cm.getColumnCount(); i < len; i++){
+            var c = cm.getColumnAt(i);
+            var ed = c.getEditor();
+            if(!ed){
+                ed = c.displayEditor || new Ext.form.DisplayField();
+            }
+            if(i == 0){
+                ed.margins = pm('0 1 2 1');
+            } else if(i == len - 1){
+                ed.margins = pm('0 0 2 1');
+            } else{
+                ed.margins = pm('0 1 2');
+            }
+            ed.setWidth(cm.getColumnWidth(i));
+            ed.column = c;
+            if(ed.ownerCt !== this){
+                ed.on('focus', this.ensureVisible, this);
+                ed.on('specialkey', this.onKey, this);
+            }
+            this.insert(i, ed);
+        }
+        this.initialized = true;
+    },
+
+    onKey: function(f, e){
+        if(e.getKey() === e.ENTER){
+            this.stopEditing(true);
+            e.stopPropagation();
+        }
+    },
+
+    onGridKey: function(e){
+        if(e.getKey() === e.ENTER && !this.isVisible()){
+            var r = this.grid.getSelectionModel().getSelected();
+            if(r){
+                var index = this.grid.store.indexOf(r);
+                this.startEditing(index);
+                e.stopPropagation();
+            }
+        }
+    },
+
+    ensureVisible: function(editor){
+        if(this.isVisible()){
+             this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
+        }
+    },
+
+    onRowClick: function(g, rowIndex, e){
+        if(this.clicksToEdit == 'auto'){
+            var li = this.lastClickIndex;
+            this.lastClickIndex = rowIndex;
+            if(li != rowIndex && !this.isVisible()){
+                return;
+            }
+        }
+        this.startEditing(rowIndex, false);
+        this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
+    },
+
+    onRowDblClick: function(g, rowIndex, e){
+        this.startEditing(rowIndex, false);
+        this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
+    },
+
+    onRender: function(){
+        Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments);
+        this.el.swallowEvent(['keydown', 'keyup', 'keypress']);
+        this.btns = new Ext.Panel({
+            baseCls: 'x-plain',
+            cls: 'x-btns',
+            elements:'body',
+            layout: 'table',
+            width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE
+            items: [{
+                ref: 'saveBtn',
+                itemId: 'saveBtn',
+                xtype: 'button',
+                text: this.saveText || 'Save',
+                width: this.minButtonWidth,
+                handler: this.stopEditing.createDelegate(this, [true])
+            }, {
+                xtype: 'button',
+                text: this.cancelText || 'Cancel',
+                width: this.minButtonWidth,
+                handler: this.stopEditing.createDelegate(this, [false])
+            }]
+        });
+        this.btns.render(this.bwrap);
+    },
+
+    afterRender: function(){
+        Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
+        this.positionButtons();
+        if(this.monitorValid){
+            this.startMonitoring();
+        }
+    },
+
+    onShow: function(){
+        if(this.monitorValid){
+            this.startMonitoring();
+        }
+        Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments);
+    },
+
+    onHide: function(){
+        Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments);
+        this.stopMonitoring();
+        this.grid.getView().focusRow(this.rowIndex);
+    },
+
+    positionButtons: function(){
+        if(this.btns){
+            var h = this.el.dom.clientHeight;
+            var view = this.grid.getView();
+            var scroll = view.scroller.dom.scrollLeft;
+            var width =  view.mainBody.getWidth();
+            var bw = this.btns.getWidth();
+            this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2});
+        }
+    },
+
+    // private
+    preEditValue : function(r, field){
+        var value = r.data[field];
+        return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
+    },
+
+    // private
+    postEditValue : function(value, originalValue, r, field){
+        return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
+    },
+
+    doFocus: function(pt){
+        if(this.isVisible()){
+            var index = 0;
+            if(pt){
+                index = this.getTargetColumnIndex(pt);
+            }
+            var cm = this.grid.getColumnModel();
+            for(var i = index||0, len = cm.getColumnCount(); i < len; i++){
+                var c = cm.getColumnAt(i);
+                if(!c.hidden && c.getEditor()){
+                    c.getEditor().focus();
+                    break;
+                }
+            }
+        }
+    },
+
+    getTargetColumnIndex: function(pt){
+        var grid = this.grid, v = grid.view;
+        var x = pt.left;
+        var cms = grid.colModel.config;
+        var i = 0, match = false;
+        for(var len = cms.length, c; c = cms[i]; i++){
+            if(!c.hidden){
+                if(Ext.fly(v.getHeaderCell(i)).getRegion().right >= x){
+                    match = i;
+                    break;
+                }
+            }
+        }
+        return match;
+    },
+
+    startMonitoring : function(){
+        if(!this.bound && this.monitorValid){
+            this.bound = true;
+            Ext.TaskMgr.start({
+                run : this.bindHandler,
+                interval : this.monitorPoll || 200,
+                scope: this
+            });
+        }
+    },
+
+    stopMonitoring : function(){
+        this.bound = false;
+        if(this.tooltip){
+            this.tooltip.hide();
+        }
+    },
+
+    isValid: function(){
+        var valid = true;
+        this.items.each(function(f){
+            if(!f.isValid(true)){
+                valid = false;
+                return false;
+            }
+        });
+        return valid;
+    },
+
+    // private
+    bindHandler : function(){
+        if(!this.bound){
+            return false; // stops binding
+        }
+        var valid = this.isValid();
+        if(!valid && this.errorSummary){
+            this.showTooltip(this.getErrorText().join(''));
+        }
+        this.btns.saveBtn.setDisabled(!valid);
+        this.fireEvent('validation', this, valid);
+    },
+
+    showTooltip: function(msg){
+        var t = this.tooltip;
+        if(!t){
+            t = this.tooltip = new Ext.ToolTip({
+                maxWidth: 600,
+                cls: 'errorTip',
+                width: 300,
+                title: 'Errors',
+                autoHide: false,
+                anchor: 'left',
+                anchorToTarget: true,
+                mouseOffset: [40,0]
+            });
+        }
+        t.initTarget(this.items.last().getEl());
+        if(!t.rendered){
+            t.show();
+            t.hide();
+        }
+        t.body.update(msg);
+        t.doAutoWidth();
+        t.show();
+    },
+
+    getErrorText: function(){
+        var data = ['<ul>'];
+        this.items.each(function(f){
+            if(!f.isValid(true)){
+                data.push('<li>', f.activeError, '</li>');
+            }
+        });
+        data.push('</ul>');
+        return data;
+    }
+});
+Ext.preg('roweditor', Ext.ux.grid.RowEditor);
+
+Ext.override(Ext.form.Field, {
+    markInvalid : function(msg){
+        if(!this.rendered || this.preventMark){ // not rendered
+            return;
+        }
+        msg = msg || this.invalidText;
+
+        var mt = this.getMessageHandler();
+        if(mt){
+            mt.mark(this, msg);
+        }else if(this.msgTarget){
+            this.el.addClass(this.invalidClass);
+            var t = Ext.getDom(this.msgTarget);
+            if(t){
+                t.innerHTML = msg;
+                t.style.display = this.msgDisplay;
+            }
+        }
+        this.activeError = msg;
+        this.fireEvent('invalid', this, msg);
+    }
+});
+
+Ext.override(Ext.ToolTip, {
+    doAutoWidth : function(){
+        var bw = this.body.getTextWidth();
+        if(this.title){
+            bw = Math.max(bw, this.header.child('span').getTextWidth(this.title));
+        }
+        bw += this.getFrameWidth() + (this.closable ? 20 : 0) + this.body.getPadding("lr") + 20;
+        this.setWidth(bw.constrain(this.minWidth, this.maxWidth));
+
+        // IE7 repaint bug on initial show
+        if(Ext.isIE7 && !this.repainted){
+            this.el.repaint();
+            this.repainted = true;
+        }
+    }
+});
+Ext.ns('Ext.ux.grid');\r
+\r
+/**\r
+ * @class Ext.ux.grid.RowExpander\r
+ * @extends Ext.util.Observable\r
+ * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables\r
+ * a second row body which expands/contracts.  The expand/contract behavior is configurable to react\r
+ * on clicking of the column, double click of the row, and/or hitting enter while a row is selected.\r
+ *\r
+ * @ptype rowexpander\r
+ */\r
+Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, {\r
+    /**\r
+     * @cfg {Boolean} expandOnEnter\r
+     * <tt>true</tt> to toggle selected row(s) between expanded/collapsed when the enter\r
+     * key is pressed (defaults to <tt>true</tt>).\r
+     */\r
+    expandOnEnter : true,\r
+    /**\r
+     * @cfg {Boolean} expandOnDblClick\r
+     * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked\r
+     * (defaults to <tt>true</tt>).\r
+     */\r
+    expandOnDblClick : true,\r
+\r
+    header : '',\r
+    width : 20,\r
+    sortable : false,\r
+    fixed : true,\r
+    menuDisabled : true,\r
+    dataIndex : '',\r
+    id : 'expander',\r
+    lazyRender : true,\r
+    enableCaching : true,\r
+\r
+    constructor: function(config){\r
+        Ext.apply(this, config);\r
+\r
+        this.addEvents({\r
+            /**\r
+             * @event beforeexpand\r
+             * Fires before the row expands. Have the listener return false to prevent the row from expanding.\r
+             * @param {Object} this RowExpander object.\r
+             * @param {Object} Ext.data.Record Record for the selected row.\r
+             * @param {Object} body body element for the secondary row.\r
+             * @param {Number} rowIndex The current row index.\r
+             */\r
+            beforeexpand: true,\r
+            /**\r
+             * @event expand\r
+             * Fires after the row expands.\r
+             * @param {Object} this RowExpander object.\r
+             * @param {Object} Ext.data.Record Record for the selected row.\r
+             * @param {Object} body body element for the secondary row.\r
+             * @param {Number} rowIndex The current row index.\r
+             */\r
+            expand: true,\r
+            /**\r
+             * @event beforecollapse\r
+             * Fires before the row collapses. Have the listener return false to prevent the row from collapsing.\r
+             * @param {Object} this RowExpander object.\r
+             * @param {Object} Ext.data.Record Record for the selected row.\r
+             * @param {Object} body body element for the secondary row.\r
+             * @param {Number} rowIndex The current row index.\r
+             */\r
+            beforecollapse: true,\r
+            /**\r
+             * @event collapse\r
+             * Fires after the row collapses.\r
+             * @param {Object} this RowExpander object.\r
+             * @param {Object} Ext.data.Record Record for the selected row.\r
+             * @param {Object} body body element for the secondary row.\r
+             * @param {Number} rowIndex The current row index.\r
+             */\r
+            collapse: true\r
+        });\r
+\r
+        Ext.ux.grid.RowExpander.superclass.constructor.call(this);\r
+\r
+        if(this.tpl){\r
+            if(typeof this.tpl == 'string'){\r
+                this.tpl = new Ext.Template(this.tpl);\r
+            }\r
+            this.tpl.compile();\r
+        }\r
+\r
+        this.state = {};\r
+        this.bodyContent = {};\r
+    },\r
+\r
+    getRowClass : function(record, rowIndex, p, ds){\r
+        p.cols = p.cols-1;\r
+        var content = this.bodyContent[record.id];\r
+        if(!content && !this.lazyRender){\r
+            content = this.getBodyContent(record, rowIndex);\r
+        }\r
+        if(content){\r
+            p.body = content;\r
+        }\r
+        return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';\r
+    },\r
+\r
+    init : function(grid){\r
+        this.grid = grid;\r
+\r
+        var view = grid.getView();\r
+        view.getRowClass = this.getRowClass.createDelegate(this);\r
+\r
+        view.enableRowBody = true;\r
+\r
+\r
+        grid.on('render', this.onRender, this);\r
+        grid.on('destroy', this.onDestroy, this);\r
+    },\r
+\r
+    // @private\r
+    onRender: function() {\r
+        var grid = this.grid;\r
+        var mainBody = grid.getView().mainBody;\r
+        mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'});\r
+        if (this.expandOnEnter) {\r
+            this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), {\r
+                'enter' : this.onEnter,\r
+                scope: this\r
+            });\r
+        }\r
+        if (this.expandOnDblClick) {\r
+            grid.on('rowdblclick', this.onRowDblClick, this);\r
+        }\r
+    },\r
+    \r
+    // @private    \r
+    onDestroy: function() {\r
+        this.keyNav.disable();\r
+        delete this.keyNav;\r
+        var mainBody = this.grid.getView().mainBody;\r
+        mainBody.un('mousedown', this.onMouseDown, this);\r
+    },\r
+    // @private\r
+    onRowDblClick: function(grid, rowIdx, e) {\r
+        this.toggleRow(rowIdx);\r
+    },\r
+\r
+    onEnter: function(e) {\r
+        var g = this.grid;\r
+        var sm = g.getSelectionModel();\r
+        var sels = sm.getSelections();\r
+        for (var i = 0, len = sels.length; i < len; i++) {\r
+            var rowIdx = g.getStore().indexOf(sels[i]);\r
+            this.toggleRow(rowIdx);\r
+        }\r
+    },\r
+\r
+    getBodyContent : function(record, index){\r
+        if(!this.enableCaching){\r
+            return this.tpl.apply(record.data);\r
+        }\r
+        var content = this.bodyContent[record.id];\r
+        if(!content){\r
+            content = this.tpl.apply(record.data);\r
+            this.bodyContent[record.id] = content;\r
+        }\r
+        return content;\r
+    },\r
+\r
+    onMouseDown : function(e, t){\r
+        e.stopEvent();\r
+        var row = e.getTarget('.x-grid3-row');\r
+        this.toggleRow(row);\r
+    },\r
+\r
+    renderer : function(v, p, record){\r
+        p.cellAttr = 'rowspan="2"';\r
+        return '<div class="x-grid3-row-expander">&#160;</div>';\r
+    },\r
+\r
+    beforeExpand : function(record, body, rowIndex){\r
+        if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){\r
+            if(this.tpl && this.lazyRender){\r
+                body.innerHTML = this.getBodyContent(record, rowIndex);\r
+            }\r
+            return true;\r
+        }else{\r
+            return false;\r
+        }\r
+    },\r
+\r
+    toggleRow : function(row){\r
+        if(typeof row == 'number'){\r
+            row = this.grid.view.getRow(row);\r
+        }\r
+        this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row);\r
+    },\r
+\r
+    expandRow : function(row){\r
+        if(typeof row == 'number'){\r
+            row = this.grid.view.getRow(row);\r
+        }\r
+        var record = this.grid.store.getAt(row.rowIndex);\r
+        var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);\r
+        if(this.beforeExpand(record, body, row.rowIndex)){\r
+            this.state[record.id] = true;\r
+            Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');\r
+            this.fireEvent('expand', this, record, body, row.rowIndex);\r
+        }\r
+    },\r
+\r
+    collapseRow : function(row){\r
+        if(typeof row == 'number'){\r
+            row = this.grid.view.getRow(row);\r
+        }\r
+        var record = this.grid.store.getAt(row.rowIndex);\r
+        var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);\r
+        if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){\r
+            this.state[record.id] = false;\r
+            Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');\r
+            this.fireEvent('collapse', this, record, body, row.rowIndex);\r
+        }\r
+    }\r
+});\r
+\r
+Ext.preg('rowexpander', Ext.ux.grid.RowExpander);\r
+\r
+//backwards compat\r
+Ext.grid.RowExpander = Ext.ux.grid.RowExpander;// We are adding these custom layouts to a namespace that does not
+// exist by default in Ext, so we have to add the namespace first:
+Ext.ns('Ext.ux.layout');
+
+/**
+ * @class Ext.ux.layout.RowLayout
+ * @extends Ext.layout.ContainerLayout
+ * <p>This is the layout style of choice for creating structural layouts in a multi-row format where the height of
+ * each row can be specified as a percentage or fixed height.  Row widths can also be fixed, percentage or auto.
+ * This class is intended to be extended or created via the layout:'ux.row' {@link Ext.Container#layout} config,
+ * and should generally not need to be created directly via the new keyword.</p>
+ * <p>RowLayout does not have any direct config options (other than inherited ones), but it does support a
+ * specific config property of <b><tt>rowHeight</tt></b> that can be included in the config of any panel added to it.  The
+ * layout will use the rowHeight (if present) or height of each panel during layout to determine how to size each panel.
+ * If height or rowHeight is not specified for a given panel, its height will default to the panel's height (or auto).</p>
+ * <p>The height property is always evaluated as pixels, and must be a number greater than or equal to 1.
+ * The rowHeight 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 row heights 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 height or none
+ * specified (auto) are skipped, but their heights are subtracted from the overall container height.  During the second
+ * pass, all panels with rowHeights are assigned pixel heights in proportion to their percentages based on
+ * the total <b>remaining</b> container height.  In other words, percentage height panels are designed to fill the space
+ * left over by all the fixed-height and/or auto-height panels.  Because of this, while you can specify any number of rows
+ * with different percentages, the rowHeights 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 rows are percentages -- they must add up to 1
+var p = new Ext.Panel({
+    title: 'Row Layout - Percentage Only',
+    layout:'ux.row',
+    items: [{
+        title: 'Row 1',
+        rowHeight: .25
+    },{
+        title: 'Row 2',
+        rowHeight: .6
+    },{
+        title: 'Row 3',
+        rowHeight: .15
+    }]
+});
+
+// Mix of height and rowHeight -- all rowHeight values must add
+// up to 1. The first row will take up exactly 120px, and the last two
+// rows will fill the remaining container height.
+var p = new Ext.Panel({
+    title: 'Row Layout - Mixed',
+    layout:'ux.row',
+    items: [{
+        title: 'Row 1',
+        height: 120,
+        // standard panel widths are still supported too:
+        width: '50%' // or 200
+    },{
+        title: 'Row 2',
+        rowHeight: .8,
+        width: 300
+    },{
+        title: 'Row 3',
+        rowHeight: .2
+    }]
+});
+</code></pre>
+ */
+Ext.ux.layout.RowLayout = Ext.extend(Ext.layout.ContainerLayout, {
+    // private
+    monitorResize:true,
+
+    // private
+    isValidParent : function(c, target){
+        return c.getEl().dom.parentNode == this.innerCt.dom;
+    },
+
+    // private
+    onLayout : function(ct, target){
+        var rs = ct.items.items, len = rs.length, r, i;
+
+        if(!this.innerCt){
+            target.addClass('ux-row-layout-ct');
+            this.innerCt = target.createChild({cls:'x-row-inner'});
+        }
+        this.renderAll(ct, this.innerCt);
+
+        var size = target.getViewSize();
+
+        if(size.width < 1 && size.height < 1){ // display none?
+            return;
+        }
+
+        var h = size.height - target.getPadding('tb'),
+            ph = h;
+
+        this.innerCt.setSize({height:h});
+
+        // some rows can be percentages while others are fixed
+        // so we need to make 2 passes
+
+        for(i = 0; i < len; i++){
+            r = rs[i];
+            if(!r.rowHeight){
+                ph -= (r.getSize().height + r.getEl().getMargins('tb'));
+            }
+        }
+
+        ph = ph < 0 ? 0 : ph;
+
+        for(i = 0; i < len; i++){
+            r = rs[i];
+            if(r.rowHeight){
+                r.setSize({height: Math.floor(r.rowHeight*ph) - r.getEl().getMargins('tb')});
+            }
+        }
+    }
+
+    /**
+     * @property activeItem
+     * @hide
+     */
+});
+
+Ext.Container.LAYOUTS['ux.row'] = Ext.ux.layout.RowLayout;
+Ext.ns('Ext.ux.form');\r
+\r
+Ext.ux.form.SearchField = Ext.extend(Ext.form.TwinTriggerField, {\r
+    initComponent : function(){\r
+        Ext.ux.form.SearchField.superclass.initComponent.call(this);\r
+        this.on('specialkey', function(f, e){\r
+            if(e.getKey() == e.ENTER){\r
+                this.onTrigger2Click();\r
+            }\r
+        }, this);\r
+    },\r
+\r
+    validationEvent:false,\r
+    validateOnBlur:false,\r
+    trigger1Class:'x-form-clear-trigger',\r
+    trigger2Class:'x-form-search-trigger',\r
+    hideTrigger1:true,\r
+    width:180,\r
+    hasSearch : false,\r
+    paramName : 'query',\r
+\r
+    onTrigger1Click : function(){\r
+        if(this.hasSearch){\r
+            this.el.dom.value = '';\r
+            var o = {start: 0};\r
+            this.store.baseParams = this.store.baseParams || {};\r
+            this.store.baseParams[this.paramName] = '';\r
+            this.store.reload({params:o});\r
+            this.triggers[0].hide();\r
+            this.hasSearch = false;\r
+        }\r
+    },\r
+\r
+    onTrigger2Click : function(){\r
+        var v = this.getRawValue();\r
+        if(v.length < 1){\r
+            this.onTrigger1Click();\r
+            return;\r
+        }\r
+        var o = {start: 0};\r
+        this.store.baseParams = this.store.baseParams || {};\r
+        this.store.baseParams[this.paramName] = v;\r
+        this.store.reload({params:o});\r
+        this.hasSearch = true;\r
+        this.triggers[0].show();\r
+    }\r
+});Ext.ns('Ext.ux.form');\r
+\r
+/**\r
+ * @class Ext.ux.form.SelectBox\r
+ * @extends Ext.form.ComboBox\r
+ * <p>Makes a ComboBox more closely mimic an HTML SELECT.  Supports clicking and dragging\r
+ * through the list, with item selection occurring when the mouse button is released.\r
+ * When used will automatically set {@link #editable} to false and call {@link Ext.Element#unselectable}\r
+ * on inner elements.  Re-enabling editable after calling this will NOT work.</p>\r
+ * @author Corey Gilmore http://extjs.com/forum/showthread.php?t=6392\r
+ * @history 2007-07-08 jvs\r
+ * Slight mods for Ext 2.0\r
+ * @xtype selectbox\r
+ */\r
+Ext.ux.form.SelectBox = Ext.extend(Ext.form.ComboBox, {\r
+       constructor: function(config){\r
+               this.searchResetDelay = 1000;\r
+               config = config || {};\r
+               config = Ext.apply(config || {}, {\r
+                       editable: false,\r
+                       forceSelection: true,\r
+                       rowHeight: false,\r
+                       lastSearchTerm: false,\r
+                       triggerAction: 'all',\r
+                       mode: 'local'\r
+               });\r
+\r
+               Ext.ux.form.SelectBox.superclass.constructor.apply(this, arguments);\r
+\r
+               this.lastSelectedIndex = this.selectedIndex || 0;\r
+       },\r
+\r
+       initEvents : function(){\r
+               Ext.ux.form.SelectBox.superclass.initEvents.apply(this, arguments);\r
+               // you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE\r
+               this.el.on('keydown', this.keySearch, this, true);\r
+               this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistory, this);\r
+       },\r
+\r
+       keySearch : function(e, target, options) {\r
+               var raw = e.getKey();\r
+               var key = String.fromCharCode(raw);\r
+               var startIndex = 0;\r
+\r
+               if( !this.store.getCount() ) {\r
+                       return;\r
+               }\r
+\r
+               switch(raw) {\r
+                       case Ext.EventObject.HOME:\r
+                               e.stopEvent();\r
+                               this.selectFirst();\r
+                               return;\r
+\r
+                       case Ext.EventObject.END:\r
+                               e.stopEvent();\r
+                               this.selectLast();\r
+                               return;\r
+\r
+                       case Ext.EventObject.PAGEDOWN:\r
+                               this.selectNextPage();\r
+                               e.stopEvent();\r
+                               return;\r
+\r
+                       case Ext.EventObject.PAGEUP:\r
+                               this.selectPrevPage();\r
+                               e.stopEvent();\r
+                               return;\r
+               }\r
+\r
+               // skip special keys other than the shift key\r
+               if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) {\r
+                       return;\r
+               }\r
+               if( this.lastSearchTerm == key ) {\r
+                       startIndex = this.lastSelectedIndex;\r
+               }\r
+               this.search(this.displayField, key, startIndex);\r
+               this.cshTask.delay(this.searchResetDelay);\r
+       },\r
+\r
+       onRender : function(ct, position) {\r
+               this.store.on('load', this.calcRowsPerPage, this);\r
+               Ext.ux.form.SelectBox.superclass.onRender.apply(this, arguments);\r
+               if( this.mode == 'local' ) {\r
+                       this.calcRowsPerPage();\r
+               }\r
+       },\r
+\r
+       onSelect : function(record, index, skipCollapse){\r
+               if(this.fireEvent('beforeselect', this, record, index) !== false){\r
+                       this.setValue(record.data[this.valueField || this.displayField]);\r
+                       if( !skipCollapse ) {\r
+                               this.collapse();\r
+                       }\r
+                       this.lastSelectedIndex = index + 1;\r
+                       this.fireEvent('select', this, record, index);\r
+               }\r
+       },\r
+\r
+       render : function(ct) {\r
+               Ext.ux.form.SelectBox.superclass.render.apply(this, arguments);\r
+               if( Ext.isSafari ) {\r
+                       this.el.swallowEvent('mousedown', true);\r
+               }\r
+               this.el.unselectable();\r
+               this.innerList.unselectable();\r
+               this.trigger.unselectable();\r
+               this.innerList.on('mouseup', function(e, target, options) {\r
+                       if( target.id && target.id == this.innerList.id ) {\r
+                               return;\r
+                       }\r
+                       this.onViewClick();\r
+               }, this);\r
+\r
+               this.innerList.on('mouseover', function(e, target, options) {\r
+                       if( target.id && target.id == this.innerList.id ) {\r
+                               return;\r
+                       }\r
+                       this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;\r
+                       this.cshTask.delay(this.searchResetDelay);\r
+               }, this);\r
+\r
+               this.trigger.un('click', this.onTriggerClick, this);\r
+               this.trigger.on('mousedown', function(e, target, options) {\r
+                       e.preventDefault();\r
+                       this.onTriggerClick();\r
+               }, this);\r
+\r
+               this.on('collapse', function(e, target, options) {\r
+                       Ext.getDoc().un('mouseup', this.collapseIf, this);\r
+               }, this, true);\r
+\r
+               this.on('expand', function(e, target, options) {\r
+                       Ext.getDoc().on('mouseup', this.collapseIf, this);\r
+               }, this, true);\r
+       },\r
+\r
+       clearSearchHistory : function() {\r
+               this.lastSelectedIndex = 0;\r
+               this.lastSearchTerm = false;\r
+       },\r
+\r
+       selectFirst : function() {\r
+               this.focusAndSelect(this.store.data.first());\r
+       },\r
+\r
+       selectLast : function() {\r
+               this.focusAndSelect(this.store.data.last());\r
+       },\r
+\r
+       selectPrevPage : function() {\r
+               if( !this.rowHeight ) {\r
+                       return;\r
+               }\r
+               var index = Math.max(this.selectedIndex-this.rowsPerPage, 0);\r
+               this.focusAndSelect(this.store.getAt(index));\r
+       },\r
+\r
+       selectNextPage : function() {\r
+               if( !this.rowHeight ) {\r
+                       return;\r
+               }\r
+               var index = Math.min(this.selectedIndex+this.rowsPerPage, this.store.getCount() - 1);\r
+               this.focusAndSelect(this.store.getAt(index));\r
+       },\r
+\r
+       search : function(field, value, startIndex) {\r
+               field = field || this.displayField;\r
+               this.lastSearchTerm = value;\r
+               var index = this.store.find.apply(this.store, arguments);\r
+               if( index !== -1 ) {\r
+                       this.focusAndSelect(index);\r
+               }\r
+       },\r
+\r
+       focusAndSelect : function(record) {\r
+               var index = typeof record === 'number' ? record : this.store.indexOf(record);\r
+               this.select(index, this.isExpanded());\r
+               this.onSelect(this.store.getAt(record), index, this.isExpanded());\r
+       },\r
+\r
+       calcRowsPerPage : function() {\r
+               if( this.store.getCount() ) {\r
+                       this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight();\r
+                       this.rowsPerPage = this.maxHeight / this.rowHeight;\r
+               } else {\r
+                       this.rowHeight = false;\r
+               }\r
+       }\r
+\r
+});\r
+\r
+Ext.reg('selectbox', Ext.ux.form.SelectBox);\r
+\r
+//backwards compat\r
+Ext.ux.SelectBox = Ext.ux.form.SelectBox;\r
+/**\r
+ * @class Ext.ux.SliderTip\r
+ * @extends Ext.Tip\r
+ * Simple plugin for using an Ext.Tip with a slider to show the slider value\r
+ */\r
+Ext.ux.SliderTip = Ext.extend(Ext.Tip, {\r
+    minWidth: 10,\r
+    offsets : [0, -10],\r
+    init : function(slider){\r
+        slider.on('dragstart', this.onSlide, this);\r
+        slider.on('drag', this.onSlide, this);\r
+        slider.on('dragend', this.hide, this);\r
+        slider.on('destroy', this.destroy, this);\r
+    },\r
+\r
+    onSlide : function(slider){\r
+        this.show();\r
+        this.body.update(this.getText(slider));\r
+        this.doAutoWidth();\r
+        this.el.alignTo(slider.thumb, 'b-t?', this.offsets);\r
+    },\r
+\r
+    getText : function(slider){\r
+        return String(slider.getValue());\r
+    }\r
+});\r
+Ext.ux.SlidingPager = Ext.extend(Object, {\r
+    init : function(pbar){\r
+        Ext.each(pbar.items.getRange(2,6), function(c){\r
+            c.hide();\r
+        });\r
+        var slider = new Ext.Slider({\r
+            width: 114,\r
+            minValue: 1,\r
+            maxValue: 1,\r
+            plugins: new Ext.ux.SliderTip({\r
+                getText : function(s){\r
+                    return String.format('Page <b>{0}</b> of <b>{1}</b>', s.value, s.maxValue);\r
+                }\r
+            }),\r
+            listeners: {\r
+                changecomplete: function(s, v){\r
+                    pbar.changePage(v);\r
+                }\r
+            }\r
+        });\r
+        pbar.insert(5, slider);\r
+        pbar.on({\r
+            change: function(pb, data){\r
+                slider.maxValue = data.pages;\r
+                slider.setValue(data.activePage);\r
+            },\r
+            beforedestroy: function(){\r
+                slider.destroy();\r
+            }\r
+        });\r
+    }\r
+});Ext.ns('Ext.ux.form');\r
+\r
+/**\r
+ * @class Ext.ux.form.SpinnerField\r
+ * @extends Ext.form.NumberField\r
+ * Creates a field utilizing Ext.ux.Spinner\r
+ * @xtype spinnerfield\r
+ */\r
+Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, {\r
+    deferHeight: true,\r
+    autoSize: Ext.emptyFn,\r
+    onBlur: Ext.emptyFn,\r
+    adjustSize: Ext.BoxComponent.prototype.adjustSize,\r
+\r
+       constructor: function(config) {\r
+               var spinnerConfig = Ext.copyTo({}, config, 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass');\r
+\r
+               var spl = this.spinner = new Ext.ux.Spinner(spinnerConfig);\r
+\r
+               var plugins = config.plugins\r
+                       ? (Ext.isArray(config.plugins)\r
+                               ? config.plugins.push(spl)\r
+                               : [config.plugins, spl])\r
+                       : spl;\r
+\r
+               Ext.ux.form.SpinnerField.superclass.constructor.call(this, Ext.apply(config, {plugins: plugins}));\r
+       },\r
+\r
+    onShow: function(){\r
+        if (this.wrap) {\r
+            this.wrap.dom.style.display = '';\r
+            this.wrap.dom.style.visibility = 'visible';\r
+        }\r
+    },\r
+\r
+    onHide: function(){\r
+        this.wrap.dom.style.display = 'none';\r
+    },\r
+\r
+    // private\r
+    getResizeEl: function(){\r
+        return this.wrap;\r
+    },\r
+\r
+    // private\r
+    getPositionEl: function(){\r
+        return this.wrap;\r
+    },\r
+\r
+    // private\r
+    alignErrorIcon: function(){\r
+        if (this.wrap) {\r
+            this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);\r
+        }\r
+    },\r
+\r
+    validateBlur: function(){\r
+        return true;\r
+    }\r
+});\r
+\r
+Ext.reg('spinnerfield', Ext.ux.form.SpinnerField);\r
+\r
+//backwards compat\r
+Ext.form.SpinnerField = Ext.ux.form.SpinnerField;\r
+/**\r
+ * @class Ext.ux.Spinner\r
+ * @extends Ext.util.Observable\r
+ * Creates a Spinner control utilized by Ext.ux.form.SpinnerField\r
+ */\r
+Ext.ux.Spinner = Ext.extend(Ext.util.Observable, {\r
+    incrementValue: 1,\r
+    alternateIncrementValue: 5,\r
+    triggerClass: 'x-form-spinner-trigger',\r
+    splitterClass: 'x-form-spinner-splitter',\r
+    alternateKey: Ext.EventObject.shiftKey,\r
+    defaultValue: 0,\r
+    accelerate: false,\r
+\r
+    constructor: function(config){\r
+        Ext.ux.Spinner.superclass.constructor.call(this, config);\r
+        Ext.apply(this, config);\r
+        this.mimicing = false;\r
+    },\r
+\r
+    init: function(field){\r
+        this.field = field;\r
+\r
+        field.afterMethod('onRender', this.doRender, this);\r
+        field.afterMethod('onEnable', this.doEnable, this);\r
+        field.afterMethod('onDisable', this.doDisable, this);\r
+        field.afterMethod('afterRender', this.doAfterRender, this);\r
+        field.afterMethod('onResize', this.doResize, this);\r
+        field.afterMethod('onFocus', this.doFocus, this);\r
+        field.beforeMethod('onDestroy', this.doDestroy, this);\r
+    },\r
+\r
+    doRender: function(ct, position){\r
+        var el = this.el = this.field.getEl();\r
+        var f = this.field;\r
+\r
+        if (!f.wrap) {\r
+            f.wrap = this.wrap = el.wrap({\r
+                cls: "x-form-field-wrap"\r
+            });\r
+        }\r
+        else {\r
+            this.wrap = f.wrap.addClass('x-form-field-wrap');\r
+        }\r
+\r
+        this.trigger = this.wrap.createChild({\r
+            tag: "img",\r
+            src: Ext.BLANK_IMAGE_URL,\r
+            cls: "x-form-trigger " + this.triggerClass\r
+        });\r
+\r
+        if (!f.width) {\r
+            this.wrap.setWidth(el.getWidth() + this.trigger.getWidth());\r
+        }\r
+\r
+        this.splitter = this.wrap.createChild({\r
+            tag: 'div',\r
+            cls: this.splitterClass,\r
+            style: 'width:13px; height:2px;'\r
+        });\r
+        this.splitter.setRight((Ext.isIE) ? 1 : 2).setTop(10).show();\r
+\r
+        this.proxy = this.trigger.createProxy('', this.splitter, true);\r
+        this.proxy.addClass("x-form-spinner-proxy");\r
+        this.proxy.setStyle('left', '0px');\r
+        this.proxy.setSize(14, 1);\r
+        this.proxy.hide();\r
+        this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", {\r
+            dragElId: this.proxy.id\r
+        });\r
+\r
+        this.initTrigger();\r
+        this.initSpinner();\r
+    },\r
+\r
+    doAfterRender: function(){\r
+        var y;\r
+        if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) {\r
+            this.el.position();\r
+            this.el.setY(y);\r
+        }\r
+    },\r
+\r
+    doEnable: function(){\r
+        if (this.wrap) {\r
+            this.wrap.removeClass(this.field.disabledClass);\r
+        }\r
+    },\r
+\r
+    doDisable: function(){\r
+        if (this.wrap) {\r
+            this.wrap.addClass(this.field.disabledClass);\r
+            this.el.removeClass(this.field.disabledClass);\r
+        }\r
+    },\r
+\r
+    doResize: function(w, h){\r
+        if (typeof w == 'number') {\r
+            this.el.setWidth(this.field.adjustWidth('input', w - this.trigger.getWidth()));\r
+        }\r
+        this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth());\r
+    },\r
+\r
+    doFocus: function(){\r
+        if (!this.mimicing) {\r
+            this.wrap.addClass('x-trigger-wrap-focus');\r
+            this.mimicing = true;\r
+            Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {\r
+                delay: 10\r
+            });\r
+            this.el.on('keydown', this.checkTab, this);\r
+        }\r
+    },\r
+\r
+    // private\r
+    checkTab: function(e){\r
+        if (e.getKey() == e.TAB) {\r
+            this.triggerBlur();\r
+        }\r
+    },\r
+\r
+    // private\r
+    mimicBlur: function(e){\r
+        if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) {\r
+            this.triggerBlur();\r
+        }\r
+    },\r
+\r
+    // private\r
+    triggerBlur: function(){\r
+        this.mimicing = false;\r
+        Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);\r
+        this.el.un("keydown", this.checkTab, this);\r
+        this.field.beforeBlur();\r
+        this.wrap.removeClass('x-trigger-wrap-focus');\r
+        this.field.onBlur.call(this.field);\r
+    },\r
+\r
+    initTrigger: function(){\r
+        this.trigger.addClassOnOver('x-form-trigger-over');\r
+        this.trigger.addClassOnClick('x-form-trigger-click');\r
+    },\r
+\r
+    initSpinner: function(){\r
+        this.field.addEvents({\r
+            'spin': true,\r
+            'spinup': true,\r
+            'spindown': true\r
+        });\r
+\r
+        this.keyNav = new Ext.KeyNav(this.el, {\r
+            "up": function(e){\r
+                e.preventDefault();\r
+                this.onSpinUp();\r
+            },\r
+\r
+            "down": function(e){\r
+                e.preventDefault();\r
+                this.onSpinDown();\r
+            },\r
+\r
+            "pageUp": function(e){\r
+                e.preventDefault();\r
+                this.onSpinUpAlternate();\r
+            },\r
+\r
+            "pageDown": function(e){\r
+                e.preventDefault();\r
+                this.onSpinDownAlternate();\r
+            },\r
+\r
+            scope: this\r
+        });\r
+\r
+        this.repeater = new Ext.util.ClickRepeater(this.trigger, {\r
+            accelerate: this.accelerate\r
+        });\r
+        this.field.mon(this.repeater, "click", this.onTriggerClick, this, {\r
+            preventDefault: true\r
+        });\r
+\r
+        this.field.mon(this.trigger, {\r
+            mouseover: this.onMouseOver,\r
+            mouseout: this.onMouseOut,\r
+            mousemove: this.onMouseMove,\r
+            mousedown: this.onMouseDown,\r
+            mouseup: this.onMouseUp,\r
+            scope: this,\r
+            preventDefault: true\r
+        });\r
+\r
+        this.field.mon(this.wrap, "mousewheel", this.handleMouseWheel, this);\r
+\r
+        this.dd.setXConstraint(0, 0, 10)\r
+        this.dd.setYConstraint(1500, 1500, 10);\r
+        this.dd.endDrag = this.endDrag.createDelegate(this);\r
+        this.dd.startDrag = this.startDrag.createDelegate(this);\r
+        this.dd.onDrag = this.onDrag.createDelegate(this);\r
+    },\r
+\r
+    onMouseOver: function(){\r
+        if (this.disabled) {\r
+            return;\r
+        }\r
+        var middle = this.getMiddle();\r
+        this.tmpHoverClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown';\r
+        this.trigger.addClass(this.tmpHoverClass);\r
+    },\r
+\r
+    //private\r
+    onMouseOut: function(){\r
+        this.trigger.removeClass(this.tmpHoverClass);\r
+    },\r
+\r
+    //private\r
+    onMouseMove: function(){\r
+        if (this.disabled) {\r
+            return;\r
+        }\r
+        var middle = this.getMiddle();\r
+        if (((Ext.EventObject.getPageY() > middle) && this.tmpHoverClass == "x-form-spinner-overup") ||\r
+        ((Ext.EventObject.getPageY() < middle) && this.tmpHoverClass == "x-form-spinner-overdown")) {\r
+        }\r
+    },\r
+\r
+    //private\r
+    onMouseDown: function(){\r
+        if (this.disabled) {\r
+            return;\r
+        }\r
+        var middle = this.getMiddle();\r
+        this.tmpClickClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown';\r
+        this.trigger.addClass(this.tmpClickClass);\r
+    },\r
+\r
+    //private\r
+    onMouseUp: function(){\r
+        this.trigger.removeClass(this.tmpClickClass);\r
+    },\r
+\r
+    //private\r
+    onTriggerClick: function(){\r
+        if (this.disabled || this.el.dom.readOnly) {\r
+            return;\r
+        }\r
+        var middle = this.getMiddle();\r
+        var ud = (Ext.EventObject.getPageY() < middle) ? 'Up' : 'Down';\r
+        this['onSpin' + ud]();\r
+    },\r
+\r
+    //private\r
+    getMiddle: function(){\r
+        var t = this.trigger.getTop();\r
+        var h = this.trigger.getHeight();\r
+        var middle = t + (h / 2);\r
+        return middle;\r
+    },\r
+\r
+    //private\r
+    //checks if control is allowed to spin\r
+    isSpinnable: function(){\r
+        if (this.disabled || this.el.dom.readOnly) {\r
+            Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly\r
+            return false;\r
+        }\r
+        return true;\r
+    },\r
+\r
+    handleMouseWheel: function(e){\r
+        //disable scrolling when not focused\r
+        if (this.wrap.hasClass('x-trigger-wrap-focus') == false) {\r
+            return;\r
+        }\r
+\r
+        var delta = e.getWheelDelta();\r
+        if (delta > 0) {\r
+            this.onSpinUp();\r
+            e.stopEvent();\r
+        }\r
+        else\r
+            if (delta < 0) {\r
+                this.onSpinDown();\r
+                e.stopEvent();\r
+            }\r
+    },\r
+\r
+    //private\r
+    startDrag: function(){\r
+        this.proxy.show();\r
+        this._previousY = Ext.fly(this.dd.getDragEl()).getTop();\r
+    },\r
+\r
+    //private\r
+    endDrag: function(){\r
+        this.proxy.hide();\r
+    },\r
+\r
+    //private\r
+    onDrag: function(){\r
+        if (this.disabled) {\r
+            return;\r
+        }\r
+        var y = Ext.fly(this.dd.getDragEl()).getTop();\r
+        var ud = '';\r
+\r
+        if (this._previousY > y) {\r
+            ud = 'Up';\r
+        } //up\r
+        if (this._previousY < y) {\r
+            ud = 'Down';\r
+        } //down\r
+        if (ud != '') {\r
+            this['onSpin' + ud]();\r
+        }\r
+\r
+        this._previousY = y;\r
+    },\r
+\r
+    //private\r
+    onSpinUp: function(){\r
+        if (this.isSpinnable() == false) {\r
+            return;\r
+        }\r
+        if (Ext.EventObject.shiftKey == true) {\r
+            this.onSpinUpAlternate();\r
+            return;\r
+        }\r
+        else {\r
+            this.spin(false, false);\r
+        }\r
+        this.field.fireEvent("spin", this);\r
+        this.field.fireEvent("spinup", this);\r
+    },\r
+\r
+    //private\r
+    onSpinDown: function(){\r
+        if (this.isSpinnable() == false) {\r
+            return;\r
+        }\r
+        if (Ext.EventObject.shiftKey == true) {\r
+            this.onSpinDownAlternate();\r
+            return;\r
+        }\r
+        else {\r
+            this.spin(true, false);\r
+        }\r
+        this.field.fireEvent("spin", this);\r
+        this.field.fireEvent("spindown", this);\r
+    },\r
+\r
+    //private\r
+    onSpinUpAlternate: function(){\r
+        if (this.isSpinnable() == false) {\r
+            return;\r
+        }\r
+        this.spin(false, true);\r
+        this.field.fireEvent("spin", this);\r
+        this.field.fireEvent("spinup", this);\r
+    },\r
+\r
+    //private\r
+    onSpinDownAlternate: function(){\r
+        if (this.isSpinnable() == false) {\r
+            return;\r
+        }\r
+        this.spin(true, true);\r
+        this.field.fireEvent("spin", this);\r
+        this.field.fireEvent("spindown", this);\r
+    },\r
+\r
+    spin: function(down, alternate){\r
+        var v = parseFloat(this.field.getValue());\r
+        var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;\r
+        (down == true) ? v -= incr : v += incr;\r
+\r
+        v = (isNaN(v)) ? this.defaultValue : v;\r
+        v = this.fixBoundries(v);\r
+        this.field.setRawValue(v);\r
+    },\r
+\r
+    fixBoundries: function(value){\r
+        var v = value;\r
+\r
+        if (this.field.minValue != undefined && v < this.field.minValue) {\r
+            v = this.field.minValue;\r
+        }\r
+        if (this.field.maxValue != undefined && v > this.field.maxValue) {\r
+            v = this.field.maxValue;\r
+        }\r
+\r
+        return this.fixPrecision(v);\r
+    },\r
+\r
+    // private\r
+    fixPrecision: function(value){\r
+        var nan = isNaN(value);\r
+        if (!this.field.allowDecimals || this.field.decimalPrecision == -1 || nan || !value) {\r
+            return nan ? '' : value;\r
+        }\r
+        return parseFloat(parseFloat(value).toFixed(this.field.decimalPrecision));\r
+    },\r
+\r
+    doDestroy: function(){\r
+        if (this.trigger) {\r
+            this.trigger.remove();\r
+        }\r
+        if (this.wrap) {\r
+            this.wrap.remove();\r
+            delete this.field.wrap;\r
+        }\r
+\r
+        if (this.splitter) {\r
+            this.splitter.remove();\r
+        }\r
+\r
+        if (this.dd) {\r
+            this.dd.unreg();\r
+            this.dd = null;\r
+        }\r
+\r
+        if (this.proxy) {\r
+            this.proxy.remove();\r
+        }\r
+\r
+        if (this.repeater) {\r
+            this.repeater.purgeListeners();\r
+        }\r
+    }\r
+});\r
+\r
+//backwards compat\r
+Ext.form.Spinner = Ext.ux.Spinner;Ext.ux.Spotlight = function(config){\r
+    Ext.apply(this, config);\r
+}\r
+Ext.ux.Spotlight.prototype = {\r
+    active : false,\r
+    animate : true,\r
+    duration: .25,\r
+    easing:'easeNone',\r
+\r
+    // private\r
+    animated : false,\r
+\r
+    createElements : function(){\r
+        var bd = Ext.getBody();\r
+\r
+        this.right = bd.createChild({cls:'x-spotlight'});\r
+        this.left = bd.createChild({cls:'x-spotlight'});\r
+        this.top = bd.createChild({cls:'x-spotlight'});\r
+        this.bottom = bd.createChild({cls:'x-spotlight'});\r
+\r
+        this.all = new Ext.CompositeElement([this.right, this.left, this.top, this.bottom]);\r
+    },\r
+\r
+    show : function(el, callback, scope){\r
+        if(this.animated){\r
+            this.show.defer(50, this, [el, callback, scope]);\r
+            return;\r
+        }\r
+        this.el = Ext.get(el);\r
+        if(!this.right){\r
+            this.createElements();\r
+        }\r
+        if(!this.active){\r
+            this.all.setDisplayed('');\r
+            this.applyBounds(true, false);\r
+            this.active = true;\r
+            Ext.EventManager.onWindowResize(this.syncSize, this);\r
+            this.applyBounds(false, this.animate, false, callback, scope);\r
+        }else{\r
+            this.applyBounds(false, false, false, callback, scope); // all these booleans look hideous\r
+        }\r
+    },\r
+\r
+    hide : function(callback, scope){\r
+        if(this.animated){\r
+            this.hide.defer(50, this, [callback, scope]);\r
+            return;\r
+        }\r
+        Ext.EventManager.removeResizeListener(this.syncSize, this);\r
+        this.applyBounds(true, this.animate, true, callback, scope);\r
+    },\r
+\r
+    doHide : function(){\r
+        this.active = false;\r
+        this.all.setDisplayed(false);\r
+    },\r
+\r
+    syncSize : function(){\r
+        this.applyBounds(false, false);\r
+    },\r
+\r
+    applyBounds : function(basePts, anim, doHide, callback, scope){\r
+\r
+        var rg = this.el.getRegion();\r
+\r
+        var dw = Ext.lib.Dom.getViewWidth(true);\r
+        var dh = Ext.lib.Dom.getViewHeight(true);\r
+\r
+        var c = 0, cb = false;\r
+        if(anim){\r
+            cb = {\r
+                callback: function(){\r
+                    c++;\r
+                    if(c == 4){\r
+                        this.animated = false;\r
+                        if(doHide){\r
+                            this.doHide();\r
+                        }\r
+                        Ext.callback(callback, scope, [this]);\r
+                    }\r
+                },\r
+                scope: this,\r
+                duration: this.duration,\r
+                easing: this.easing\r
+            };\r
+            this.animated = true;\r
+        }\r
+\r
+        this.right.setBounds(\r
+                rg.right,\r
+                basePts ? dh : rg.top,\r
+                dw - rg.right,\r
+                basePts ? 0 : (dh - rg.top),\r
+                cb);\r
+\r
+        this.left.setBounds(\r
+                0,\r
+                0,\r
+                rg.left,\r
+                basePts ? 0 : rg.bottom,\r
+                cb);\r
+\r
+        this.top.setBounds(\r
+                basePts ? dw : rg.left,\r
+                0,\r
+                basePts ? 0 : dw - rg.left,\r
+                rg.top,\r
+                cb);\r
+\r
+        this.bottom.setBounds(\r
+                0,\r
+                rg.bottom,\r
+                basePts ? 0 : rg.right,\r
+                dh - rg.bottom,\r
+                cb);\r
+\r
+        if(!anim){\r
+            if(doHide){\r
+                this.doHide();\r
+            }\r
+            if(callback){\r
+                Ext.callback(callback, scope, [this]);\r
+            }\r
+        }\r
+    },\r
+\r
+    destroy : function(){\r
+        this.doHide();\r
+        Ext.destroy(\r
+            this.right,\r
+            this.left,\r
+            this.top,\r
+            this.bottom);\r
+        delete this.el;\r
+        delete this.all;\r
+    }\r
+};\r
+\r
+//backwards compat\r
+Ext.Spotlight = Ext.ux.Spotlight;/**\r
+ * @class Ext.ux.TabCloseMenu\r
+ * @extends Object \r
+ * Plugin (ptype = 'tabclosemenu') for adding a close context menu to tabs.\r
+ * \r
+ * @ptype tabclosemenu\r
+ */\r
+Ext.ux.TabCloseMenu = function(){\r
+    var tabs, menu, ctxItem;\r
+    this.init = function(tp){\r
+        tabs = tp;\r
+        tabs.on('contextmenu', onContextMenu);\r
+    };\r
+\r
+    function onContextMenu(ts, item, e){\r
+        if(!menu){ // create context menu on first right click\r
+            menu = new Ext.menu.Menu({            \r
+            items: [{\r
+                id: tabs.id + '-close',\r
+                text: 'Close Tab',\r
+                handler : function(){\r
+                    tabs.remove(ctxItem);\r
+                }\r
+            },{\r
+                id: tabs.id + '-close-others',\r
+                text: 'Close Other Tabs',\r
+                handler : function(){\r
+                    tabs.items.each(function(item){\r
+                        if(item.closable && item != ctxItem){\r
+                            tabs.remove(item);\r
+                        }\r
+                    });\r
+                }\r
+            }]});\r
+        }\r
+        ctxItem = item;\r
+        var items = menu.items;\r
+        items.get(tabs.id + '-close').setDisabled(!item.closable);\r
+        var disableOthers = true;\r
+        tabs.items.each(function(){\r
+            if(this != item && this.closable){\r
+                disableOthers = false;\r
+                return false;\r
+            }\r
+        });\r
+        items.get(tabs.id + '-close-others').setDisabled(disableOthers);\r
+       e.stopEvent();\r
+        menu.showAt(e.getPoint());\r
+    }\r
+};\r
+\r
+Ext.preg('tabclosemenu', Ext.ux.TabCloseMenu);\r
+Ext.ns('Ext.ux.grid');
+
+/**
+ * @class Ext.ux.grid.TableGrid
+ * @extends Ext.grid.GridPanel
+ * A Grid which creates itself from an existing HTML table element.
+ * @history
+ * 2007-03-01 Original version by Nige "Animal" White
+ * 2007-03-10 jvs Slightly refactored to reuse existing classes * @constructor
+ * @param {String/HTMLElement/Ext.Element} table The table element from which this grid will be created -
+ * The table MUST have some type of size defined for the grid to fill. The container will be
+ * automatically set to position relative if it isn't already.
+ * @param {Object} config A config object that sets properties on this grid and has two additional (optional)
+ * properties: fields and columns which allow for customizing data fields and columns for this grid.
+ */
+Ext.ux.grid.TableGrid = function(table, config){
+    config = config ||
+    {};
+    Ext.apply(this, config);
+    var cf = config.fields || [], ch = config.columns || [];
+    table = Ext.get(table);
+    
+    var ct = table.insertSibling();
+    
+    var fields = [], cols = [];
+    var headers = table.query("thead th");
+    for (var i = 0, h; h = headers[i]; i++) {
+        var text = h.innerHTML;
+        var name = 'tcol-' + i;
+        
+        fields.push(Ext.applyIf(cf[i] ||
+        {}, {
+            name: name,
+            mapping: 'td:nth(' + (i + 1) + ')/@innerHTML'
+        }));
+        
+        cols.push(Ext.applyIf(ch[i] ||
+        {}, {
+            'header': text,
+            'dataIndex': name,
+            'width': h.offsetWidth,
+            'tooltip': h.title,
+            'sortable': true
+        }));
+    }
+    
+    var ds = new Ext.data.Store({
+        reader: new Ext.data.XmlReader({
+            record: 'tbody tr'
+        }, fields)
+    });
+    
+    ds.loadData(table.dom);
+    
+    var cm = new Ext.grid.ColumnModel(cols);
+    
+    if (config.width || config.height) {
+        ct.setSize(config.width || 'auto', config.height || 'auto');
+    }
+    else {
+        ct.setWidth(table.getWidth());
+    }
+    
+    if (config.remove !== false) {
+        table.remove();
+    }
+    
+    Ext.applyIf(this, {
+        'ds': ds,
+        'cm': cm,
+        'sm': new Ext.grid.RowSelectionModel(),
+        autoHeight: true,
+        autoWidth: false
+    });
+    Ext.ux.grid.TableGrid.superclass.constructor.call(this, ct, {});
+};
+
+Ext.extend(Ext.ux.grid.TableGrid, Ext.grid.GridPanel);
+
+//backwards compat
+Ext.grid.TableGrid = Ext.ux.grid.TableGrid;
+
+
+Ext.ux.TabScrollerMenu =  Ext.extend(Object, {
+       pageSize       : 10,
+       maxText        : 15,
+       menuPrefixText : 'Items',
+       constructor    : function(config) {
+               config = config || {};
+               Ext.apply(this, config);
+       },
+       init : function(tabPanel) {
+               Ext.apply(tabPanel, this.tabPanelMethods);
+               
+               tabPanel.tabScrollerMenu = this;
+               var thisRef = this;
+               
+               tabPanel.on({
+                       render : {
+                               scope  : tabPanel,
+                               single : true,
+                               fn     : function() { 
+                                       var newFn = tabPanel.createScrollers.createSequence(thisRef.createPanelsMenu, this);
+                                       tabPanel.createScrollers = newFn;
+                               }
+                       }
+               });
+       },
+       // private && sequeneced
+       createPanelsMenu : function() {
+               var h = this.stripWrap.dom.offsetHeight;
+               
+               //move the right menu item to the left 18px
+               var rtScrBtn = this.header.dom.firstChild;
+               Ext.fly(rtScrBtn).applyStyles({
+                       right : '18px'
+               });
+               
+               var stripWrap = Ext.get(this.strip.dom.parentNode);
+               stripWrap.applyStyles({
+                        'margin-right' : '36px'
+               });
+               
+               // Add the new righthand menu
+               var scrollMenu = this.header.insertFirst({
+                       cls:'x-tab-tabmenu-right'
+               });
+               scrollMenu.setHeight(h);
+               scrollMenu.addClassOnOver('x-tab-tabmenu-over');
+               scrollMenu.on('click', this.showTabsMenu, this);        
+               
+               this.scrollLeft.show = this.scrollLeft.show.createSequence(function() {
+                       scrollMenu.show();                                                                                                                                               
+               });
+               
+               this.scrollLeft.hide = this.scrollLeft.hide.createSequence(function() {
+                       scrollMenu.hide();                                                              
+               });
+               
+       },
+       // public
+       getPageSize : function() {
+               return this.pageSize;
+       },
+       // public
+       setPageSize : function(pageSize) {
+               this.pageSize = pageSize;
+       },
+       // public
+       getMaxText : function() {
+               return this.maxText;
+       },
+       // public
+       setMaxText : function(t) {
+               this.maxText = t;
+       },
+       getMenuPrefixText : function() {
+               return this.menuPrefixText;
+       },
+       setMenuPrefixText : function(t) {
+               this.menuPrefixText = t;
+       },
+       // private && applied to the tab panel itself.
+       tabPanelMethods : {
+               // all execute within the scope of the tab panel
+               // private      
+               showTabsMenu : function(e) {            
+                       if (! this.tabsMenu) {
+                               this.tabsMenu =  new Ext.menu.Menu();
+                               this.on('beforedestroy', this.tabsMenu.destroy, this.tabsMenu);
+                       }
+                       
+                       this.tabsMenu.removeAll();
+                       
+                       this.generateTabMenuItems();
+                       
+                       var target = Ext.get(e.getTarget());
+                       var xy     = target.getXY();
+                       
+                       //Y param + 24 pixels
+                       xy[1] += 24;
+                       
+                       this.tabsMenu.showAt(xy);
+               },
+               // private      
+               generateTabMenuItems : function() {
+                       var curActive  = this.getActiveTab();
+                       var totalItems = this.items.getCount();
+                       var pageSize   = this.tabScrollerMenu.getPageSize();
+                       
+                       
+                       if (totalItems > pageSize)  {
+                               var numSubMenus = Math.floor(totalItems / pageSize);
+                               var remainder   = totalItems % pageSize;
+                               
+                               // Loop through all of the items and create submenus in chunks of 10
+                               for (var i = 0 ; i < numSubMenus; i++) {
+                                       var curPage = (i + 1) * pageSize;
+                                       var menuItems = [];
+                                       
+                                       
+                                       for (var x = 0; x < pageSize; x++) {                            
+                                               index = x + curPage - pageSize;
+                                               var item = this.items.get(index);
+                                               menuItems.push(this.autoGenMenuItem(item));
+                                       }
+                                       
+                                       this.tabsMenu.add({
+                                               text : this.tabScrollerMenu.getMenuPrefixText() + ' '  + (curPage - pageSize + 1) + ' - ' + curPage,
+                                               menu : menuItems
+                                       });
+                                       
+                               }
+                               // remaining items
+                               if (remainder > 0) {
+                                       var start = numSubMenus * pageSize;
+                                       menuItems = [];
+                                       for (var i = start ; i < totalItems; i ++ ) {                                   
+                                               var item = this.items.get(i);
+                                               menuItems.push(this.autoGenMenuItem(item));
+                                       }
+                                       
+                                       
+                                       this.tabsMenu.add({
+                                               text : this.tabScrollerMenu.menuPrefixText  + ' ' + (start + 1) + ' - ' + (start + menuItems.length),
+                                               menu : menuItems
+                                       });
+                                       
+
+                               }
+                       }
+                       else {
+                               this.items.each(function(item) {
+                                       if (item.id != curActive.id && ! item.hidden) {
+                                               menuItems.push(this.autoGenMenuItem(item));
+                                       }
+                               }, this);
+                       }       
+               },
+               // private
+               autoGenMenuItem : function(item) {
+                       var maxText = this.tabScrollerMenu.getMaxText();
+                       var text    = Ext.util.Format.ellipsis(item.title, maxText);
+                       
+                       return {
+                               text      : text,
+                               handler   : this.showTabFromMenu,
+                               scope     : this,
+                               disabled  : item.disabled,
+                               tabToShow : item,
+                               iconCls   : item.iconCls
+                       }
+               
+               },
+               // private
+               showTabFromMenu : function(menuItem) {
+                       this.setActiveTab(menuItem.tabToShow);
+               }       
+       }       
+});
+Ext.ns('Ext.ux.tree');
+
+/**
+ * @class Ext.ux.tree.XmlTreeLoader
+ * @extends Ext.tree.TreeLoader
+ * <p>A TreeLoader that can convert an XML document into a hierarchy of {@link Ext.tree.TreeNode}s.
+ * Any text value included as a text node in the XML will be added to the parent node as an attribute
+ * called <tt>innerText</tt>.  Also, the tag name of each XML node will be added to the tree node as
+ * an attribute called <tt>tagName</tt>.</p>
+ * <p>By default, this class expects that your source XML will provide the necessary attributes on each
+ * node as expected by the {@link Ext.tree.TreePanel} to display and load properly.  However, you can
+ * provide your own custom processing of node attributes by overriding the {@link #processNode} method
+ * and modifying the attributes as needed before they are used to create the associated TreeNode.</p>
+ * @constructor
+ * Creates a new XmlTreeloader.
+ * @param {Object} config A config object containing config properties.
+ */
+Ext.ux.tree.XmlTreeLoader = Ext.extend(Ext.tree.TreeLoader, {
+    /**
+     * @property  XML_NODE_ELEMENT
+     * XML element node (value 1, read-only)
+     * @type Number
+     */
+    XML_NODE_ELEMENT : 1,
+    /**
+     * @property  XML_NODE_TEXT
+     * XML text node (value 3, read-only)
+     * @type Number
+     */
+    XML_NODE_TEXT : 3,
+
+    // private override
+    processResponse : function(response, node, callback){
+        var xmlData = response.responseXML;
+        var root = xmlData.documentElement || xmlData;
+
+        try{
+            node.beginUpdate();
+            node.appendChild(this.parseXml(root));
+            node.endUpdate();
+
+            if(typeof callback == "function"){
+                callback(this, node);
+            }
+        }catch(e){
+            this.handleFailure(response);
+        }
+    },
+
+    // private
+    parseXml : function(node) {
+        var nodes = [];
+        Ext.each(node.childNodes, function(n){
+            if(n.nodeType == this.XML_NODE_ELEMENT){
+                var treeNode = this.createNode(n);
+                if(n.childNodes.length > 0){
+                    var child = this.parseXml(n);
+                    if(typeof child == 'string'){
+                        treeNode.attributes.innerText = child;
+                    }else{
+                        treeNode.appendChild(child);
+                    }
+                }
+                nodes.push(treeNode);
+            }
+            else if(n.nodeType == this.XML_NODE_TEXT){
+                var text = n.nodeValue.trim();
+                if(text.length > 0){
+                    return nodes = text;
+                }
+            }
+        }, this);
+
+        return nodes;
+    },
+
+    // private override
+    createNode : function(node){
+        var attr = {
+            tagName: node.tagName
+        };
+
+        Ext.each(node.attributes, function(a){
+            attr[a.nodeName] = a.nodeValue;
+        });
+
+        this.processAttributes(attr);
+
+        return Ext.ux.tree.XmlTreeLoader.superclass.createNode.call(this, attr);
+    },
+
+    /*
+     * Template method intended to be overridden by subclasses that need to provide
+     * custom attribute processing prior to the creation of each TreeNode.  This method
+     * will be passed a config object containing existing TreeNode attribute name/value
+     * pairs which can be modified as needed directly (no need to return the object).
+     */
+    processAttributes: Ext.emptyFn
+});
+
+//backwards compat
+Ext.ux.XmlTreeLoader = Ext.ux.tree.XmlTreeLoader;