Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / examples / ux / ux-all-debug.js
1 /*!
2  * Ext JS Library 3.3.1
3  * Copyright(c) 2006-2010 Sencha Inc.
4  * licensing@sencha.com
5  * http://www.sencha.com/license
6  */
7 Ext.ns('Ext.ux.grid');
8
9 /**
10  * @class Ext.ux.grid.BufferView
11  * @extends Ext.grid.GridView
12  * A custom GridView which renders rows on an as-needed basis.
13  */
14 Ext.ux.grid.BufferView = Ext.extend(Ext.grid.GridView, {
15         /**
16          * @cfg {Number} rowHeight
17          * The height of a row in the grid.
18          */
19         rowHeight: 19,
20
21         /**
22          * @cfg {Number} borderHeight
23          * The combined height of border-top and border-bottom of a row.
24          */
25         borderHeight: 2,
26
27         /**
28          * @cfg {Boolean/Number} scrollDelay
29          * The number of milliseconds before rendering rows out of the visible
30          * viewing area. Defaults to 100. Rows will render immediately with a config
31          * of false.
32          */
33         scrollDelay: 100,
34
35         /**
36          * @cfg {Number} cacheSize
37          * The number of rows to look forward and backwards from the currently viewable
38          * area.  The cache applies only to rows that have been rendered already.
39          */
40         cacheSize: 20,
41
42         /**
43          * @cfg {Number} cleanDelay
44          * The number of milliseconds to buffer cleaning of extra rows not in the
45          * cache.
46          */
47         cleanDelay: 500,
48
49         initTemplates : function(){
50                 Ext.ux.grid.BufferView.superclass.initTemplates.call(this);
51                 var ts = this.templates;
52                 // empty div to act as a place holder for a row
53                 ts.rowHolder = new Ext.Template(
54                         '<div class="x-grid3-row {alt}" style="{tstyle}"></div>'
55                 );
56                 ts.rowHolder.disableFormats = true;
57                 ts.rowHolder.compile();
58
59                 ts.rowBody = new Ext.Template(
60                         '<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
61                         '<tbody><tr>{cells}</tr>',
62                         (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>' : ''),
63                         '</tbody></table>'
64                 );
65                 ts.rowBody.disableFormats = true;
66                 ts.rowBody.compile();
67         },
68
69         getStyleRowHeight : function(){
70                 return Ext.isBorderBox ? (this.rowHeight + this.borderHeight) : this.rowHeight;
71         },
72
73         getCalculatedRowHeight : function(){
74                 return this.rowHeight + this.borderHeight;
75         },
76
77         getVisibleRowCount : function(){
78                 var rh = this.getCalculatedRowHeight(),
79                     visibleHeight = this.scroller.dom.clientHeight;
80                 return (visibleHeight < 1) ? 0 : Math.ceil(visibleHeight / rh);
81         },
82
83         getVisibleRows: function(){
84                 var count = this.getVisibleRowCount(),
85                     sc = this.scroller.dom.scrollTop,
86                     start = (sc === 0 ? 0 : Math.floor(sc/this.getCalculatedRowHeight())-1);
87                 return {
88                         first: Math.max(start, 0),
89                         last: Math.min(start + count + 2, this.ds.getCount()-1)
90                 };
91         },
92
93         doRender : function(cs, rs, ds, startRow, colCount, stripe, onlyBody){
94                 var ts = this.templates, 
95             ct = ts.cell, 
96             rt = ts.row, 
97             rb = ts.rowBody, 
98             last = colCount-1,
99                     rh = this.getStyleRowHeight(),
100                     vr = this.getVisibleRows(),
101                     tstyle = 'width:'+this.getTotalWidth()+';height:'+rh+'px;',
102                     // buffers
103                     buf = [], 
104             cb, 
105             c, 
106             p = {}, 
107             rp = {tstyle: tstyle}, 
108             r;
109                 for (var j = 0, len = rs.length; j < len; j++) {
110                         r = rs[j]; cb = [];
111                         var rowIndex = (j+startRow),
112                             visible = rowIndex >= vr.first && rowIndex <= vr.last;
113                         if (visible) {
114                                 for (var i = 0; i < colCount; i++) {
115                                         c = cs[i];
116                                         p.id = c.id;
117                                         p.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
118                                         p.attr = p.cellAttr = "";
119                                         p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
120                                         p.style = c.style;
121                                         if (p.value === undefined || p.value === "") {
122                                                 p.value = "&#160;";
123                                         }
124                                         if (r.dirty && typeof r.modified[c.name] !== 'undefined') {
125                                                 p.css += ' x-grid3-dirty-cell';
126                                         }
127                                         cb[cb.length] = ct.apply(p);
128                                 }
129                         }
130                         var alt = [];
131                         if(stripe && ((rowIndex+1) % 2 === 0)){
132                             alt[0] = "x-grid3-row-alt";
133                         }
134                         if(r.dirty){
135                             alt[1] = " x-grid3-dirty-row";
136                         }
137                         rp.cols = colCount;
138                         if(this.getRowClass){
139                             alt[2] = this.getRowClass(r, rowIndex, rp, ds);
140                         }
141                         rp.alt = alt.join(" ");
142                         rp.cells = cb.join("");
143                         buf[buf.length] =  !visible ? ts.rowHolder.apply(rp) : (onlyBody ? rb.apply(rp) : rt.apply(rp));
144                 }
145                 return buf.join("");
146         },
147
148         isRowRendered: function(index){
149                 var row = this.getRow(index);
150                 return row && row.childNodes.length > 0;
151         },
152
153         syncScroll: function(){
154                 Ext.ux.grid.BufferView.superclass.syncScroll.apply(this, arguments);
155                 this.update();
156         },
157
158         // a (optionally) buffered method to update contents of gridview
159         update: function(){
160                 if (this.scrollDelay) {
161                         if (!this.renderTask) {
162                                 this.renderTask = new Ext.util.DelayedTask(this.doUpdate, this);
163                         }
164                         this.renderTask.delay(this.scrollDelay);
165                 }else{
166                         this.doUpdate();
167                 }
168         },
169     
170     onRemove : function(ds, record, index, isUpdate){
171         Ext.ux.grid.BufferView.superclass.onRemove.apply(this, arguments);
172         if(isUpdate !== true){
173             this.update();
174         }
175     },
176
177         doUpdate: function(){
178                 if (this.getVisibleRowCount() > 0) {
179                         var g = this.grid, 
180                 cm = g.colModel, 
181                 ds = g.store,
182                 cs = this.getColumnData(),
183                         vr = this.getVisibleRows(),
184                 row;
185                         for (var i = vr.first; i <= vr.last; i++) {
186                                 // if row is NOT rendered and is visible, render it
187                                 if(!this.isRowRendered(i) && (row = this.getRow(i))){
188                                         var html = this.doRender(cs, [ds.getAt(i)], ds, i, cm.getColumnCount(), g.stripeRows, true);
189                                         row.innerHTML = html;
190                                 }
191                         }
192                         this.clean();
193                 }
194         },
195
196         // a buffered method to clean rows
197         clean : function(){
198                 if(!this.cleanTask){
199                         this.cleanTask = new Ext.util.DelayedTask(this.doClean, this);
200                 }
201                 this.cleanTask.delay(this.cleanDelay);
202         },
203
204         doClean: function(){
205                 if (this.getVisibleRowCount() > 0) {
206                         var vr = this.getVisibleRows();
207                         vr.first -= this.cacheSize;
208                         vr.last += this.cacheSize;
209
210                         var i = 0, rows = this.getRows();
211                         // if first is less than 0, all rows have been rendered
212                         // so lets clean the end...
213                         if(vr.first <= 0){
214                                 i = vr.last + 1;
215                         }
216                         for(var len = this.ds.getCount(); i < len; i++){
217                                 // if current row is outside of first and last and
218                                 // has content, update the innerHTML to nothing
219                                 if ((i < vr.first || i > vr.last) && rows[i].innerHTML) {
220                                         rows[i].innerHTML = '';
221                                 }
222                         }
223                 }
224         },
225     
226     removeTask: function(name){
227         var task = this[name];
228         if(task && task.cancel){
229             task.cancel();
230             this[name] = null;
231         }
232     },
233     
234     destroy : function(){
235         this.removeTask('cleanTask');
236         this.removeTask('renderTask');  
237         Ext.ux.grid.BufferView.superclass.destroy.call(this);
238     },
239
240         layout: function(){
241                 Ext.ux.grid.BufferView.superclass.layout.call(this);
242                 this.update();
243         }
244 });// We are adding these custom layouts to a namespace that does not
245 // exist by default in Ext, so we have to add the namespace first:
246 Ext.ns('Ext.ux.layout');
247
248 /**
249  * @class Ext.ux.layout.CenterLayout
250  * @extends Ext.layout.FitLayout
251  * <p>This is a very simple layout style used to center contents within a container.  This layout works within
252  * nested containers and can also be used as expected as a Viewport layout to center the page layout.</p>
253  * <p>As a subclass of FitLayout, CenterLayout expects to have a single child panel of the container that uses
254  * the layout.  The layout does not require any config options, although the child panel contained within the
255  * layout must provide a fixed or percentage width.  The child panel's height will fit to the container by
256  * default, but you can specify <tt>autoHeight:true</tt> to allow it to autosize based on its content height.
257  * Example usage:</p>
258  * <pre><code>
259 // The content panel is centered in the container
260 var p = new Ext.Panel({
261     title: 'Center Layout',
262     layout: 'ux.center',
263     items: [{
264         title: 'Centered Content',
265         width: '75%',
266         html: 'Some content'
267     }]
268 });
269
270 // If you leave the title blank and specify no border
271 // you'll create a non-visual, structural panel just
272 // for centering the contents in the main container.
273 var p = new Ext.Panel({
274     layout: 'ux.center',
275     border: false,
276     items: [{
277         title: 'Centered Content',
278         width: 300,
279         autoHeight: true,
280         html: 'Some content'
281     }]
282 });
283 </code></pre>
284  */
285 Ext.ux.layout.CenterLayout = Ext.extend(Ext.layout.FitLayout, {
286         // private
287     setItemSize : function(item, size){
288         this.container.addClass('ux-layout-center');
289         item.addClass('ux-layout-center-item');
290         if(item && size.height > 0){
291             if(item.width){
292                 size.width = item.width;
293             }
294             item.setSize(size);
295         }
296     }
297 });
298
299 Ext.Container.LAYOUTS['ux.center'] = Ext.ux.layout.CenterLayout;
300 Ext.ns('Ext.ux.grid');
301
302 /**
303  * @class Ext.ux.grid.CheckColumn
304  * @extends Ext.grid.Column
305  * <p>A Column subclass which renders a checkbox in each column cell which toggles the truthiness of the associated data field on click.</p>
306  * <p><b>Note. As of ExtJS 3.3 this no longer has to be configured as a plugin of the GridPanel.</b></p>
307  * <p>Example usage:</p>
308  * <pre><code>
309 var cm = new Ext.grid.ColumnModel([{
310        header: 'Foo',
311        ...
312     },{
313        xtype: 'checkcolumn',
314        header: 'Indoor?',
315        dataIndex: 'indoor',
316        width: 55
317     }
318 ]);
319
320 // create the grid
321 var grid = new Ext.grid.EditorGridPanel({
322     ...
323     colModel: cm,
324     ...
325 });
326  * </code></pre>
327  * In addition to toggling a Boolean value within the record data, this
328  * class toggles a css class between <tt>'x-grid3-check-col'</tt> and
329  * <tt>'x-grid3-check-col-on'</tt> to alter the background image used for
330  * a column.
331  */
332 Ext.ux.grid.CheckColumn = Ext.extend(Ext.grid.Column, {
333
334     /**
335      * @private
336      * Process and refire events routed from the GridView's processEvent method.
337      */
338     processEvent : function(name, e, grid, rowIndex, colIndex){
339         if (name == 'mousedown') {
340             var record = grid.store.getAt(rowIndex);
341             record.set(this.dataIndex, !record.data[this.dataIndex]);
342             return false; // Cancel row selection.
343         } else {
344             return Ext.grid.ActionColumn.superclass.processEvent.apply(this, arguments);
345         }
346     },
347
348     renderer : function(v, p, record){
349         p.css += ' x-grid3-check-col-td'; 
350         return String.format('<div class="x-grid3-check-col{0}">&#160;</div>', v ? '-on' : '');
351     },
352
353     // Deprecate use as a plugin. Remove in 4.0
354     init: Ext.emptyFn
355 });
356
357 // register ptype. Deprecate. Remove in 4.0
358 Ext.preg('checkcolumn', Ext.ux.grid.CheckColumn);
359
360 // backwards compat. Remove in 4.0
361 Ext.grid.CheckColumn = Ext.ux.grid.CheckColumn;
362
363 // register Column xtype
364 Ext.grid.Column.types.checkcolumn = Ext.ux.grid.CheckColumn;Ext.ns('Ext.ux.grid');
365
366 Ext.ux.grid.ColumnHeaderGroup = Ext.extend(Ext.util.Observable, {
367
368     constructor: function(config){
369         this.config = config;
370     },
371
372     init: function(grid){
373         Ext.applyIf(grid.colModel, this.config);
374         Ext.apply(grid.getView(), this.viewConfig);
375     },
376
377     viewConfig: {
378         initTemplates: function(){
379             this.constructor.prototype.initTemplates.apply(this, arguments);
380             var ts = this.templates || {};
381             if(!ts.gcell){
382                 ts.gcell = new Ext.XTemplate('<td class="x-grid3-hd x-grid3-gcell x-grid3-td-{id} ux-grid-hd-group-row-{row} {cls}" style="{style}">', '<div {tooltip} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">', this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '', '{value}</div></td>');
383             }
384             this.templates = ts;
385             this.hrowRe = new RegExp("ux-grid-hd-group-row-(\\d+)", "");
386         },
387
388         renderHeaders: function(){
389             var ts = this.templates, headers = [], cm = this.cm, rows = cm.rows, tstyle = 'width:' + this.getTotalWidth() + ';';
390
391             for(var row = 0, rlen = rows.length; row < rlen; row++){
392                 var r = rows[row], cells = [];
393                 for(var i = 0, gcol = 0, len = r.length; i < len; i++){
394                     var group = r[i];
395                     group.colspan = group.colspan || 1;
396                     var id = this.getColumnId(group.dataIndex ? cm.findColumnIndex(group.dataIndex) : gcol), gs = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupStyle.call(this, group, gcol);
397                     cells[i] = ts.gcell.apply({
398                         cls: 'ux-grid-hd-group-cell',
399                         id: id,
400                         row: row,
401                         style: 'width:' + gs.width + ';' + (gs.hidden ? 'display:none;' : '') + (group.align ? 'text-align:' + group.align + ';' : ''),
402                         tooltip: group.tooltip ? (Ext.QuickTips.isEnabled() ? 'ext:qtip' : 'title') + '="' + group.tooltip + '"' : '',
403                         istyle: group.align == 'right' ? 'padding-right:16px' : '',
404                         btn: this.grid.enableHdMenu && group.header,
405                         value: group.header || '&nbsp;'
406                     });
407                     gcol += group.colspan;
408                 }
409                 headers[row] = ts.header.apply({
410                     tstyle: tstyle,
411                     cells: cells.join('')
412                 });
413             }
414             headers.push(this.constructor.prototype.renderHeaders.apply(this, arguments));
415             return headers.join('');
416         },
417
418         onColumnWidthUpdated: function(){
419             this.constructor.prototype.onColumnWidthUpdated.apply(this, arguments);
420             Ext.ux.grid.ColumnHeaderGroup.prototype.updateGroupStyles.call(this);
421         },
422
423         onAllColumnWidthsUpdated: function(){
424             this.constructor.prototype.onAllColumnWidthsUpdated.apply(this, arguments);
425             Ext.ux.grid.ColumnHeaderGroup.prototype.updateGroupStyles.call(this);
426         },
427
428         onColumnHiddenUpdated: function(){
429             this.constructor.prototype.onColumnHiddenUpdated.apply(this, arguments);
430             Ext.ux.grid.ColumnHeaderGroup.prototype.updateGroupStyles.call(this);
431         },
432
433         getHeaderCell: function(index){
434             return this.mainHd.query(this.cellSelector)[index];
435         },
436
437         findHeaderCell: function(el){
438             return el ? this.fly(el).findParent('td.x-grid3-hd', this.cellSelectorDepth) : false;
439         },
440
441         findHeaderIndex: function(el){
442             var cell = this.findHeaderCell(el);
443             return cell ? this.getCellIndex(cell) : false;
444         },
445
446         updateSortIcon: function(col, dir){
447             var sc = this.sortClasses, hds = this.mainHd.select(this.cellSelector).removeClass(sc);
448             hds.item(col).addClass(sc[dir == "DESC" ? 1 : 0]);
449         },
450
451         handleHdDown: function(e, t){
452             var el = Ext.get(t);
453             if(el.hasClass('x-grid3-hd-btn')){
454                 e.stopEvent();
455                 var hd = this.findHeaderCell(t);
456                 Ext.fly(hd).addClass('x-grid3-hd-menu-open');
457                 var index = this.getCellIndex(hd);
458                 this.hdCtxIndex = index;
459                 var ms = this.hmenu.items, cm = this.cm;
460                 ms.get('asc').setDisabled(!cm.isSortable(index));
461                 ms.get('desc').setDisabled(!cm.isSortable(index));
462                 this.hmenu.on('hide', function(){
463                     Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
464                 }, this, {
465                     single: true
466                 });
467                 this.hmenu.show(t, 'tl-bl?');
468             }else if(el.hasClass('ux-grid-hd-group-cell') || Ext.fly(t).up('.ux-grid-hd-group-cell')){
469                 e.stopEvent();
470             }
471         },
472
473         handleHdMove: function(e, t){
474             var hd = this.findHeaderCell(this.activeHdRef);
475             if(hd && !this.headersDisabled && !Ext.fly(hd).hasClass('ux-grid-hd-group-cell')){
476                 var hw = this.splitHandleWidth || 5, r = this.activeHdRegion, x = e.getPageX(), ss = hd.style, cur = '';
477                 if(this.grid.enableColumnResize !== false){
478                     if(x - r.left <= hw && this.cm.isResizable(this.activeHdIndex - 1)){
479                         cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize'; // col-resize
480                                                                                                 // not
481                                                                                                 // always
482                                                                                                 // supported
483                     }else if(r.right - x <= (!this.activeHdBtn ? hw : 2) && this.cm.isResizable(this.activeHdIndex)){
484                         cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize';
485                     }
486                 }
487                 ss.cursor = cur;
488             }
489         },
490
491         handleHdOver: function(e, t){
492             var hd = this.findHeaderCell(t);
493             if(hd && !this.headersDisabled){
494                 this.activeHdRef = t;
495                 this.activeHdIndex = this.getCellIndex(hd);
496                 var fly = this.fly(hd);
497                 this.activeHdRegion = fly.getRegion();
498                 if(!(this.cm.isMenuDisabled(this.activeHdIndex) || fly.hasClass('ux-grid-hd-group-cell'))){
499                     fly.addClass('x-grid3-hd-over');
500                     this.activeHdBtn = fly.child('.x-grid3-hd-btn');
501                     if(this.activeHdBtn){
502                         this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight - 1) + 'px';
503                     }
504                 }
505             }
506         },
507
508         handleHdOut: function(e, t){
509             var hd = this.findHeaderCell(t);
510             if(hd && (!Ext.isIE || !e.within(hd, true))){
511                 this.activeHdRef = null;
512                 this.fly(hd).removeClass('x-grid3-hd-over');
513                 hd.style.cursor = '';
514             }
515         },
516
517         handleHdMenuClick: function(item){
518             var index = this.hdCtxIndex, cm = this.cm, ds = this.ds, id = item.getItemId();
519             switch(id){
520                 case 'asc':
521                     ds.sort(cm.getDataIndex(index), 'ASC');
522                     break;
523                 case 'desc':
524                     ds.sort(cm.getDataIndex(index), 'DESC');
525                     break;
526                 default:
527                     if(id.substr(0, 6) == 'group-'){
528                         var i = id.split('-'), row = parseInt(i[1], 10), col = parseInt(i[2], 10), r = this.cm.rows[row], group, gcol = 0;
529                         for(var i = 0, len = r.length; i < len; i++){
530                             group = r[i];
531                             if(col >= gcol && col < gcol + group.colspan){
532                                 break;
533                             }
534                             gcol += group.colspan;
535                         }
536                         if(item.checked){
537                             var max = cm.getColumnsBy(this.isHideableColumn, this).length;
538                             for(var i = gcol, len = gcol + group.colspan; i < len; i++){
539                                 if(!cm.isHidden(i)){
540                                     max--;
541                                 }
542                             }
543                             if(max < 1){
544                                 this.onDenyColumnHide();
545                                 return false;
546                             }
547                         }
548                         for(var i = gcol, len = gcol + group.colspan; i < len; i++){
549                             if(cm.config[i].fixed !== true && cm.config[i].hideable !== false){
550                                 cm.setHidden(i, item.checked);
551                             }
552                         }
553                     }else if(id.substr(0, 4) == 'col-'){
554                         index = cm.getIndexById(id.substr(4));
555                         if(index != -1){
556                             if(item.checked && cm.getColumnsBy(this.isHideableColumn, this).length <= 1){
557                                 this.onDenyColumnHide();
558                                 return false;
559                             }
560                             cm.setHidden(index, item.checked);
561                         }
562                     }
563                     if(id.substr(0, 6) == 'group-' || id.substr(0, 4) == 'col-'){
564                         item.checked = !item.checked;
565                         if(item.menu){
566                             var updateChildren = function(menu){
567                                 menu.items.each(function(childItem){
568                                     if(!childItem.disabled){
569                                         childItem.setChecked(item.checked, false);
570                                         if(childItem.menu){
571                                             updateChildren(childItem.menu);
572                                         }
573                                     }
574                                 });
575                             }
576                             updateChildren(item.menu);
577                         }
578                         var parentMenu = item, parentItem;
579                         while(parentMenu = parentMenu.parentMenu){
580                             if(!parentMenu.parentMenu || !(parentItem = parentMenu.parentMenu.items.get(parentMenu.getItemId())) || !parentItem.setChecked){
581                                 break;
582                             }
583                             var checked = parentMenu.items.findIndexBy(function(m){
584                                 return m.checked;
585                             }) >= 0;
586                             parentItem.setChecked(checked, true);
587                         }
588                         item.checked = !item.checked;
589                     }
590             }
591             return true;
592         },
593
594         beforeColMenuShow: function(){
595             var cm = this.cm, rows = this.cm.rows;
596             this.colMenu.removeAll();
597             for(var col = 0, clen = cm.getColumnCount(); col < clen; col++){
598                 var menu = this.colMenu, title = cm.getColumnHeader(col), text = [];
599                 if(cm.config[col].fixed !== true && cm.config[col].hideable !== false){
600                     for(var row = 0, rlen = rows.length; row < rlen; row++){
601                         var r = rows[row], group, gcol = 0;
602                         for(var i = 0, len = r.length; i < len; i++){
603                             group = r[i];
604                             if(col >= gcol && col < gcol + group.colspan){
605                                 break;
606                             }
607                             gcol += group.colspan;
608                         }
609                         if(group && group.header){
610                             if(cm.hierarchicalColMenu){
611                                 var gid = 'group-' + row + '-' + gcol,
612                                     item = menu.items ? menu.getComponent(gid) : null,
613                                     submenu = item ? item.menu : null;
614                                 if(!submenu){
615                                     submenu = new Ext.menu.Menu({
616                                         itemId: gid
617                                     });
618                                     submenu.on("itemclick", this.handleHdMenuClick, this);
619                                     var checked = false, disabled = true;
620                                     for(var c = gcol, lc = gcol + group.colspan; c < lc; c++){
621                                         if(!cm.isHidden(c)){
622                                             checked = true;
623                                         }
624                                         if(cm.config[c].hideable !== false){
625                                             disabled = false;
626                                         }
627                                     }
628                                     menu.add({
629                                         itemId: gid,
630                                         text: group.header,
631                                         menu: submenu,
632                                         hideOnClick: false,
633                                         checked: checked,
634                                         disabled: disabled
635                                     });
636                                 }
637                                 menu = submenu;
638                             }else{
639                                 text.push(group.header);
640                             }
641                         }
642                     }
643                     text.push(title);
644                     menu.add(new Ext.menu.CheckItem({
645                         itemId: "col-" + cm.getColumnId(col),
646                         text: text.join(' '),
647                         checked: !cm.isHidden(col),
648                         hideOnClick: false,
649                         disabled: cm.config[col].hideable === false
650                     }));
651                 }
652             }
653         },
654
655         afterRenderUI: function(){
656             this.constructor.prototype.afterRenderUI.apply(this, arguments);
657             Ext.apply(this.columnDrop, Ext.ux.grid.ColumnHeaderGroup.prototype.columnDropConfig);
658             Ext.apply(this.splitZone, Ext.ux.grid.ColumnHeaderGroup.prototype.splitZoneConfig);
659         }
660     },
661
662     splitZoneConfig: {
663         allowHeaderDrag: function(e){
664             return !e.getTarget(null, null, true).hasClass('ux-grid-hd-group-cell');
665         }
666     },
667
668     columnDropConfig: {
669         getTargetFromEvent: function(e){
670             var t = Ext.lib.Event.getTarget(e);
671             return this.view.findHeaderCell(t);
672         },
673
674         positionIndicator: function(h, n, e){
675             var data = Ext.ux.grid.ColumnHeaderGroup.prototype.getDragDropData.call(this, h, n, e);
676             if(data === false){
677                 return false;
678             }
679             var px = data.px + this.proxyOffsets[0];
680             this.proxyTop.setLeftTop(px, data.r.top + this.proxyOffsets[1]);
681             this.proxyTop.show();
682             this.proxyBottom.setLeftTop(px, data.r.bottom);
683             this.proxyBottom.show();
684             return data.pt;
685         },
686
687         onNodeDrop: function(n, dd, e, data){
688             var h = data.header;
689             if(h != n){
690                 var d = Ext.ux.grid.ColumnHeaderGroup.prototype.getDragDropData.call(this, h, n, e);
691                 if(d === false){
692                     return false;
693                 }
694                 var cm = this.grid.colModel, right = d.oldIndex < d.newIndex, rows = cm.rows;
695                 for(var row = d.row, rlen = rows.length; row < rlen; row++){
696                     var r = rows[row], len = r.length, fromIx = 0, span = 1, toIx = len;
697                     for(var i = 0, gcol = 0; i < len; i++){
698                         var group = r[i];
699                         if(d.oldIndex >= gcol && d.oldIndex < gcol + group.colspan){
700                             fromIx = i;
701                         }
702                         if(d.oldIndex + d.colspan - 1 >= gcol && d.oldIndex + d.colspan - 1 < gcol + group.colspan){
703                             span = i - fromIx + 1;
704                         }
705                         if(d.newIndex >= gcol && d.newIndex < gcol + group.colspan){
706                             toIx = i;
707                         }
708                         gcol += group.colspan;
709                     }
710                     var groups = r.splice(fromIx, span);
711                     rows[row] = r.splice(0, toIx - (right ? span : 0)).concat(groups).concat(r);
712                 }
713                 for(var c = 0; c < d.colspan; c++){
714                     var oldIx = d.oldIndex + (right ? 0 : c), newIx = d.newIndex + (right ? -1 : c);
715                     cm.moveColumn(oldIx, newIx);
716                     this.grid.fireEvent("columnmove", oldIx, newIx);
717                 }
718                 return true;
719             }
720             return false;
721         }
722     },
723
724     getGroupStyle: function(group, gcol){
725         var width = 0, hidden = true;
726         for(var i = gcol, len = gcol + group.colspan; i < len; i++){
727             if(!this.cm.isHidden(i)){
728                 var cw = this.cm.getColumnWidth(i);
729                 if(typeof cw == 'number'){
730                     width += cw;
731                 }
732                 hidden = false;
733             }
734         }
735         return {
736             width: (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2) ? width : Math.max(width - this.borderWidth, 0)) + 'px',
737             hidden: hidden
738         };
739     },
740
741     updateGroupStyles: function(col){
742         var tables = this.mainHd.query('.x-grid3-header-offset > table'), tw = this.getTotalWidth(), rows = this.cm.rows;
743         for(var row = 0; row < tables.length; row++){
744             tables[row].style.width = tw;
745             if(row < rows.length){
746                 var cells = tables[row].firstChild.firstChild.childNodes;
747                 for(var i = 0, gcol = 0; i < cells.length; i++){
748                     var group = rows[row][i];
749                     if((typeof col != 'number') || (col >= gcol && col < gcol + group.colspan)){
750                         var gs = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupStyle.call(this, group, gcol);
751                         cells[i].style.width = gs.width;
752                         cells[i].style.display = gs.hidden ? 'none' : '';
753                     }
754                     gcol += group.colspan;
755                 }
756             }
757         }
758     },
759
760     getGroupRowIndex: function(el){
761         if(el){
762             var m = el.className.match(this.hrowRe);
763             if(m && m[1]){
764                 return parseInt(m[1], 10);
765             }
766         }
767         return this.cm.rows.length;
768     },
769
770     getGroupSpan: function(row, col){
771         if(row < 0){
772             return {
773                 col: 0,
774                 colspan: this.cm.getColumnCount()
775             };
776         }
777         var r = this.cm.rows[row];
778         if(r){
779             for(var i = 0, gcol = 0, len = r.length; i < len; i++){
780                 var group = r[i];
781                 if(col >= gcol && col < gcol + group.colspan){
782                     return {
783                         col: gcol,
784                         colspan: group.colspan
785                     };
786                 }
787                 gcol += group.colspan;
788             }
789             return {
790                 col: gcol,
791                 colspan: 0
792             };
793         }
794         return {
795             col: col,
796             colspan: 1
797         };
798     },
799
800     getDragDropData: function(h, n, e){
801         if(h.parentNode != n.parentNode){
802             return false;
803         }
804         var cm = this.grid.colModel, x = Ext.lib.Event.getPageX(e), r = Ext.lib.Dom.getRegion(n.firstChild), px, pt;
805         if((r.right - x) <= (r.right - r.left) / 2){
806             px = r.right + this.view.borderWidth;
807             pt = "after";
808         }else{
809             px = r.left;
810             pt = "before";
811         }
812         var oldIndex = this.view.getCellIndex(h), newIndex = this.view.getCellIndex(n);
813         if(cm.isFixed(newIndex)){
814             return false;
815         }
816         var row = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupRowIndex.call(this.view, h),
817             oldGroup = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupSpan.call(this.view, row, oldIndex),
818             newGroup = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupSpan.call(this.view, row, newIndex),
819             oldIndex = oldGroup.col;
820             newIndex = newGroup.col + (pt == "after" ? newGroup.colspan : 0);
821         if(newIndex >= oldGroup.col && newIndex <= oldGroup.col + oldGroup.colspan){
822             return false;
823         }
824         var parentGroup = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupSpan.call(this.view, row - 1, oldIndex);
825         if(newIndex < parentGroup.col || newIndex > parentGroup.col + parentGroup.colspan){
826             return false;
827         }
828         return {
829             r: r,
830             px: px,
831             pt: pt,
832             row: row,
833             oldIndex: oldIndex,
834             newIndex: newIndex,
835             colspan: oldGroup.colspan
836         };
837     }
838 });Ext.ns('Ext.ux.tree');
839
840 /**
841  * @class Ext.ux.tree.ColumnTree
842  * @extends Ext.tree.TreePanel
843  * 
844  * @xtype columntree
845  */
846 Ext.ux.tree.ColumnTree = Ext.extend(Ext.tree.TreePanel, {
847     lines : false,
848     borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
849     cls : 'x-column-tree',
850
851     onRender : function(){
852         Ext.tree.ColumnTree.superclass.onRender.apply(this, arguments);
853         this.headers = this.header.createChild({cls:'x-tree-headers'});
854
855         var cols = this.columns, c;
856         var totalWidth = 0;
857         var scrollOffset = 19; // similar to Ext.grid.GridView default
858
859         for(var i = 0, len = cols.length; i < len; i++){
860              c = cols[i];
861              totalWidth += c.width;
862              this.headers.createChild({
863                  cls:'x-tree-hd ' + (c.cls?c.cls+'-hd':''),
864                  cn: {
865                      cls:'x-tree-hd-text',
866                      html: c.header
867                  },
868                  style:'width:'+(c.width-this.borderWidth)+'px;'
869              });
870         }
871         this.headers.createChild({cls:'x-clear'});
872         // prevent floats from wrapping when clipped
873         this.headers.setWidth(totalWidth+scrollOffset);
874         this.innerCt.setWidth(totalWidth);
875     }
876 });
877
878 Ext.reg('columntree', Ext.ux.tree.ColumnTree);
879
880 //backwards compat
881 Ext.tree.ColumnTree = Ext.ux.tree.ColumnTree;
882
883
884 /**
885  * @class Ext.ux.tree.ColumnNodeUI
886  * @extends Ext.tree.TreeNodeUI
887  */
888 Ext.ux.tree.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
889     focus: Ext.emptyFn, // prevent odd scrolling behavior
890
891     renderElements : function(n, a, targetNode, bulkRender){
892         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
893
894         var t = n.getOwnerTree();
895         var cols = t.columns;
896         var bw = t.borderWidth;
897         var c = cols[0];
898
899         var buf = [
900              '<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf ', a.cls,'">',
901                 '<div class="x-tree-col" style="width:',c.width-bw,'px;">',
902                     '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
903                     '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow">',
904                     '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on">',
905                     '<a hidefocus="on" class="x-tree-node-anchor" href="',a.href ? a.href : "#",'" tabIndex="1" ',
906                     a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '>',
907                     '<span unselectable="on">', n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</span></a>",
908                 "</div>"];
909          for(var i = 1, len = cols.length; i < len; i++){
910              c = cols[i];
911
912              buf.push('<div class="x-tree-col ',(c.cls?c.cls:''),'" style="width:',c.width-bw,'px;">',
913                         '<div class="x-tree-col-text">',(c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</div>",
914                       "</div>");
915          }
916          buf.push(
917             '<div class="x-clear"></div></div>',
918             '<ul class="x-tree-node-ct" style="display:none;"></ul>',
919             "</li>");
920
921         if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
922             this.wrap = Ext.DomHelper.insertHtml("beforeBegin",
923                                 n.nextSibling.ui.getEl(), buf.join(""));
924         }else{
925             this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));
926         }
927
928         this.elNode = this.wrap.childNodes[0];
929         this.ctNode = this.wrap.childNodes[1];
930         var cs = this.elNode.firstChild.childNodes;
931         this.indentNode = cs[0];
932         this.ecNode = cs[1];
933         this.iconNode = cs[2];
934         this.anchor = cs[3];
935         this.textNode = cs[3].firstChild;
936     }
937 });
938
939 //backwards compat
940 Ext.tree.ColumnNodeUI = Ext.ux.tree.ColumnNodeUI;
941 /**
942  * @class Ext.DataView.LabelEditor
943  * @extends Ext.Editor
944  * 
945  */
946 Ext.DataView.LabelEditor = Ext.extend(Ext.Editor, {
947     alignment: "tl-tl",
948     hideEl : false,
949     cls: "x-small-editor",
950     shim: false,
951     completeOnEnter: true,
952     cancelOnEsc: true,
953     labelSelector: 'span.x-editable',
954     
955     constructor: function(cfg, field){
956         Ext.DataView.LabelEditor.superclass.constructor.call(this,
957             field || new Ext.form.TextField({
958                 allowBlank: false,
959                 growMin:90,
960                 growMax:240,
961                 grow:true,
962                 selectOnFocus:true
963             }), cfg
964         );
965     },
966     
967     init : function(view){
968         this.view = view;
969         view.on('render', this.initEditor, this);
970         this.on('complete', this.onSave, this);
971     },
972
973     initEditor : function(){
974         this.view.on({
975             scope: this,
976             containerclick: this.doBlur,
977             click: this.doBlur
978         });
979         this.view.getEl().on('mousedown', this.onMouseDown, this, {delegate: this.labelSelector});
980     },
981     
982     doBlur: function(){
983         if(this.editing){
984             this.field.blur();
985         }
986     },
987
988     onMouseDown : function(e, target){
989         if(!e.ctrlKey && !e.shiftKey){
990             var item = this.view.findItemFromChild(target);
991             e.stopEvent();
992             var record = this.view.store.getAt(this.view.indexOf(item));
993             this.startEdit(target, record.data[this.dataIndex]);
994             this.activeRecord = record;
995         }else{
996             e.preventDefault();
997         }
998     },
999
1000     onSave : function(ed, value){
1001         this.activeRecord.set(this.dataIndex, value);
1002     }
1003 });
1004
1005
1006 Ext.DataView.DragSelector = function(cfg){
1007     cfg = cfg || {};
1008     var view, proxy, tracker;
1009     var rs, bodyRegion, dragRegion = new Ext.lib.Region(0,0,0,0);
1010     var dragSafe = cfg.dragSafe === true;
1011
1012     this.init = function(dataView){
1013         view = dataView;
1014         view.on('render', onRender);
1015     };
1016
1017     function fillRegions(){
1018         rs = [];
1019         view.all.each(function(el){
1020             rs[rs.length] = el.getRegion();
1021         });
1022         bodyRegion = view.el.getRegion();
1023     }
1024
1025     function cancelClick(){
1026         return false;
1027     }
1028
1029     function onBeforeStart(e){
1030         return !dragSafe || e.target == view.el.dom;
1031     }
1032
1033     function onStart(e){
1034         view.on('containerclick', cancelClick, view, {single:true});
1035         if(!proxy){
1036             proxy = view.el.createChild({cls:'x-view-selector'});
1037         }else{
1038             if(proxy.dom.parentNode !== view.el.dom){
1039                 view.el.dom.appendChild(proxy.dom);
1040             }
1041             proxy.setDisplayed('block');
1042         }
1043         fillRegions();
1044         view.clearSelections();
1045     }
1046
1047     function onDrag(e){
1048         var startXY = tracker.startXY;
1049         var xy = tracker.getXY();
1050
1051         var x = Math.min(startXY[0], xy[0]);
1052         var y = Math.min(startXY[1], xy[1]);
1053         var w = Math.abs(startXY[0] - xy[0]);
1054         var h = Math.abs(startXY[1] - xy[1]);
1055
1056         dragRegion.left = x;
1057         dragRegion.top = y;
1058         dragRegion.right = x+w;
1059         dragRegion.bottom = y+h;
1060
1061         dragRegion.constrainTo(bodyRegion);
1062         proxy.setRegion(dragRegion);
1063
1064         for(var i = 0, len = rs.length; i < len; i++){
1065             var r = rs[i], sel = dragRegion.intersect(r);
1066             if(sel && !r.selected){
1067                 r.selected = true;
1068                 view.select(i, true);
1069             }else if(!sel && r.selected){
1070                 r.selected = false;
1071                 view.deselect(i);
1072             }
1073         }
1074     }
1075
1076     function onEnd(e){
1077         if (!Ext.isIE) {
1078             view.un('containerclick', cancelClick, view);    
1079         }        
1080         if(proxy){
1081             proxy.setDisplayed(false);
1082         }
1083     }
1084
1085     function onRender(view){
1086         tracker = new Ext.dd.DragTracker({
1087             onBeforeStart: onBeforeStart,
1088             onStart: onStart,
1089             onDrag: onDrag,
1090             onEnd: onEnd
1091         });
1092         tracker.initEl(view.el);
1093     }
1094 };Ext.ns('Ext.ux.form');
1095
1096 /**
1097  * @class Ext.ux.form.FileUploadField
1098  * @extends Ext.form.TextField
1099  * Creates a file upload field.
1100  * @xtype fileuploadfield
1101  */
1102 Ext.ux.form.FileUploadField = Ext.extend(Ext.form.TextField,  {
1103     /**
1104      * @cfg {String} buttonText The button text to display on the upload button (defaults to
1105      * 'Browse...').  Note that if you supply a value for {@link #buttonCfg}, the buttonCfg.text
1106      * value will be used instead if available.
1107      */
1108     buttonText: 'Browse...',
1109     /**
1110      * @cfg {Boolean} buttonOnly True to display the file upload field as a button with no visible
1111      * text field (defaults to false).  If true, all inherited TextField members will still be available.
1112      */
1113     buttonOnly: false,
1114     /**
1115      * @cfg {Number} buttonOffset The number of pixels of space reserved between the button and the text field
1116      * (defaults to 3).  Note that this only applies if {@link #buttonOnly} = false.
1117      */
1118     buttonOffset: 3,
1119     /**
1120      * @cfg {Object} buttonCfg A standard {@link Ext.Button} config object.
1121      */
1122
1123     // private
1124     readOnly: true,
1125
1126     /**
1127      * @hide
1128      * @method autoSize
1129      */
1130     autoSize: Ext.emptyFn,
1131
1132     // private
1133     initComponent: function(){
1134         Ext.ux.form.FileUploadField.superclass.initComponent.call(this);
1135
1136         this.addEvents(
1137             /**
1138              * @event fileselected
1139              * Fires when the underlying file input field's value has changed from the user
1140              * selecting a new file from the system file selection dialog.
1141              * @param {Ext.ux.form.FileUploadField} this
1142              * @param {String} value The file value returned by the underlying file input field
1143              */
1144             'fileselected'
1145         );
1146     },
1147
1148     // private
1149     onRender : function(ct, position){
1150         Ext.ux.form.FileUploadField.superclass.onRender.call(this, ct, position);
1151
1152         this.wrap = this.el.wrap({cls:'x-form-field-wrap x-form-file-wrap'});
1153         this.el.addClass('x-form-file-text');
1154         this.el.dom.removeAttribute('name');
1155         this.createFileInput();
1156
1157         var btnCfg = Ext.applyIf(this.buttonCfg || {}, {
1158             text: this.buttonText
1159         });
1160         this.button = new Ext.Button(Ext.apply(btnCfg, {
1161             renderTo: this.wrap,
1162             cls: 'x-form-file-btn' + (btnCfg.iconCls ? ' x-btn-icon' : '')
1163         }));
1164
1165         if(this.buttonOnly){
1166             this.el.hide();
1167             this.wrap.setWidth(this.button.getEl().getWidth());
1168         }
1169
1170         this.bindListeners();
1171         this.resizeEl = this.positionEl = this.wrap;
1172     },
1173     
1174     bindListeners: function(){
1175         this.fileInput.on({
1176             scope: this,
1177             mouseenter: function() {
1178                 this.button.addClass(['x-btn-over','x-btn-focus'])
1179             },
1180             mouseleave: function(){
1181                 this.button.removeClass(['x-btn-over','x-btn-focus','x-btn-click'])
1182             },
1183             mousedown: function(){
1184                 this.button.addClass('x-btn-click')
1185             },
1186             mouseup: function(){
1187                 this.button.removeClass(['x-btn-over','x-btn-focus','x-btn-click'])
1188             },
1189             change: function(){
1190                 var v = this.fileInput.dom.value;
1191                 this.setValue(v);
1192                 this.fireEvent('fileselected', this, v);    
1193             }
1194         }); 
1195     },
1196     
1197     createFileInput : function() {
1198         this.fileInput = this.wrap.createChild({
1199             id: this.getFileInputId(),
1200             name: this.name||this.getId(),
1201             cls: 'x-form-file',
1202             tag: 'input',
1203             type: 'file',
1204             size: 1
1205         });
1206     },
1207     
1208     reset : function(){
1209         this.fileInput.remove();
1210         this.createFileInput();
1211         this.bindListeners();
1212         Ext.ux.form.FileUploadField.superclass.reset.call(this);
1213     },
1214
1215     // private
1216     getFileInputId: function(){
1217         return this.id + '-file';
1218     },
1219
1220     // private
1221     onResize : function(w, h){
1222         Ext.ux.form.FileUploadField.superclass.onResize.call(this, w, h);
1223
1224         this.wrap.setWidth(w);
1225
1226         if(!this.buttonOnly){
1227             var w = this.wrap.getWidth() - this.button.getEl().getWidth() - this.buttonOffset;
1228             this.el.setWidth(w);
1229         }
1230     },
1231
1232     // private
1233     onDestroy: function(){
1234         Ext.ux.form.FileUploadField.superclass.onDestroy.call(this);
1235         Ext.destroy(this.fileInput, this.button, this.wrap);
1236     },
1237     
1238     onDisable: function(){
1239         Ext.ux.form.FileUploadField.superclass.onDisable.call(this);
1240         this.doDisable(true);
1241     },
1242     
1243     onEnable: function(){
1244         Ext.ux.form.FileUploadField.superclass.onEnable.call(this);
1245         this.doDisable(false);
1246
1247     },
1248     
1249     // private
1250     doDisable: function(disabled){
1251         this.fileInput.dom.disabled = disabled;
1252         this.button.setDisabled(disabled);
1253     },
1254
1255
1256     // private
1257     preFocus : Ext.emptyFn,
1258
1259     // private
1260     alignErrorIcon : function(){
1261         this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
1262     }
1263
1264 });
1265
1266 Ext.reg('fileuploadfield', Ext.ux.form.FileUploadField);
1267
1268 // backwards compat
1269 Ext.form.FileUploadField = Ext.ux.form.FileUploadField;
1270 /**
1271  * @class Ext.ux.GMapPanel
1272  * @extends Ext.Panel
1273  * @author Shea Frederick
1274  */
1275 Ext.ux.GMapPanel = Ext.extend(Ext.Panel, {
1276     initComponent : function(){
1277         
1278         var defConfig = {
1279             plain: true,
1280             zoomLevel: 3,
1281             yaw: 180,
1282             pitch: 0,
1283             zoom: 0,
1284             gmapType: 'map',
1285             border: false
1286         };
1287         
1288         Ext.applyIf(this,defConfig);
1289         
1290         Ext.ux.GMapPanel.superclass.initComponent.call(this);        
1291
1292     },
1293     afterRender : function(){
1294         
1295         var wh = this.ownerCt.getSize();
1296         Ext.applyIf(this, wh);
1297         
1298         Ext.ux.GMapPanel.superclass.afterRender.call(this);    
1299         
1300         if (this.gmapType === 'map'){
1301             this.gmap = new GMap2(this.body.dom);
1302         }
1303         
1304         if (this.gmapType === 'panorama'){
1305             this.gmap = new GStreetviewPanorama(this.body.dom);
1306         }
1307         
1308         if (typeof this.addControl == 'object' && this.gmapType === 'map') {
1309             this.gmap.addControl(this.addControl);
1310         }
1311         
1312         if (typeof this.setCenter === 'object') {
1313             if (typeof this.setCenter.geoCodeAddr === 'string'){
1314                 this.geoCodeLookup(this.setCenter.geoCodeAddr);
1315             }else{
1316                 if (this.gmapType === 'map'){
1317                     var point = new GLatLng(this.setCenter.lat,this.setCenter.lng);
1318                     this.gmap.setCenter(point, this.zoomLevel);    
1319                 }
1320                 if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){
1321                     this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear);
1322                 }
1323             }
1324             if (this.gmapType === 'panorama'){
1325                 this.gmap.setLocationAndPOV(new GLatLng(this.setCenter.lat,this.setCenter.lng), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoom});
1326             }
1327         }
1328
1329         GEvent.bind(this.gmap, 'load', this, function(){
1330             this.onMapReady();
1331         });
1332
1333     },
1334     onMapReady : function(){
1335         this.addMarkers(this.markers);
1336         this.addMapControls();
1337         this.addOptions();  
1338     },
1339     onResize : function(w, h){
1340
1341         if (typeof this.getMap() == 'object') {
1342             this.gmap.checkResize();
1343         }
1344         
1345         Ext.ux.GMapPanel.superclass.onResize.call(this, w, h);
1346
1347     },
1348     setSize : function(width, height, animate){
1349         
1350         if (typeof this.getMap() == 'object') {
1351             this.gmap.checkResize();
1352         }
1353         
1354         Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate);
1355         
1356     },
1357     getMap : function(){
1358         
1359         return this.gmap;
1360         
1361     },
1362     getCenter : function(){
1363         
1364         return this.getMap().getCenter();
1365         
1366     },
1367     getCenterLatLng : function(){
1368         
1369         var ll = this.getCenter();
1370         return {lat: ll.lat(), lng: ll.lng()};
1371         
1372     },
1373     addMarkers : function(markers) {
1374         
1375         if (Ext.isArray(markers)){
1376             for (var i = 0; i < markers.length; i++) {
1377                 var mkr_point = new GLatLng(markers[i].lat,markers[i].lng);
1378                 this.addMarker(mkr_point,markers[i].marker,false,markers[i].setCenter, markers[i].listeners);
1379             }
1380         }
1381         
1382     },
1383     addMarker : function(point, marker, clear, center, listeners){
1384         
1385         Ext.applyIf(marker,G_DEFAULT_ICON);
1386
1387         if (clear === true){
1388             this.getMap().clearOverlays();
1389         }
1390         if (center === true) {
1391             this.getMap().setCenter(point, this.zoomLevel);
1392         }
1393
1394         var mark = new GMarker(point,marker);
1395         if (typeof listeners === 'object'){
1396             for (evt in listeners) {
1397                 GEvent.bind(mark, evt, this, listeners[evt]);
1398             }
1399         }
1400         this.getMap().addOverlay(mark);
1401
1402     },
1403     addMapControls : function(){
1404         
1405         if (this.gmapType === 'map') {
1406             if (Ext.isArray(this.mapControls)) {
1407                 for(i=0;i<this.mapControls.length;i++){
1408                     this.addMapControl(this.mapControls[i]);
1409                 }
1410             }else if(typeof this.mapControls === 'string'){
1411                 this.addMapControl(this.mapControls);
1412             }else if(typeof this.mapControls === 'object'){
1413                 this.getMap().addControl(this.mapControls);
1414             }
1415         }
1416         
1417     },
1418     addMapControl : function(mc){
1419         
1420         var mcf = window[mc];
1421         if (typeof mcf === 'function') {
1422             this.getMap().addControl(new mcf());
1423         }    
1424         
1425     },
1426     addOptions : function(){
1427         
1428         if (Ext.isArray(this.mapConfOpts)) {
1429             var mc;
1430             for(i=0;i<this.mapConfOpts.length;i++){
1431                 this.addOption(this.mapConfOpts[i]);
1432             }
1433         }else if(typeof this.mapConfOpts === 'string'){
1434             this.addOption(this.mapConfOpts);
1435         }        
1436         
1437     },
1438     addOption : function(mc){
1439         
1440         var mcf = this.getMap()[mc];
1441         if (typeof mcf === 'function') {
1442             this.getMap()[mc]();
1443         }    
1444         
1445     },
1446     geoCodeLookup : function(addr) {
1447         
1448         this.geocoder = new GClientGeocoder();
1449         this.geocoder.getLocations(addr, this.addAddressToMap.createDelegate(this));
1450         
1451     },
1452     addAddressToMap : function(response) {
1453         
1454         if (!response || response.Status.code != 200) {
1455             Ext.MessageBox.alert('Error', 'Code '+response.Status.code+' Error Returned');
1456         }else{
1457             place = response.Placemark[0];
1458             addressinfo = place.AddressDetails;
1459             accuracy = addressinfo.Accuracy;
1460             if (accuracy === 0) {
1461                 Ext.MessageBox.alert('Unable to Locate Address', 'Unable to Locate the Address you provided');
1462             }else{
1463                 if (accuracy < 7) {
1464                     Ext.MessageBox.alert('Address Accuracy', 'The address provided has a low accuracy.<br><br>Level '+accuracy+' Accuracy (8 = Exact Match, 1 = Vague Match)');
1465                 }else{
1466                     point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
1467                     if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){
1468                         this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear,true, this.setCenter.listeners);
1469                     }
1470                 }
1471             }
1472         }
1473         
1474     }
1475  
1476 });
1477
1478 Ext.reg('gmappanel', Ext.ux.GMapPanel); Ext.namespace('Ext.ux.grid');
1479
1480 /**
1481  * @class Ext.ux.grid.GridFilters
1482  * @extends Ext.util.Observable
1483  * <p>GridFilter is a plugin (<code>ptype='gridfilters'</code>) for grids that
1484  * allow for a slightly more robust representation of filtering than what is
1485  * provided by the default store.</p>
1486  * <p>Filtering is adjusted by the user using the grid's column header menu
1487  * (this menu can be disabled through configuration). Through this menu users
1488  * can configure, enable, and disable filters for each column.</p>
1489  * <p><b><u>Features:</u></b></p>
1490  * <div class="mdetail-params"><ul>
1491  * <li><b>Filtering implementations</b> :
1492  * <div class="sub-desc">
1493  * Default filtering for Strings, Numeric Ranges, Date Ranges, Lists (which can
1494  * be backed by a Ext.data.Store), and Boolean. Additional custom filter types
1495  * and menus are easily created by extending Ext.ux.grid.filter.Filter.
1496  * </div></li>
1497  * <li><b>Graphical indicators</b> :
1498  * <div class="sub-desc">
1499  * Columns that are filtered have {@link #filterCls a configurable css class}
1500  * applied to the column headers.
1501  * </div></li>
1502  * <li><b>Paging</b> :
1503  * <div class="sub-desc">
1504  * If specified as a plugin to the grid's configured PagingToolbar, the current page
1505  * will be reset to page 1 whenever you update the filters.
1506  * </div></li>
1507  * <li><b>Automatic Reconfiguration</b> :
1508  * <div class="sub-desc">
1509  * Filters automatically reconfigure when the grid 'reconfigure' event fires.
1510  * </div></li>
1511  * <li><b>Stateful</b> :
1512  * Filter information will be persisted across page loads by specifying a
1513  * <code>stateId</code> in the Grid configuration.
1514  * <div class="sub-desc">
1515  * The filter collection binds to the
1516  * <code>{@link Ext.grid.GridPanel#beforestaterestore beforestaterestore}</code>
1517  * and <code>{@link Ext.grid.GridPanel#beforestatesave beforestatesave}</code>
1518  * events in order to be stateful.
1519  * </div></li>
1520  * <li><b>Grid Changes</b> :
1521  * <div class="sub-desc"><ul>
1522  * <li>A <code>filters</code> <i>property</i> is added to the grid pointing to
1523  * this plugin.</li>
1524  * <li>A <code>filterupdate</code> <i>event</i> is added to the grid and is
1525  * fired upon onStateChange completion.</li>
1526  * </ul></div></li>
1527  * <li><b>Server side code examples</b> :
1528  * <div class="sub-desc"><ul>
1529  * <li><a href="http://www.vinylfox.com/extjs/grid-filter-php-backend-code.php">PHP</a> - (Thanks VinylFox)</li>
1530  * <li><a href="http://extjs.com/forum/showthread.php?p=77326#post77326">Ruby on Rails</a> - (Thanks Zyclops)</li>
1531  * <li><a href="http://extjs.com/forum/showthread.php?p=176596#post176596">Ruby on Rails</a> - (Thanks Rotomaul)</li>
1532  * <li><a href="http://www.debatablybeta.com/posts/using-extjss-grid-filtering-with-django/">Python</a> - (Thanks Matt)</li>
1533  * <li><a href="http://mcantrell.wordpress.com/2008/08/22/extjs-grids-and-grails/">Grails</a> - (Thanks Mike)</li>
1534  * </ul></div></li>
1535  * </ul></div>
1536  * <p><b><u>Example usage:</u></b></p>
1537  * <pre><code>
1538 var store = new Ext.data.GroupingStore({
1539     ...
1540 });
1541
1542 var filters = new Ext.ux.grid.GridFilters({
1543     autoReload: false, //don&#39;t reload automatically
1544     local: true, //only filter locally
1545     // filters may be configured through the plugin,
1546     // or in the column definition within the column model configuration
1547     filters: [{
1548         type: 'numeric',
1549         dataIndex: 'id'
1550     }, {
1551         type: 'string',
1552         dataIndex: 'name'
1553     }, {
1554         type: 'numeric',
1555         dataIndex: 'price'
1556     }, {
1557         type: 'date',
1558         dataIndex: 'dateAdded'
1559     }, {
1560         type: 'list',
1561         dataIndex: 'size',
1562         options: ['extra small', 'small', 'medium', 'large', 'extra large'],
1563         phpMode: true
1564     }, {
1565         type: 'boolean',
1566         dataIndex: 'visible'
1567     }]
1568 });
1569 var cm = new Ext.grid.ColumnModel([{
1570     ...
1571 }]);
1572
1573 var grid = new Ext.grid.GridPanel({
1574      ds: store,
1575      cm: cm,
1576      view: new Ext.grid.GroupingView(),
1577      plugins: [filters],
1578      height: 400,
1579      width: 700,
1580      bbar: new Ext.PagingToolbar({
1581          store: store,
1582          pageSize: 15,
1583          plugins: [filters] //reset page to page 1 if filters change
1584      })
1585  });
1586
1587 store.load({params: {start: 0, limit: 15}});
1588
1589 // a filters property is added to the grid
1590 grid.filters
1591  * </code></pre>
1592  */
1593 Ext.ux.grid.GridFilters = Ext.extend(Ext.util.Observable, {
1594     /**
1595      * @cfg {Boolean} autoReload
1596      * Defaults to true, reloading the datasource when a filter change happens.
1597      * Set this to false to prevent the datastore from being reloaded if there
1598      * are changes to the filters.  See <code>{@link updateBuffer}</code>.
1599      */
1600     autoReload : true,
1601     /**
1602      * @cfg {Boolean} encode
1603      * Specify true for {@link #buildQuery} to use Ext.util.JSON.encode to
1604      * encode the filter query parameter sent with a remote request.
1605      * Defaults to false.
1606      */
1607     /**
1608      * @cfg {Array} filters
1609      * An Array of filters config objects. Refer to each filter type class for
1610      * configuration details specific to each filter type. Filters for Strings,
1611      * Numeric Ranges, Date Ranges, Lists, and Boolean are the standard filters
1612      * available.
1613      */
1614     /**
1615      * @cfg {String} filterCls
1616      * The css class to be applied to column headers with active filters.
1617      * Defaults to <tt>'ux-filterd-column'</tt>.
1618      */
1619     filterCls : 'ux-filtered-column',
1620     /**
1621      * @cfg {Boolean} local
1622      * <tt>true</tt> to use Ext.data.Store filter functions (local filtering)
1623      * instead of the default (<tt>false</tt>) server side filtering.
1624      */
1625     local : false,
1626     /**
1627      * @cfg {String} menuFilterText
1628      * defaults to <tt>'Filters'</tt>.
1629      */
1630     menuFilterText : 'Filters',
1631     /**
1632      * @cfg {String} paramPrefix
1633      * The url parameter prefix for the filters.
1634      * Defaults to <tt>'filter'</tt>.
1635      */
1636     paramPrefix : 'filter',
1637     /**
1638      * @cfg {Boolean} showMenu
1639      * Defaults to true, including a filter submenu in the default header menu.
1640      */
1641     showMenu : true,
1642     /**
1643      * @cfg {String} stateId
1644      * Name of the value to be used to store state information.
1645      */
1646     stateId : undefined,
1647     /**
1648      * @cfg {Integer} updateBuffer
1649      * Number of milliseconds to defer store updates since the last filter change.
1650      */
1651     updateBuffer : 500,
1652
1653     /** @private */
1654     constructor : function (config) {
1655         config = config || {};
1656         this.deferredUpdate = new Ext.util.DelayedTask(this.reload, this);
1657         this.filters = new Ext.util.MixedCollection();
1658         this.filters.getKey = function (o) {
1659             return o ? o.dataIndex : null;
1660         };
1661         this.addFilters(config.filters);
1662         delete config.filters;
1663         Ext.apply(this, config);
1664     },
1665
1666     /** @private */
1667     init : function (grid) {
1668         if (grid instanceof Ext.grid.GridPanel) {
1669             this.grid = grid;
1670
1671             this.bindStore(this.grid.getStore(), true);
1672             // assumes no filters were passed in the constructor, so try and use ones from the colModel
1673             if(this.filters.getCount() == 0){
1674                 this.addFilters(this.grid.getColumnModel());
1675             }
1676
1677             this.grid.filters = this;
1678
1679             this.grid.addEvents({'filterupdate': true});
1680
1681             grid.on({
1682                 scope: this,
1683                 beforestaterestore: this.applyState,
1684                 beforestatesave: this.saveState,
1685                 beforedestroy: this.destroy,
1686                 reconfigure: this.onReconfigure
1687             });
1688
1689             if (grid.rendered){
1690                 this.onRender();
1691             } else {
1692                 grid.on({
1693                     scope: this,
1694                     single: true,
1695                     render: this.onRender
1696                 });
1697             }
1698
1699         } else if (grid instanceof Ext.PagingToolbar) {
1700             this.toolbar = grid;
1701         }
1702     },
1703
1704     /**
1705      * @private
1706      * Handler for the grid's beforestaterestore event (fires before the state of the
1707      * grid is restored).
1708      * @param {Object} grid The grid object
1709      * @param {Object} state The hash of state values returned from the StateProvider.
1710      */
1711     applyState : function (grid, state) {
1712         var key, filter;
1713         this.applyingState = true;
1714         this.clearFilters();
1715         if (state.filters) {
1716             for (key in state.filters) {
1717                 filter = this.filters.get(key);
1718                 if (filter) {
1719                     filter.setValue(state.filters[key]);
1720                     filter.setActive(true);
1721                 }
1722             }
1723         }
1724         this.deferredUpdate.cancel();
1725         if (this.local) {
1726             this.reload();
1727         }
1728         delete this.applyingState;
1729         delete state.filters;
1730     },
1731
1732     /**
1733      * Saves the state of all active filters
1734      * @param {Object} grid
1735      * @param {Object} state
1736      * @return {Boolean}
1737      */
1738     saveState : function (grid, state) {
1739         var filters = {};
1740         this.filters.each(function (filter) {
1741             if (filter.active) {
1742                 filters[filter.dataIndex] = filter.getValue();
1743             }
1744         });
1745         return (state.filters = filters);
1746     },
1747
1748     /**
1749      * @private
1750      * Handler called when the grid is rendered
1751      */
1752     onRender : function () {
1753         this.grid.getView().on('refresh', this.onRefresh, this);
1754         this.createMenu();
1755     },
1756
1757     /**
1758      * @private
1759      * Handler called by the grid 'beforedestroy' event
1760      */
1761     destroy : function () {
1762         this.removeAll();
1763         this.purgeListeners();
1764
1765         if(this.filterMenu){
1766             Ext.menu.MenuMgr.unregister(this.filterMenu);
1767             this.filterMenu.destroy();
1768              this.filterMenu = this.menu.menu = null;
1769         }
1770     },
1771
1772     /**
1773      * Remove all filters, permanently destroying them.
1774      */
1775     removeAll : function () {
1776         if(this.filters){
1777             Ext.destroy.apply(Ext, this.filters.items);
1778             // remove all items from the collection
1779             this.filters.clear();
1780         }
1781     },
1782
1783
1784     /**
1785      * Changes the data store bound to this view and refreshes it.
1786      * @param {Store} store The store to bind to this view
1787      */
1788     bindStore : function(store, initial){
1789         if(!initial && this.store){
1790             if (this.local) {
1791                 store.un('load', this.onLoad, this);
1792             } else {
1793                 store.un('beforeload', this.onBeforeLoad, this);
1794             }
1795         }
1796         if(store){
1797             if (this.local) {
1798                 store.on('load', this.onLoad, this);
1799             } else {
1800                 store.on('beforeload', this.onBeforeLoad, this);
1801             }
1802         }
1803         this.store = store;
1804     },
1805
1806     /**
1807      * @private
1808      * Handler called when the grid reconfigure event fires
1809      */
1810     onReconfigure : function () {
1811         this.bindStore(this.grid.getStore());
1812         this.store.clearFilter();
1813         this.removeAll();
1814         this.addFilters(this.grid.getColumnModel());
1815         this.updateColumnHeadings();
1816     },
1817
1818     createMenu : function () {
1819         var view = this.grid.getView(),
1820             hmenu = view.hmenu;
1821
1822         if (this.showMenu && hmenu) {
1823
1824             this.sep  = hmenu.addSeparator();
1825             this.filterMenu = new Ext.menu.Menu({
1826                 id: this.grid.id + '-filters-menu'
1827             });
1828             this.menu = hmenu.add({
1829                 checked: false,
1830                 itemId: 'filters',
1831                 text: this.menuFilterText,
1832                 menu: this.filterMenu
1833             });
1834
1835             this.menu.on({
1836                 scope: this,
1837                 checkchange: this.onCheckChange,
1838                 beforecheckchange: this.onBeforeCheck
1839             });
1840             hmenu.on('beforeshow', this.onMenu, this);
1841         }
1842         this.updateColumnHeadings();
1843     },
1844
1845     /**
1846      * @private
1847      * Get the filter menu from the filters MixedCollection based on the clicked header
1848      */
1849     getMenuFilter : function () {
1850         var view = this.grid.getView();
1851         if (!view || view.hdCtxIndex === undefined) {
1852             return null;
1853         }
1854         return this.filters.get(
1855             view.cm.config[view.hdCtxIndex].dataIndex
1856         );
1857     },
1858
1859     /**
1860      * @private
1861      * Handler called by the grid's hmenu beforeshow event
1862      */
1863     onMenu : function (filterMenu) {
1864         var filter = this.getMenuFilter();
1865
1866         if (filter) {
1867 /*
1868 TODO: lazy rendering
1869             if (!filter.menu) {
1870                 filter.menu = filter.createMenu();
1871             }
1872 */
1873             this.menu.menu = filter.menu;
1874             this.menu.setChecked(filter.active, false);
1875             // disable the menu if filter.disabled explicitly set to true
1876             this.menu.setDisabled(filter.disabled === true);
1877         }
1878
1879         this.menu.setVisible(filter !== undefined);
1880         this.sep.setVisible(filter !== undefined);
1881     },
1882
1883     /** @private */
1884     onCheckChange : function (item, value) {
1885         this.getMenuFilter().setActive(value);
1886     },
1887
1888     /** @private */
1889     onBeforeCheck : function (check, value) {
1890         return !value || this.getMenuFilter().isActivatable();
1891     },
1892
1893     /**
1894      * @private
1895      * Handler for all events on filters.
1896      * @param {String} event Event name
1897      * @param {Object} filter Standard signature of the event before the event is fired
1898      */
1899     onStateChange : function (event, filter) {
1900         if (event === 'serialize') {
1901             return;
1902         }
1903
1904         if (filter == this.getMenuFilter()) {
1905             this.menu.setChecked(filter.active, false);
1906         }
1907
1908         if ((this.autoReload || this.local) && !this.applyingState) {
1909             this.deferredUpdate.delay(this.updateBuffer);
1910         }
1911         this.updateColumnHeadings();
1912
1913         if (!this.applyingState) {
1914             this.grid.saveState();
1915         }
1916         this.grid.fireEvent('filterupdate', this, filter);
1917     },
1918
1919     /**
1920      * @private
1921      * Handler for store's beforeload event when configured for remote filtering
1922      * @param {Object} store
1923      * @param {Object} options
1924      */
1925     onBeforeLoad : function (store, options) {
1926         options.params = options.params || {};
1927         this.cleanParams(options.params);
1928         var params = this.buildQuery(this.getFilterData());
1929         Ext.apply(options.params, params);
1930     },
1931
1932     /**
1933      * @private
1934      * Handler for store's load event when configured for local filtering
1935      * @param {Object} store
1936      * @param {Object} options
1937      */
1938     onLoad : function (store, options) {
1939         store.filterBy(this.getRecordFilter());
1940     },
1941
1942     /**
1943      * @private
1944      * Handler called when the grid's view is refreshed
1945      */
1946     onRefresh : function () {
1947         this.updateColumnHeadings();
1948     },
1949
1950     /**
1951      * Update the styles for the header row based on the active filters
1952      */
1953     updateColumnHeadings : function () {
1954         var view = this.grid.getView(),
1955             i, len, filter;
1956         if (view.mainHd) {
1957             for (i = 0, len = view.cm.config.length; i < len; i++) {
1958                 filter = this.getFilter(view.cm.config[i].dataIndex);
1959                 Ext.fly(view.getHeaderCell(i))[filter && filter.active ? 'addClass' : 'removeClass'](this.filterCls);
1960             }
1961         }
1962     },
1963
1964     /** @private */
1965     reload : function () {
1966         if (this.local) {
1967             this.grid.store.clearFilter(true);
1968             this.grid.store.filterBy(this.getRecordFilter());
1969         } else {
1970             var start,
1971                 store = this.grid.store;
1972             this.deferredUpdate.cancel();
1973             if (this.toolbar) {
1974                 start = store.paramNames.start;
1975                 if (store.lastOptions && store.lastOptions.params && store.lastOptions.params[start]) {
1976                     store.lastOptions.params[start] = 0;
1977                 }
1978             }
1979             store.reload();
1980         }
1981     },
1982
1983     /**
1984      * Method factory that generates a record validator for the filters active at the time
1985      * of invokation.
1986      * @private
1987      */
1988     getRecordFilter : function () {
1989         var f = [], len, i;
1990         this.filters.each(function (filter) {
1991             if (filter.active) {
1992                 f.push(filter);
1993             }
1994         });
1995
1996         len = f.length;
1997         return function (record) {
1998             for (i = 0; i < len; i++) {
1999                 if (!f[i].validateRecord(record)) {
2000                     return false;
2001                 }
2002             }
2003             return true;
2004         };
2005     },
2006
2007     /**
2008      * Adds a filter to the collection and observes it for state change.
2009      * @param {Object/Ext.ux.grid.filter.Filter} config A filter configuration or a filter object.
2010      * @return {Ext.ux.grid.filter.Filter} The existing or newly created filter object.
2011      */
2012     addFilter : function (config) {
2013         var Cls = this.getFilterClass(config.type),
2014             filter = config.menu ? config : (new Cls(config));
2015         this.filters.add(filter);
2016
2017         Ext.util.Observable.capture(filter, this.onStateChange, this);
2018         return filter;
2019     },
2020
2021     /**
2022      * Adds filters to the collection.
2023      * @param {Array/Ext.grid.ColumnModel} filters Either an Array of
2024      * filter configuration objects or an Ext.grid.ColumnModel.  The columns
2025      * of a passed Ext.grid.ColumnModel will be examined for a <code>filter</code>
2026      * property and, if present, will be used as the filter configuration object.
2027      */
2028     addFilters : function (filters) {
2029         if (filters) {
2030             var i, len, filter, cm = false, dI;
2031             if (filters instanceof Ext.grid.ColumnModel) {
2032                 filters = filters.config;
2033                 cm = true;
2034             }
2035             for (i = 0, len = filters.length; i < len; i++) {
2036                 filter = false;
2037                 if (cm) {
2038                     dI = filters[i].dataIndex;
2039                     filter = filters[i].filter || filters[i].filterable;
2040                     if (filter){
2041                         filter = (filter === true) ? {} : filter;
2042                         Ext.apply(filter, {dataIndex:dI});
2043                         // filter type is specified in order of preference:
2044                         //     filter type specified in config
2045                         //     type specified in store's field's type config
2046                         filter.type = filter.type || this.store.fields.get(dI).type.type;
2047                     }
2048                 } else {
2049                     filter = filters[i];
2050                 }
2051                 // if filter config found add filter for the column
2052                 if (filter) {
2053                     this.addFilter(filter);
2054                 }
2055             }
2056         }
2057     },
2058
2059     /**
2060      * Returns a filter for the given dataIndex, if one exists.
2061      * @param {String} dataIndex The dataIndex of the desired filter object.
2062      * @return {Ext.ux.grid.filter.Filter}
2063      */
2064     getFilter : function (dataIndex) {
2065         return this.filters.get(dataIndex);
2066     },
2067
2068     /**
2069      * Turns all filters off. This does not clear the configuration information
2070      * (see {@link #removeAll}).
2071      */
2072     clearFilters : function () {
2073         this.filters.each(function (filter) {
2074             filter.setActive(false);
2075         });
2076     },
2077
2078     /**
2079      * Returns an Array of the currently active filters.
2080      * @return {Array} filters Array of the currently active filters.
2081      */
2082     getFilterData : function () {
2083         var filters = [], i, len;
2084
2085         this.filters.each(function (f) {
2086             if (f.active) {
2087                 var d = [].concat(f.serialize());
2088                 for (i = 0, len = d.length; i < len; i++) {
2089                     filters.push({
2090                         field: f.dataIndex,
2091                         data: d[i]
2092                     });
2093                 }
2094             }
2095         });
2096         return filters;
2097     },
2098
2099     /**
2100      * Function to take the active filters data and build it into a query.
2101      * The format of the query depends on the <code>{@link #encode}</code>
2102      * configuration:
2103      * <div class="mdetail-params"><ul>
2104      *
2105      * <li><b><tt>false</tt></b> : <i>Default</i>
2106      * <div class="sub-desc">
2107      * Flatten into query string of the form (assuming <code>{@link #paramPrefix}='filters'</code>:
2108      * <pre><code>
2109 filters[0][field]="someDataIndex"&
2110 filters[0][data][comparison]="someValue1"&
2111 filters[0][data][type]="someValue2"&
2112 filters[0][data][value]="someValue3"&
2113      * </code></pre>
2114      * </div></li>
2115      * <li><b><tt>true</tt></b> :
2116      * <div class="sub-desc">
2117      * JSON encode the filter data
2118      * <pre><code>
2119 filters[0][field]="someDataIndex"&
2120 filters[0][data][comparison]="someValue1"&
2121 filters[0][data][type]="someValue2"&
2122 filters[0][data][value]="someValue3"&
2123      * </code></pre>
2124      * </div></li>
2125      * </ul></div>
2126      * Override this method to customize the format of the filter query for remote requests.
2127      * @param {Array} filters A collection of objects representing active filters and their configuration.
2128      *    Each element will take the form of {field: dataIndex, data: filterConf}. dataIndex is not assured
2129      *    to be unique as any one filter may be a composite of more basic filters for the same dataIndex.
2130      * @return {Object} Query keys and values
2131      */
2132     buildQuery : function (filters) {
2133         var p = {}, i, f, root, dataPrefix, key, tmp,
2134             len = filters.length;
2135
2136         if (!this.encode){
2137             for (i = 0; i < len; i++) {
2138                 f = filters[i];
2139                 root = [this.paramPrefix, '[', i, ']'].join('');
2140                 p[root + '[field]'] = f.field;
2141
2142                 dataPrefix = root + '[data]';
2143                 for (key in f.data) {
2144                     p[[dataPrefix, '[', key, ']'].join('')] = f.data[key];
2145                 }
2146             }
2147         } else {
2148             tmp = [];
2149             for (i = 0; i < len; i++) {
2150                 f = filters[i];
2151                 tmp.push(Ext.apply(
2152                     {},
2153                     {field: f.field},
2154                     f.data
2155                 ));
2156             }
2157             // only build if there is active filter
2158             if (tmp.length > 0){
2159                 p[this.paramPrefix] = Ext.util.JSON.encode(tmp);
2160             }
2161         }
2162         return p;
2163     },
2164
2165     /**
2166      * Removes filter related query parameters from the provided object.
2167      * @param {Object} p Query parameters that may contain filter related fields.
2168      */
2169     cleanParams : function (p) {
2170         // if encoding just delete the property
2171         if (this.encode) {
2172             delete p[this.paramPrefix];
2173         // otherwise scrub the object of filter data
2174         } else {
2175             var regex, key;
2176             regex = new RegExp('^' + this.paramPrefix + '\[[0-9]+\]');
2177             for (key in p) {
2178                 if (regex.test(key)) {
2179                     delete p[key];
2180                 }
2181             }
2182         }
2183     },
2184
2185     /**
2186      * Function for locating filter classes, overwrite this with your favorite
2187      * loader to provide dynamic filter loading.
2188      * @param {String} type The type of filter to load ('Filter' is automatically
2189      * appended to the passed type; eg, 'string' becomes 'StringFilter').
2190      * @return {Class} The Ext.ux.grid.filter.Class
2191      */
2192     getFilterClass : function (type) {
2193         // map the supported Ext.data.Field type values into a supported filter
2194         switch(type) {
2195             case 'auto':
2196               type = 'string';
2197               break;
2198             case 'int':
2199             case 'float':
2200               type = 'numeric';
2201               break;
2202             case 'bool':
2203               type = 'boolean';
2204               break;
2205         }
2206         return Ext.ux.grid.filter[type.substr(0, 1).toUpperCase() + type.substr(1) + 'Filter'];
2207     }
2208 });
2209
2210 // register ptype
2211 Ext.preg('gridfilters', Ext.ux.grid.GridFilters);
2212 Ext.namespace('Ext.ux.grid.filter');
2213
2214 /** 
2215  * @class Ext.ux.grid.filter.Filter
2216  * @extends Ext.util.Observable
2217  * Abstract base class for filter implementations.
2218  */
2219 Ext.ux.grid.filter.Filter = Ext.extend(Ext.util.Observable, {
2220     /**
2221      * @cfg {Boolean} active
2222      * Indicates the initial status of the filter (defaults to false).
2223      */
2224     active : false,
2225     /**
2226      * True if this filter is active.  Use setActive() to alter after configuration.
2227      * @type Boolean
2228      * @property active
2229      */
2230     /**
2231      * @cfg {String} dataIndex 
2232      * The {@link Ext.data.Store} dataIndex of the field this filter represents.
2233      * The dataIndex does not actually have to exist in the store.
2234      */
2235     dataIndex : null,
2236     /**
2237      * The filter configuration menu that will be installed into the filter submenu of a column menu.
2238      * @type Ext.menu.Menu
2239      * @property
2240      */
2241     menu : null,
2242     /**
2243      * @cfg {Number} updateBuffer
2244      * Number of milliseconds to wait after user interaction to fire an update. Only supported 
2245      * by filters: 'list', 'numeric', and 'string'. Defaults to 500.
2246      */
2247     updateBuffer : 500,
2248
2249     constructor : function (config) {
2250         Ext.apply(this, config);
2251             
2252         this.addEvents(
2253             /**
2254              * @event activate
2255              * Fires when an inactive filter becomes active
2256              * @param {Ext.ux.grid.filter.Filter} this
2257              */
2258             'activate',
2259             /**
2260              * @event deactivate
2261              * Fires when an active filter becomes inactive
2262              * @param {Ext.ux.grid.filter.Filter} this
2263              */
2264             'deactivate',
2265             /**
2266              * @event serialize
2267              * Fires after the serialization process. Use this to attach additional parameters to serialization
2268              * data before it is encoded and sent to the server.
2269              * @param {Array/Object} data A map or collection of maps representing the current filter configuration.
2270              * @param {Ext.ux.grid.filter.Filter} filter The filter being serialized.
2271              */
2272             'serialize',
2273             /**
2274              * @event update
2275              * Fires when a filter configuration has changed
2276              * @param {Ext.ux.grid.filter.Filter} this The filter object.
2277              */
2278             'update'
2279         );
2280         Ext.ux.grid.filter.Filter.superclass.constructor.call(this);
2281
2282         this.menu = new Ext.menu.Menu();
2283         this.init(config);
2284         if(config && config.value){
2285             this.setValue(config.value);
2286             this.setActive(config.active !== false, true);
2287             delete config.value;
2288         }
2289     },
2290
2291     /**
2292      * Destroys this filter by purging any event listeners, and removing any menus.
2293      */
2294     destroy : function(){
2295         if (this.menu){
2296             this.menu.destroy();
2297         }
2298         this.purgeListeners();
2299     },
2300
2301     /**
2302      * Template method to be implemented by all subclasses that is to
2303      * initialize the filter and install required menu items.
2304      * Defaults to Ext.emptyFn.
2305      */
2306     init : Ext.emptyFn,
2307     
2308     /**
2309      * Template method to be implemented by all subclasses that is to
2310      * get and return the value of the filter.
2311      * Defaults to Ext.emptyFn.
2312      * @return {Object} The 'serialized' form of this filter
2313      * @methodOf Ext.ux.grid.filter.Filter
2314      */
2315     getValue : Ext.emptyFn,
2316     
2317     /**
2318      * Template method to be implemented by all subclasses that is to
2319      * set the value of the filter and fire the 'update' event.
2320      * Defaults to Ext.emptyFn.
2321      * @param {Object} data The value to set the filter
2322      * @methodOf Ext.ux.grid.filter.Filter
2323      */ 
2324     setValue : Ext.emptyFn,
2325     
2326     /**
2327      * Template method to be implemented by all subclasses that is to
2328      * return <tt>true</tt> if the filter has enough configuration information to be activated.
2329      * Defaults to <tt>return true</tt>.
2330      * @return {Boolean}
2331      */
2332     isActivatable : function(){
2333         return true;
2334     },
2335     
2336     /**
2337      * Template method to be implemented by all subclasses that is to
2338      * get and return serialized filter data for transmission to the server.
2339      * Defaults to Ext.emptyFn.
2340      */
2341     getSerialArgs : Ext.emptyFn,
2342
2343     /**
2344      * Template method to be implemented by all subclasses that is to
2345      * validates the provided Ext.data.Record against the filters configuration.
2346      * Defaults to <tt>return true</tt>.
2347      * @param {Ext.data.Record} record The record to validate
2348      * @return {Boolean} true if the record is valid within the bounds
2349      * of the filter, false otherwise.
2350      */
2351     validateRecord : function(){
2352         return true;
2353     },
2354
2355     /**
2356      * Returns the serialized filter data for transmission to the server
2357      * and fires the 'serialize' event.
2358      * @return {Object/Array} An object or collection of objects containing
2359      * key value pairs representing the current configuration of the filter.
2360      * @methodOf Ext.ux.grid.filter.Filter
2361      */
2362     serialize : function(){
2363         var args = this.getSerialArgs();
2364         this.fireEvent('serialize', args, this);
2365         return args;
2366     },
2367
2368     /** @private */
2369     fireUpdate : function(){
2370         if (this.active) {
2371             this.fireEvent('update', this);
2372         }
2373         this.setActive(this.isActivatable());
2374     },
2375     
2376     /**
2377      * Sets the status of the filter and fires the appropriate events.
2378      * @param {Boolean} active        The new filter state.
2379      * @param {Boolean} suppressEvent True to prevent events from being fired.
2380      * @methodOf Ext.ux.grid.filter.Filter
2381      */
2382     setActive : function(active, suppressEvent){
2383         if(this.active != active){
2384             this.active = active;
2385             if (suppressEvent !== true) {
2386                 this.fireEvent(active ? 'activate' : 'deactivate', this);
2387             }
2388         }
2389     }    
2390 });/** 
2391  * @class Ext.ux.grid.filter.BooleanFilter
2392  * @extends Ext.ux.grid.filter.Filter
2393  * Boolean filters use unique radio group IDs (so you can have more than one!)
2394  * <p><b><u>Example Usage:</u></b></p>
2395  * <pre><code>    
2396 var filters = new Ext.ux.grid.GridFilters({
2397     ...
2398     filters: [{
2399         // required configs
2400         type: 'boolean',
2401         dataIndex: 'visible'
2402
2403         // optional configs
2404         defaultValue: null, // leave unselected (false selected by default)
2405         yesText: 'Yes',     // default
2406         noText: 'No'        // default
2407     }]
2408 });
2409  * </code></pre>
2410  */
2411 Ext.ux.grid.filter.BooleanFilter = Ext.extend(Ext.ux.grid.filter.Filter, {
2412         /**
2413          * @cfg {Boolean} defaultValue
2414          * Set this to null if you do not want either option to be checked by default. Defaults to false.
2415          */
2416         defaultValue : false,
2417         /**
2418          * @cfg {String} yesText
2419          * Defaults to 'Yes'.
2420          */
2421         yesText : 'Yes',
2422         /**
2423          * @cfg {String} noText
2424          * Defaults to 'No'.
2425          */
2426         noText : 'No',
2427
2428     /**  
2429      * @private
2430      * Template method that is to initialize the filter and install required menu items.
2431      */
2432     init : function (config) {
2433         var gId = Ext.id();
2434                 this.options = [
2435                         new Ext.menu.CheckItem({text: this.yesText, group: gId, checked: this.defaultValue === true}),
2436                         new Ext.menu.CheckItem({text: this.noText, group: gId, checked: this.defaultValue === false})];
2437                 
2438                 this.menu.add(this.options[0], this.options[1]);
2439                 
2440                 for(var i=0; i<this.options.length; i++){
2441                         this.options[i].on('click', this.fireUpdate, this);
2442                         this.options[i].on('checkchange', this.fireUpdate, this);
2443                 }
2444         },
2445         
2446     /**
2447      * @private
2448      * Template method that is to get and return the value of the filter.
2449      * @return {String} The value of this filter
2450      */
2451     getValue : function () {
2452                 return this.options[0].checked;
2453         },
2454
2455     /**
2456      * @private
2457      * Template method that is to set the value of the filter.
2458      * @param {Object} value The value to set the filter
2459      */ 
2460         setValue : function (value) {
2461                 this.options[value ? 0 : 1].setChecked(true);
2462         },
2463
2464     /**
2465      * @private
2466      * Template method that is to get and return serialized filter data for
2467      * transmission to the server.
2468      * @return {Object/Array} An object or collection of objects containing
2469      * key value pairs representing the current configuration of the filter.
2470      */
2471     getSerialArgs : function () {
2472                 var args = {type: 'boolean', value: this.getValue()};
2473                 return args;
2474         },
2475         
2476     /**
2477      * Template method that is to validate the provided Ext.data.Record
2478      * against the filters configuration.
2479      * @param {Ext.data.Record} record The record to validate
2480      * @return {Boolean} true if the record is valid within the bounds
2481      * of the filter, false otherwise.
2482      */
2483     validateRecord : function (record) {
2484                 return record.get(this.dataIndex) == this.getValue();
2485         }
2486 });/** 
2487  * @class Ext.ux.grid.filter.DateFilter
2488  * @extends Ext.ux.grid.filter.Filter
2489  * Filter by a configurable Ext.menu.DateMenu
2490  * <p><b><u>Example Usage:</u></b></p>
2491  * <pre><code>    
2492 var filters = new Ext.ux.grid.GridFilters({
2493     ...
2494     filters: [{
2495         // required configs
2496         type: 'date',
2497         dataIndex: 'dateAdded',
2498         
2499         // optional configs
2500         dateFormat: 'm/d/Y',  // default
2501         beforeText: 'Before', // default
2502         afterText: 'After',   // default
2503         onText: 'On',         // default
2504         pickerOpts: {
2505             // any DateMenu configs
2506         },
2507
2508         active: true // default is false
2509     }]
2510 });
2511  * </code></pre>
2512  */
2513 Ext.ux.grid.filter.DateFilter = Ext.extend(Ext.ux.grid.filter.Filter, {
2514     /**
2515      * @cfg {String} afterText
2516      * Defaults to 'After'.
2517      */
2518     afterText : 'After',
2519     /**
2520      * @cfg {String} beforeText
2521      * Defaults to 'Before'.
2522      */
2523     beforeText : 'Before',
2524     /**
2525      * @cfg {Object} compareMap
2526      * Map for assigning the comparison values used in serialization.
2527      */
2528     compareMap : {
2529         before: 'lt',
2530         after:  'gt',
2531         on:     'eq'
2532     },
2533     /**
2534      * @cfg {String} dateFormat
2535      * The date format to return when using getValue.
2536      * Defaults to 'm/d/Y'.
2537      */
2538     dateFormat : 'm/d/Y',
2539
2540     /**
2541      * @cfg {Date} maxDate
2542      * Allowable date as passed to the Ext.DatePicker
2543      * Defaults to undefined.
2544      */
2545     /**
2546      * @cfg {Date} minDate
2547      * Allowable date as passed to the Ext.DatePicker
2548      * Defaults to undefined.
2549      */
2550     /**
2551      * @cfg {Array} menuItems
2552      * The items to be shown in this menu
2553      * Defaults to:<pre>
2554      * menuItems : ['before', 'after', '-', 'on'],
2555      * </pre>
2556      */
2557     menuItems : ['before', 'after', '-', 'on'],
2558
2559     /**
2560      * @cfg {Object} menuItemCfgs
2561      * Default configuration options for each menu item
2562      */
2563     menuItemCfgs : {
2564         selectOnFocus: true,
2565         width: 125
2566     },
2567
2568     /**
2569      * @cfg {String} onText
2570      * Defaults to 'On'.
2571      */
2572     onText : 'On',
2573     
2574     /**
2575      * @cfg {Object} pickerOpts
2576      * Configuration options for the date picker associated with each field.
2577      */
2578     pickerOpts : {},
2579
2580     /**  
2581      * @private
2582      * Template method that is to initialize the filter and install required menu items.
2583      */
2584     init : function (config) {
2585         var menuCfg, i, len, item, cfg, Cls;
2586
2587         menuCfg = Ext.apply(this.pickerOpts, {
2588             minDate: this.minDate, 
2589             maxDate: this.maxDate, 
2590             format:  this.dateFormat,
2591             listeners: {
2592                 scope: this,
2593                 select: this.onMenuSelect
2594             }
2595         });
2596
2597         this.fields = {};
2598         for (i = 0, len = this.menuItems.length; i < len; i++) {
2599             item = this.menuItems[i];
2600             if (item !== '-') {
2601                 cfg = {
2602                     itemId: 'range-' + item,
2603                     text: this[item + 'Text'],
2604                     menu: new Ext.menu.DateMenu(
2605                         Ext.apply(menuCfg, {
2606                             itemId: item
2607                         })
2608                     ),
2609                     listeners: {
2610                         scope: this,
2611                         checkchange: this.onCheckChange
2612                     }
2613                 };
2614                 Cls = Ext.menu.CheckItem;
2615                 item = this.fields[item] = new Cls(cfg);
2616             }
2617             //this.add(item);
2618             this.menu.add(item);
2619         }
2620     },
2621
2622     onCheckChange : function () {
2623         this.setActive(this.isActivatable());
2624         this.fireEvent('update', this);
2625     },
2626
2627     /**  
2628      * @private
2629      * Handler method called when there is a keyup event on an input
2630      * item of this menu.
2631      */
2632     onInputKeyUp : function (field, e) {
2633         var k = e.getKey();
2634         if (k == e.RETURN && field.isValid()) {
2635             e.stopEvent();
2636             this.menu.hide(true);
2637             return;
2638         }
2639     },
2640
2641     /**
2642      * Handler for when the menu for a field fires the 'select' event
2643      * @param {Object} date
2644      * @param {Object} menuItem
2645      * @param {Object} value
2646      * @param {Object} picker
2647      */
2648     onMenuSelect : function (menuItem, value, picker) {
2649         var fields = this.fields,
2650             field = this.fields[menuItem.itemId];
2651         
2652         field.setChecked(true);
2653         
2654         if (field == fields.on) {
2655             fields.before.setChecked(false, true);
2656             fields.after.setChecked(false, true);
2657         } else {
2658             fields.on.setChecked(false, true);
2659             if (field == fields.after && fields.before.menu.picker.value < value) {
2660                 fields.before.setChecked(false, true);
2661             } else if (field == fields.before && fields.after.menu.picker.value > value) {
2662                 fields.after.setChecked(false, true);
2663             }
2664         }
2665         this.fireEvent('update', this);
2666     },
2667
2668     /**
2669      * @private
2670      * Template method that is to get and return the value of the filter.
2671      * @return {String} The value of this filter
2672      */
2673     getValue : function () {
2674         var key, result = {};
2675         for (key in this.fields) {
2676             if (this.fields[key].checked) {
2677                 result[key] = this.fields[key].menu.picker.getValue();
2678             }
2679         }
2680         return result;
2681     },
2682
2683     /**
2684      * @private
2685      * Template method that is to set the value of the filter.
2686      * @param {Object} value The value to set the filter
2687      * @param {Boolean} preserve true to preserve the checked status
2688      * of the other fields.  Defaults to false, unchecking the
2689      * other fields
2690      */ 
2691     setValue : function (value, preserve) {
2692         var key;
2693         for (key in this.fields) {
2694             if(value[key]){
2695                 this.fields[key].menu.picker.setValue(value[key]);
2696                 this.fields[key].setChecked(true);
2697             } else if (!preserve) {
2698                 this.fields[key].setChecked(false);
2699             }
2700         }
2701         this.fireEvent('update', this);
2702     },
2703
2704     /**
2705      * @private
2706      * Template method that is to return <tt>true</tt> if the filter
2707      * has enough configuration information to be activated.
2708      * @return {Boolean}
2709      */
2710     isActivatable : function () {
2711         var key;
2712         for (key in this.fields) {
2713             if (this.fields[key].checked) {
2714                 return true;
2715             }
2716         }
2717         return false;
2718     },
2719
2720     /**
2721      * @private
2722      * Template method that is to get and return serialized filter data for
2723      * transmission to the server.
2724      * @return {Object/Array} An object or collection of objects containing
2725      * key value pairs representing the current configuration of the filter.
2726      */
2727     getSerialArgs : function () {
2728         var args = [];
2729         for (var key in this.fields) {
2730             if(this.fields[key].checked){
2731                 args.push({
2732                     type: 'date',
2733                     comparison: this.compareMap[key],
2734                     value: this.getFieldValue(key).format(this.dateFormat)
2735                 });
2736             }
2737         }
2738         return args;
2739     },
2740
2741     /**
2742      * Get and return the date menu picker value
2743      * @param {String} item The field identifier ('before', 'after', 'on')
2744      * @return {Date} Gets the current selected value of the date field
2745      */
2746     getFieldValue : function(item){
2747         return this.fields[item].menu.picker.getValue();
2748     },
2749     
2750     /**
2751      * Gets the menu picker associated with the passed field
2752      * @param {String} item The field identifier ('before', 'after', 'on')
2753      * @return {Object} The menu picker
2754      */
2755     getPicker : function(item){
2756         return this.fields[item].menu.picker;
2757     },
2758
2759     /**
2760      * Template method that is to validate the provided Ext.data.Record
2761      * against the filters configuration.
2762      * @param {Ext.data.Record} record The record to validate
2763      * @return {Boolean} true if the record is valid within the bounds
2764      * of the filter, false otherwise.
2765      */
2766     validateRecord : function (record) {
2767         var key,
2768             pickerValue,
2769             val = record.get(this.dataIndex);
2770             
2771         if(!Ext.isDate(val)){
2772             return false;
2773         }
2774         val = val.clearTime(true).getTime();
2775         
2776         for (key in this.fields) {
2777             if (this.fields[key].checked) {
2778                 pickerValue = this.getFieldValue(key).clearTime(true).getTime();
2779                 if (key == 'before' && pickerValue <= val) {
2780                     return false;
2781                 }
2782                 if (key == 'after' && pickerValue >= val) {
2783                     return false;
2784                 }
2785                 if (key == 'on' && pickerValue != val) {
2786                     return false;
2787                 }
2788             }
2789         }
2790         return true;
2791     }
2792 });/** 
2793  * @class Ext.ux.grid.filter.ListFilter
2794  * @extends Ext.ux.grid.filter.Filter
2795  * <p>List filters are able to be preloaded/backed by an Ext.data.Store to load
2796  * their options the first time they are shown. ListFilter utilizes the
2797  * {@link Ext.ux.menu.ListMenu} component.</p>
2798  * <p>Although not shown here, this class accepts all configuration options
2799  * for {@link Ext.ux.menu.ListMenu}.</p>
2800  * 
2801  * <p><b><u>Example Usage:</u></b></p>
2802  * <pre><code>    
2803 var filters = new Ext.ux.grid.GridFilters({
2804     ...
2805     filters: [{
2806         type: 'list',
2807         dataIndex: 'size',
2808         phpMode: true,
2809         // options will be used as data to implicitly creates an ArrayStore
2810         options: ['extra small', 'small', 'medium', 'large', 'extra large']
2811     }]
2812 });
2813  * </code></pre>
2814  * 
2815  */
2816 Ext.ux.grid.filter.ListFilter = Ext.extend(Ext.ux.grid.filter.Filter, {
2817
2818     /**
2819      * @cfg {Array} options
2820      * <p><code>data</code> to be used to implicitly create a data store
2821      * to back this list when the data source is <b>local</b>. If the
2822      * data for the list is remote, use the <code>{@link #store}</code>
2823      * config instead.</p>
2824      * <br><p>Each item within the provided array may be in one of the
2825      * following formats:</p>
2826      * <div class="mdetail-params"><ul>
2827      * <li><b>Array</b> :
2828      * <pre><code>
2829 options: [
2830     [11, 'extra small'], 
2831     [18, 'small'],
2832     [22, 'medium'],
2833     [35, 'large'],
2834     [44, 'extra large']
2835 ]
2836      * </code></pre>
2837      * </li>
2838      * <li><b>Object</b> :
2839      * <pre><code>
2840 labelField: 'name', // override default of 'text'
2841 options: [
2842     {id: 11, name:'extra small'}, 
2843     {id: 18, name:'small'}, 
2844     {id: 22, name:'medium'}, 
2845     {id: 35, name:'large'}, 
2846     {id: 44, name:'extra large'} 
2847 ]
2848      * </code></pre>
2849      * </li>
2850      * <li><b>String</b> :
2851      * <pre><code>
2852      * options: ['extra small', 'small', 'medium', 'large', 'extra large']
2853      * </code></pre>
2854      * </li>
2855      */
2856     /**
2857      * @cfg {Boolean} phpMode
2858      * <p>Adjust the format of this filter. Defaults to false.</p>
2859      * <br><p>When GridFilters <code>@cfg encode = false</code> (default):</p>
2860      * <pre><code>
2861 // phpMode == false (default):
2862 filter[0][data][type] list
2863 filter[0][data][value] value1
2864 filter[0][data][value] value2
2865 filter[0][field] prod 
2866
2867 // phpMode == true:
2868 filter[0][data][type] list
2869 filter[0][data][value] value1, value2
2870 filter[0][field] prod 
2871      * </code></pre>
2872      * When GridFilters <code>@cfg encode = true</code>:
2873      * <pre><code>
2874 // phpMode == false (default):
2875 filter : [{"type":"list","value":["small","medium"],"field":"size"}]
2876
2877 // phpMode == true:
2878 filter : [{"type":"list","value":"small,medium","field":"size"}]
2879      * </code></pre>
2880      */
2881     phpMode : false,
2882     /**
2883      * @cfg {Ext.data.Store} store
2884      * The {@link Ext.data.Store} this list should use as its data source
2885      * when the data source is <b>remote</b>. If the data for the list
2886      * is local, use the <code>{@link #options}</code> config instead.
2887      */
2888
2889     /**  
2890      * @private
2891      * Template method that is to initialize the filter and install required menu items.
2892      * @param {Object} config
2893      */
2894     init : function (config) {
2895         this.dt = new Ext.util.DelayedTask(this.fireUpdate, this);
2896
2897         // if a menu already existed, do clean up first
2898         if (this.menu){
2899             this.menu.destroy();
2900         }
2901         this.menu = new Ext.ux.menu.ListMenu(config);
2902         this.menu.on('checkchange', this.onCheckChange, this);
2903     },
2904     
2905     /**
2906      * @private
2907      * Template method that is to get and return the value of the filter.
2908      * @return {String} The value of this filter
2909      */
2910     getValue : function () {
2911         return this.menu.getSelected();
2912     },
2913     /**
2914      * @private
2915      * Template method that is to set the value of the filter.
2916      * @param {Object} value The value to set the filter
2917      */ 
2918     setValue : function (value) {
2919         this.menu.setSelected(value);
2920         this.fireEvent('update', this);
2921     },
2922
2923     /**
2924      * @private
2925      * Template method that is to return <tt>true</tt> if the filter
2926      * has enough configuration information to be activated.
2927      * @return {Boolean}
2928      */
2929     isActivatable : function () {
2930         return this.getValue().length > 0;
2931     },
2932     
2933     /**
2934      * @private
2935      * Template method that is to get and return serialized filter data for
2936      * transmission to the server.
2937      * @return {Object/Array} An object or collection of objects containing
2938      * key value pairs representing the current configuration of the filter.
2939      */
2940     getSerialArgs : function () {
2941         var args = {type: 'list', value: this.phpMode ? this.getValue().join(',') : this.getValue()};
2942         return args;
2943     },
2944
2945     /** @private */
2946     onCheckChange : function(){
2947         this.dt.delay(this.updateBuffer);
2948     },
2949     
2950     
2951     /**
2952      * Template method that is to validate the provided Ext.data.Record
2953      * against the filters configuration.
2954      * @param {Ext.data.Record} record The record to validate
2955      * @return {Boolean} true if the record is valid within the bounds
2956      * of the filter, false otherwise.
2957      */
2958     validateRecord : function (record) {
2959         return this.getValue().indexOf(record.get(this.dataIndex)) > -1;
2960     }
2961 });/** 
2962  * @class Ext.ux.grid.filter.NumericFilter
2963  * @extends Ext.ux.grid.filter.Filter
2964  * Filters using an Ext.ux.menu.RangeMenu.
2965  * <p><b><u>Example Usage:</u></b></p>
2966  * <pre><code>    
2967 var filters = new Ext.ux.grid.GridFilters({
2968     ...
2969     filters: [{
2970         type: 'numeric',
2971         dataIndex: 'price'
2972     }]
2973 });
2974  * </code></pre> 
2975  */
2976 Ext.ux.grid.filter.NumericFilter = Ext.extend(Ext.ux.grid.filter.Filter, {
2977
2978     /**
2979      * @cfg {Object} fieldCls
2980      * The Class to use to construct each field item within this menu
2981      * Defaults to:<pre>
2982      * fieldCls : Ext.form.NumberField
2983      * </pre>
2984      */
2985     fieldCls : Ext.form.NumberField,
2986     /**
2987      * @cfg {Object} fieldCfg
2988      * The default configuration options for any field item unless superseded
2989      * by the <code>{@link #fields}</code> configuration.
2990      * Defaults to:<pre>
2991      * fieldCfg : {}
2992      * </pre>
2993      * Example usage:
2994      * <pre><code>
2995 fieldCfg : {
2996     width: 150,
2997 },
2998      * </code></pre>
2999      */
3000     /**
3001      * @cfg {Object} fields
3002      * The field items may be configured individually
3003      * Defaults to <tt>undefined</tt>.
3004      * Example usage:
3005      * <pre><code>
3006 fields : {
3007     gt: { // override fieldCfg options
3008         width: 200,
3009         fieldCls: Ext.ux.form.CustomNumberField // to override default {@link #fieldCls}
3010     }
3011 },
3012      * </code></pre>
3013      */
3014     /**
3015      * @cfg {Object} iconCls
3016      * The iconCls to be applied to each comparator field item.
3017      * Defaults to:<pre>
3018 iconCls : {
3019     gt : 'ux-rangemenu-gt',
3020     lt : 'ux-rangemenu-lt',
3021     eq : 'ux-rangemenu-eq'
3022 }
3023      * </pre>
3024      */
3025     iconCls : {
3026         gt : 'ux-rangemenu-gt',
3027         lt : 'ux-rangemenu-lt',
3028         eq : 'ux-rangemenu-eq'
3029     },
3030
3031     /**
3032      * @cfg {Object} menuItemCfgs
3033      * Default configuration options for each menu item
3034      * Defaults to:<pre>
3035 menuItemCfgs : {
3036     emptyText: 'Enter Filter Text...',
3037     selectOnFocus: true,
3038     width: 125
3039 }
3040      * </pre>
3041      */
3042     menuItemCfgs : {
3043         emptyText: 'Enter Filter Text...',
3044         selectOnFocus: true,
3045         width: 125
3046     },
3047
3048     /**
3049      * @cfg {Array} menuItems
3050      * The items to be shown in this menu.  Items are added to the menu
3051      * according to their position within this array. Defaults to:<pre>
3052      * menuItems : ['lt','gt','-','eq']
3053      * </pre>
3054      */
3055     menuItems : ['lt', 'gt', '-', 'eq'],
3056
3057     /**  
3058      * @private
3059      * Template method that is to initialize the filter and install required menu items.
3060      */
3061     init : function (config) {
3062         // if a menu already existed, do clean up first
3063         if (this.menu){
3064             this.menu.destroy();
3065         }        
3066         this.menu = new Ext.ux.menu.RangeMenu(Ext.apply(config, {
3067             // pass along filter configs to the menu
3068             fieldCfg : this.fieldCfg || {},
3069             fieldCls : this.fieldCls,
3070             fields : this.fields || {},
3071             iconCls: this.iconCls,
3072             menuItemCfgs: this.menuItemCfgs,
3073             menuItems: this.menuItems,
3074             updateBuffer: this.updateBuffer
3075         }));
3076         // relay the event fired by the menu
3077         this.menu.on('update', this.fireUpdate, this);
3078     },
3079     
3080     /**
3081      * @private
3082      * Template method that is to get and return the value of the filter.
3083      * @return {String} The value of this filter
3084      */
3085     getValue : function () {
3086         return this.menu.getValue();
3087     },
3088
3089     /**
3090      * @private
3091      * Template method that is to set the value of the filter.
3092      * @param {Object} value The value to set the filter
3093      */ 
3094     setValue : function (value) {
3095         this.menu.setValue(value);
3096     },
3097
3098     /**
3099      * @private
3100      * Template method that is to return <tt>true</tt> if the filter
3101      * has enough configuration information to be activated.
3102      * @return {Boolean}
3103      */
3104     isActivatable : function () {
3105         var values = this.getValue();
3106         for (key in values) {
3107             if (values[key] !== undefined) {
3108                 return true;
3109             }
3110         }
3111         return false;
3112     },
3113     
3114     /**
3115      * @private
3116      * Template method that is to get and return serialized filter data for
3117      * transmission to the server.
3118      * @return {Object/Array} An object or collection of objects containing
3119      * key value pairs representing the current configuration of the filter.
3120      */
3121     getSerialArgs : function () {
3122         var key,
3123             args = [],
3124             values = this.menu.getValue();
3125         for (key in values) {
3126             args.push({
3127                 type: 'numeric',
3128                 comparison: key,
3129                 value: values[key]
3130             });
3131         }
3132         return args;
3133     },
3134
3135     /**
3136      * Template method that is to validate the provided Ext.data.Record
3137      * against the filters configuration.
3138      * @param {Ext.data.Record} record The record to validate
3139      * @return {Boolean} true if the record is valid within the bounds
3140      * of the filter, false otherwise.
3141      */
3142     validateRecord : function (record) {
3143         var val = record.get(this.dataIndex),
3144             values = this.getValue();
3145         if (values.eq !== undefined && val != values.eq) {
3146             return false;
3147         }
3148         if (values.lt !== undefined && val >= values.lt) {
3149             return false;
3150         }
3151         if (values.gt !== undefined && val <= values.gt) {
3152             return false;
3153         }
3154         return true;
3155     }
3156 });/** 
3157  * @class Ext.ux.grid.filter.StringFilter
3158  * @extends Ext.ux.grid.filter.Filter
3159  * Filter by a configurable Ext.form.TextField
3160  * <p><b><u>Example Usage:</u></b></p>
3161  * <pre><code>    
3162 var filters = new Ext.ux.grid.GridFilters({
3163     ...
3164     filters: [{
3165         // required configs
3166         type: 'string',
3167         dataIndex: 'name',
3168         
3169         // optional configs
3170         value: 'foo',
3171         active: true, // default is false
3172         iconCls: 'ux-gridfilter-text-icon' // default
3173         // any Ext.form.TextField configs accepted
3174     }]
3175 });
3176  * </code></pre>
3177  */
3178 Ext.ux.grid.filter.StringFilter = Ext.extend(Ext.ux.grid.filter.Filter, {
3179
3180     /**
3181      * @cfg {String} iconCls
3182      * The iconCls to be applied to the menu item.
3183      * Defaults to <tt>'ux-gridfilter-text-icon'</tt>.
3184      */
3185     iconCls : 'ux-gridfilter-text-icon',
3186
3187     emptyText: 'Enter Filter Text...',
3188     selectOnFocus: true,
3189     width: 125,
3190     
3191     /**  
3192      * @private
3193      * Template method that is to initialize the filter and install required menu items.
3194      */
3195     init : function (config) {
3196         Ext.applyIf(config, {
3197             enableKeyEvents: true,
3198             iconCls: this.iconCls,
3199             listeners: {
3200                 scope: this,
3201                 keyup: this.onInputKeyUp
3202             }
3203         });
3204
3205         this.inputItem = new Ext.form.TextField(config); 
3206         this.menu.add(this.inputItem);
3207         this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this);
3208     },
3209     
3210     /**
3211      * @private
3212      * Template method that is to get and return the value of the filter.
3213      * @return {String} The value of this filter
3214      */
3215     getValue : function () {
3216         return this.inputItem.getValue();
3217     },
3218     
3219     /**
3220      * @private
3221      * Template method that is to set the value of the filter.
3222      * @param {Object} value The value to set the filter
3223      */ 
3224     setValue : function (value) {
3225         this.inputItem.setValue(value);
3226         this.fireEvent('update', this);
3227     },
3228
3229     /**
3230      * @private
3231      * Template method that is to return <tt>true</tt> if the filter
3232      * has enough configuration information to be activated.
3233      * @return {Boolean}
3234      */
3235     isActivatable : function () {
3236         return this.inputItem.getValue().length > 0;
3237     },
3238
3239     /**
3240      * @private
3241      * Template method that is to get and return serialized filter data for
3242      * transmission to the server.
3243      * @return {Object/Array} An object or collection of objects containing
3244      * key value pairs representing the current configuration of the filter.
3245      */
3246     getSerialArgs : function () {
3247         return {type: 'string', value: this.getValue()};
3248     },
3249
3250     /**
3251      * Template method that is to validate the provided Ext.data.Record
3252      * against the filters configuration.
3253      * @param {Ext.data.Record} record The record to validate
3254      * @return {Boolean} true if the record is valid within the bounds
3255      * of the filter, false otherwise.
3256      */
3257     validateRecord : function (record) {
3258         var val = record.get(this.dataIndex);
3259
3260         if(typeof val != 'string') {
3261             return (this.getValue().length === 0);
3262         }
3263
3264         return val.toLowerCase().indexOf(this.getValue().toLowerCase()) > -1;
3265     },
3266     
3267     /**  
3268      * @private
3269      * Handler method called when there is a keyup event on this.inputItem
3270      */
3271     onInputKeyUp : function (field, e) {
3272         var k = e.getKey();
3273         if (k == e.RETURN && field.isValid()) {
3274             e.stopEvent();
3275             this.menu.hide(true);
3276             return;
3277         }
3278         // restart the timer
3279         this.updateTask.delay(this.updateBuffer);
3280     }
3281 });
3282 Ext.namespace('Ext.ux.menu');
3283
3284 /** 
3285  * @class Ext.ux.menu.ListMenu
3286  * @extends Ext.menu.Menu
3287  * This is a supporting class for {@link Ext.ux.grid.filter.ListFilter}.
3288  * Although not listed as configuration options for this class, this class
3289  * also accepts all configuration options from {@link Ext.ux.grid.filter.ListFilter}.
3290  */
3291 Ext.ux.menu.ListMenu = Ext.extend(Ext.menu.Menu, {
3292     /**
3293      * @cfg {String} labelField
3294      * Defaults to 'text'.
3295      */
3296     labelField :  'text',
3297     /**
3298      * @cfg {String} paramPrefix
3299      * Defaults to 'Loading...'.
3300      */
3301     loadingText : 'Loading...',
3302     /**
3303      * @cfg {Boolean} loadOnShow
3304      * Defaults to true.
3305      */
3306     loadOnShow : true,
3307     /**
3308      * @cfg {Boolean} single
3309      * Specify true to group all items in this list into a single-select
3310      * radio button group. Defaults to false.
3311      */
3312     single : false,
3313
3314     constructor : function (cfg) {
3315         this.selected = [];
3316         this.addEvents(
3317             /**
3318              * @event checkchange
3319              * Fires when there is a change in checked items from this list
3320              * @param {Object} item Ext.menu.CheckItem
3321              * @param {Object} checked The checked value that was set
3322              */
3323             'checkchange'
3324         );
3325       
3326         Ext.ux.menu.ListMenu.superclass.constructor.call(this, cfg = cfg || {});
3327     
3328         if(!cfg.store && cfg.options){
3329             var options = [];
3330             for(var i=0, len=cfg.options.length; i<len; i++){
3331                 var value = cfg.options[i];
3332                 switch(Ext.type(value)){
3333                     case 'array':  options.push(value); break;
3334                     case 'object': options.push([value.id, value[this.labelField]]); break;
3335                     case 'string': options.push([value, value]); break;
3336                 }
3337             }
3338             
3339             this.store = new Ext.data.Store({
3340                 reader: new Ext.data.ArrayReader({id: 0}, ['id', this.labelField]),
3341                 data:   options,
3342                 listeners: {
3343                     'load': this.onLoad,
3344                     scope:  this
3345                 }
3346             });
3347             this.loaded = true;
3348         } else {
3349             this.add({text: this.loadingText, iconCls: 'loading-indicator'});
3350             this.store.on('load', this.onLoad, this);
3351         }
3352     },
3353
3354     destroy : function () {
3355         if (this.store) {
3356             this.store.destroy();    
3357         }
3358         Ext.ux.menu.ListMenu.superclass.destroy.call(this);
3359     },
3360
3361     /**
3362      * Lists will initially show a 'loading' item while the data is retrieved from the store.
3363      * In some cases the loaded data will result in a list that goes off the screen to the
3364      * right (as placement calculations were done with the loading item). This adapter will
3365      * allow show to be called with no arguments to show with the previous arguments and
3366      * thus recalculate the width and potentially hang the menu from the left.
3367      */
3368     show : function () {
3369         var lastArgs = null;
3370         return function(){
3371             if(arguments.length === 0){
3372                 Ext.ux.menu.ListMenu.superclass.show.apply(this, lastArgs);
3373             } else {
3374                 lastArgs = arguments;
3375                 if (this.loadOnShow && !this.loaded) {
3376                     this.store.load();
3377                 }
3378                 Ext.ux.menu.ListMenu.superclass.show.apply(this, arguments);
3379             }
3380         };
3381     }(),
3382     
3383     /** @private */
3384     onLoad : function (store, records) {
3385         var visible = this.isVisible();
3386         this.hide(false);
3387         
3388         this.removeAll(true);
3389         
3390         var gid = this.single ? Ext.id() : null;
3391         for(var i=0, len=records.length; i<len; i++){
3392             var item = new Ext.menu.CheckItem({
3393                 text:    records[i].get(this.labelField), 
3394                 group:   gid,
3395                 checked: this.selected.indexOf(records[i].id) > -1,
3396                 hideOnClick: false});
3397             
3398             item.itemId = records[i].id;
3399             item.on('checkchange', this.checkChange, this);
3400                         
3401             this.add(item);
3402         }
3403         
3404         this.loaded = true;
3405         
3406         if (visible) {
3407             this.show();
3408         }       
3409         this.fireEvent('load', this, records);
3410     },
3411
3412     /**
3413      * Get the selected items.
3414      * @return {Array} selected
3415      */
3416     getSelected : function () {
3417         return this.selected;
3418     },
3419     
3420     /** @private */
3421     setSelected : function (value) {
3422         value = this.selected = [].concat(value);
3423
3424         if (this.loaded) {
3425             this.items.each(function(item){
3426                 item.setChecked(false, true);
3427                 for (var i = 0, len = value.length; i < len; i++) {
3428                     if (item.itemId == value[i]) {
3429                         item.setChecked(true, true);
3430                     }
3431                 }
3432             }, this);
3433         }
3434     },
3435     
3436     /**
3437      * Handler for the 'checkchange' event from an check item in this menu
3438      * @param {Object} item Ext.menu.CheckItem
3439      * @param {Object} checked The checked value that was set
3440      */
3441     checkChange : function (item, checked) {
3442         var value = [];
3443         this.items.each(function(item){
3444             if (item.checked) {
3445                 value.push(item.itemId);
3446             }
3447         },this);
3448         this.selected = value;
3449         
3450         this.fireEvent('checkchange', item, checked);
3451     }    
3452 });Ext.ns('Ext.ux.menu');
3453
3454 /** 
3455  * @class Ext.ux.menu.RangeMenu
3456  * @extends Ext.menu.Menu
3457  * Custom implementation of Ext.menu.Menu that has preconfigured
3458  * items for gt, lt, eq.
3459  * <p><b><u>Example Usage:</u></b></p>
3460  * <pre><code>    
3461
3462  * </code></pre> 
3463  */
3464 Ext.ux.menu.RangeMenu = Ext.extend(Ext.menu.Menu, {
3465
3466     constructor : function (config) {
3467
3468         Ext.ux.menu.RangeMenu.superclass.constructor.call(this, config);
3469
3470         this.addEvents(
3471             /**
3472              * @event update
3473              * Fires when a filter configuration has changed
3474              * @param {Ext.ux.grid.filter.Filter} this The filter object.
3475              */
3476             'update'
3477         );
3478       
3479         this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this);
3480     
3481         var i, len, item, cfg, Cls;
3482
3483         for (i = 0, len = this.menuItems.length; i < len; i++) {
3484             item = this.menuItems[i];
3485             if (item !== '-') {
3486                 // defaults
3487                 cfg = {
3488                     itemId: 'range-' + item,
3489                     enableKeyEvents: true,
3490                     iconCls: this.iconCls[item] || 'no-icon',
3491                     listeners: {
3492                         scope: this,
3493                         keyup: this.onInputKeyUp
3494                     }
3495                 };
3496                 Ext.apply(
3497                     cfg,
3498                     // custom configs
3499                     Ext.applyIf(this.fields[item] || {}, this.fieldCfg[item]),
3500                     // configurable defaults
3501                     this.menuItemCfgs
3502                 );
3503                 Cls = cfg.fieldCls || this.fieldCls;
3504                 item = this.fields[item] = new Cls(cfg);
3505             }
3506             this.add(item);
3507         }
3508     },
3509
3510     /**
3511      * @private
3512      * called by this.updateTask
3513      */
3514     fireUpdate : function () {
3515         this.fireEvent('update', this);
3516     },
3517     
3518     /**
3519      * Get and return the value of the filter.
3520      * @return {String} The value of this filter
3521      */
3522     getValue : function () {
3523         var result = {}, key, field;
3524         for (key in this.fields) {
3525             field = this.fields[key];
3526             if (field.isValid() && String(field.getValue()).length > 0) {
3527                 result[key] = field.getValue();
3528             }
3529         }
3530         return result;
3531     },
3532   
3533     /**
3534      * Set the value of this menu and fires the 'update' event.
3535      * @param {Object} data The data to assign to this menu
3536      */ 
3537     setValue : function (data) {
3538         var key;
3539         for (key in this.fields) {
3540             this.fields[key].setValue(data[key] !== undefined ? data[key] : '');
3541         }
3542         this.fireEvent('update', this);
3543     },
3544
3545     /**  
3546      * @private
3547      * Handler method called when there is a keyup event on an input
3548      * item of this menu.
3549      */
3550     onInputKeyUp : function (field, e) {
3551         var k = e.getKey();
3552         if (k == e.RETURN && field.isValid()) {
3553             e.stopEvent();
3554             this.hide(true);
3555             return;
3556         }
3557         
3558         if (field == this.fields.eq) {
3559             if (this.fields.gt) {
3560                 this.fields.gt.setValue(null);
3561             }
3562             if (this.fields.lt) {
3563                 this.fields.lt.setValue(null);
3564             }
3565         }
3566         else {
3567             this.fields.eq.setValue(null);
3568         }
3569         
3570         // restart the timer
3571         this.updateTask.delay(this.updateBuffer);
3572     }
3573 });
3574 Ext.ns('Ext.ux.grid');
3575
3576 /**
3577  * @class Ext.ux.grid.GroupSummary
3578  * @extends Ext.util.Observable
3579  * A GridPanel plugin that enables dynamic column calculations and a dynamically
3580  * updated grouped summary row.
3581  */
3582 Ext.ux.grid.GroupSummary = Ext.extend(Ext.util.Observable, {
3583     /**
3584      * @cfg {Function} summaryRenderer Renderer example:<pre><code>
3585 summaryRenderer: function(v, params, data){
3586     return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');
3587 },
3588      * </code></pre>
3589      */
3590     /**
3591      * @cfg {String} summaryType (Optional) The type of
3592      * calculation to be used for the column.  For options available see
3593      * {@link #Calculations}.
3594      */
3595
3596     constructor : function(config){
3597         Ext.apply(this, config);
3598         Ext.ux.grid.GroupSummary.superclass.constructor.call(this);
3599     },
3600     init : function(grid){
3601         this.grid = grid;
3602         var v = this.view = grid.getView();
3603         v.doGroupEnd = this.doGroupEnd.createDelegate(this);
3604
3605         v.afterMethod('onColumnWidthUpdated', this.doWidth, this);
3606         v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);
3607         v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);
3608         v.afterMethod('onUpdate', this.doUpdate, this);
3609         v.afterMethod('onRemove', this.doRemove, this);
3610
3611         if(!this.rowTpl){
3612             this.rowTpl = new Ext.Template(
3613                 '<div class="x-grid3-summary-row" style="{tstyle}">',
3614                 '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
3615                     '<tbody><tr>{cells}</tr></tbody>',
3616                 '</table></div>'
3617             );
3618             this.rowTpl.disableFormats = true;
3619         }
3620         this.rowTpl.compile();
3621
3622         if(!this.cellTpl){
3623             this.cellTpl = new Ext.Template(
3624                 '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',
3625                 '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on">{value}</div>',
3626                 "</td>"
3627             );
3628             this.cellTpl.disableFormats = true;
3629         }
3630         this.cellTpl.compile();
3631     },
3632
3633     /**
3634      * Toggle the display of the summary row on/off
3635      * @param {Boolean} visible <tt>true</tt> to show the summary, <tt>false</tt> to hide the summary.
3636      */
3637     toggleSummaries : function(visible){
3638         var el = this.grid.getGridEl();
3639         if(el){
3640             if(visible === undefined){
3641                 visible = el.hasClass('x-grid-hide-summary');
3642             }
3643             el[visible ? 'removeClass' : 'addClass']('x-grid-hide-summary');
3644         }
3645     },
3646
3647     renderSummary : function(o, cs){
3648         cs = cs || this.view.getColumnData();
3649         var cfg = this.grid.getColumnModel().config,
3650             buf = [], c, p = {}, cf, last = cs.length-1;
3651         for(var i = 0, len = cs.length; i < len; i++){
3652             c = cs[i];
3653             cf = cfg[i];
3654             p.id = c.id;
3655             p.style = c.style;
3656             p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
3657             if(cf.summaryType || cf.summaryRenderer){
3658                 p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);
3659             }else{
3660                 p.value = '';
3661             }
3662             if(p.value == undefined || p.value === "") p.value = "&#160;";
3663             buf[buf.length] = this.cellTpl.apply(p);
3664         }
3665
3666         return this.rowTpl.apply({
3667             tstyle: 'width:'+this.view.getTotalWidth()+';',
3668             cells: buf.join('')
3669         });
3670     },
3671
3672     /**
3673      * @private
3674      * @param {Object} rs
3675      * @param {Object} cs
3676      */
3677     calculate : function(rs, cs){
3678         var data = {}, r, c, cfg = this.grid.getColumnModel().config, cf;
3679         for(var j = 0, jlen = rs.length; j < jlen; j++){
3680             r = rs[j];
3681             for(var i = 0, len = cs.length; i < len; i++){
3682                 c = cs[i];
3683                 cf = cfg[i];
3684                 if(cf.summaryType){
3685                     data[c.name] = Ext.ux.grid.GroupSummary.Calculations[cf.summaryType](data[c.name] || 0, r, c.name, data);
3686                 }
3687             }
3688         }
3689         return data;
3690     },
3691
3692     doGroupEnd : function(buf, g, cs, ds, colCount){
3693         var data = this.calculate(g.rs, cs);
3694         buf.push('</div>', this.renderSummary({data: data}, cs), '</div>');
3695     },
3696
3697     doWidth : function(col, w, tw){
3698         if(!this.isGrouped()){
3699             return;
3700         }
3701         var gs = this.view.getGroups(),
3702             len = gs.length,
3703             i = 0,
3704             s;
3705         for(; i < len; ++i){
3706             s = gs[i].childNodes[2];
3707             s.style.width = tw;
3708             s.firstChild.style.width = tw;
3709             s.firstChild.rows[0].childNodes[col].style.width = w;
3710         }
3711     },
3712
3713     doAllWidths : function(ws, tw){
3714         if(!this.isGrouped()){
3715             return;
3716         }
3717         var gs = this.view.getGroups(),
3718             len = gs.length,
3719             i = 0,
3720             j, 
3721             s, 
3722             cells, 
3723             wlen = ws.length;
3724             
3725         for(; i < len; i++){
3726             s = gs[i].childNodes[2];
3727             s.style.width = tw;
3728             s.firstChild.style.width = tw;
3729             cells = s.firstChild.rows[0].childNodes;
3730             for(j = 0; j < wlen; j++){
3731                 cells[j].style.width = ws[j];
3732             }
3733         }
3734     },
3735
3736     doHidden : function(col, hidden, tw){
3737         if(!this.isGrouped()){
3738             return;
3739         }
3740         var gs = this.view.getGroups(),
3741             len = gs.length,
3742             i = 0,
3743             s, 
3744             display = hidden ? 'none' : '';
3745         for(; i < len; i++){
3746             s = gs[i].childNodes[2];
3747             s.style.width = tw;
3748             s.firstChild.style.width = tw;
3749             s.firstChild.rows[0].childNodes[col].style.display = display;
3750         }
3751     },
3752     
3753     isGrouped : function(){
3754         return !Ext.isEmpty(this.grid.getStore().groupField);
3755     },
3756
3757     // Note: requires that all (or the first) record in the
3758     // group share the same group value. Returns false if the group
3759     // could not be found.
3760     refreshSummary : function(groupValue){
3761         return this.refreshSummaryById(this.view.getGroupId(groupValue));
3762     },
3763
3764     getSummaryNode : function(gid){
3765         var g = Ext.fly(gid, '_gsummary');
3766         if(g){
3767             return g.down('.x-grid3-summary-row', true);
3768         }
3769         return null;
3770     },
3771
3772     refreshSummaryById : function(gid){
3773         var g = Ext.getDom(gid);
3774         if(!g){
3775             return false;
3776         }
3777         var rs = [];
3778         this.grid.getStore().each(function(r){
3779             if(r._groupId == gid){
3780                 rs[rs.length] = r;
3781             }
3782         });
3783         var cs = this.view.getColumnData(),
3784             data = this.calculate(rs, cs),
3785             markup = this.renderSummary({data: data}, cs),
3786             existing = this.getSummaryNode(gid);
3787             
3788         if(existing){
3789             g.removeChild(existing);
3790         }
3791         Ext.DomHelper.append(g, markup);
3792         return true;
3793     },
3794
3795     doUpdate : function(ds, record){
3796         this.refreshSummaryById(record._groupId);
3797     },
3798
3799     doRemove : function(ds, record, index, isUpdate){
3800         if(!isUpdate){
3801             this.refreshSummaryById(record._groupId);
3802         }
3803     },
3804
3805     /**
3806      * Show a message in the summary row.
3807      * <pre><code>
3808 grid.on('afteredit', function(){
3809     var groupValue = 'Ext Forms: Field Anchoring';
3810     summary.showSummaryMsg(groupValue, 'Updating Summary...');
3811 });
3812      * </code></pre>
3813      * @param {String} groupValue
3814      * @param {String} msg Text to use as innerHTML for the summary row.
3815      */
3816     showSummaryMsg : function(groupValue, msg){
3817         var gid = this.view.getGroupId(groupValue),
3818              node = this.getSummaryNode(gid);
3819         if(node){
3820             node.innerHTML = '<div class="x-grid3-summary-msg">' + msg + '</div>';
3821         }
3822     }
3823 });
3824
3825 //backwards compat
3826 Ext.grid.GroupSummary = Ext.ux.grid.GroupSummary;
3827
3828
3829 /**
3830  * Calculation types for summary row:</p><div class="mdetail-params"><ul>
3831  * <li><b><tt>sum</tt></b> : <div class="sub-desc"></div></li>
3832  * <li><b><tt>count</tt></b> : <div class="sub-desc"></div></li>
3833  * <li><b><tt>max</tt></b> : <div class="sub-desc"></div></li>
3834  * <li><b><tt>min</tt></b> : <div class="sub-desc"></div></li>
3835  * <li><b><tt>average</tt></b> : <div class="sub-desc"></div></li>
3836  * </ul></div>
3837  * <p>Custom calculations may be implemented.  An example of
3838  * custom <code>summaryType=totalCost</code>:</p><pre><code>
3839 // define a custom summary function
3840 Ext.ux.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){
3841     return v + (record.data.estimate * record.data.rate);
3842 };
3843  * </code></pre>
3844  * @property Calculations
3845  */
3846
3847 Ext.ux.grid.GroupSummary.Calculations = {
3848     'sum' : function(v, record, field){
3849         return v + (record.data[field]||0);
3850     },
3851
3852     'count' : function(v, record, field, data){
3853         return data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);
3854     },
3855
3856     'max' : function(v, record, field, data){
3857         var v = record.data[field];
3858         var max = data[field+'max'] === undefined ? (data[field+'max'] = v) : data[field+'max'];
3859         return v > max ? (data[field+'max'] = v) : max;
3860     },
3861
3862     'min' : function(v, record, field, data){
3863         var v = record.data[field];
3864         var min = data[field+'min'] === undefined ? (data[field+'min'] = v) : data[field+'min'];
3865         return v < min ? (data[field+'min'] = v) : min;
3866     },
3867
3868     'average' : function(v, record, field, data){
3869         var c = data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);
3870         var t = (data[field+'total'] = ((data[field+'total']||0) + (record.data[field]||0)));
3871         return t === 0 ? 0 : t / c;
3872     }
3873 };
3874 Ext.grid.GroupSummary.Calculations = Ext.ux.grid.GroupSummary.Calculations;
3875
3876 /**
3877  * @class Ext.ux.grid.HybridSummary
3878  * @extends Ext.ux.grid.GroupSummary
3879  * Adds capability to specify the summary data for the group via json as illustrated here:
3880  * <pre><code>
3881 {
3882     data: [
3883         {
3884             projectId: 100,     project: 'House',
3885             taskId:    112, description: 'Paint',
3886             estimate:    6,        rate:     150,
3887             due:'06/24/2007'
3888         },
3889         ...
3890     ],
3891
3892     summaryData: {
3893         'House': {
3894             description: 14, estimate: 9,
3895                    rate: 99, due: new Date(2009, 6, 29),
3896                    cost: 999
3897         }
3898     }
3899 }
3900  * </code></pre>
3901  *
3902  */
3903 Ext.ux.grid.HybridSummary = Ext.extend(Ext.ux.grid.GroupSummary, {
3904     /**
3905      * @private
3906      * @param {Object} rs
3907      * @param {Object} cs
3908      */
3909     calculate : function(rs, cs){
3910         var gcol = this.view.getGroupField(),
3911             gvalue = rs[0].data[gcol],
3912             gdata = this.getSummaryData(gvalue);
3913         return gdata || Ext.ux.grid.HybridSummary.superclass.calculate.call(this, rs, cs);
3914     },
3915
3916     /**
3917      * <pre><code>
3918 grid.on('afteredit', function(){
3919     var groupValue = 'Ext Forms: Field Anchoring';
3920     summary.showSummaryMsg(groupValue, 'Updating Summary...');
3921     setTimeout(function(){ // simulate server call
3922         // HybridSummary class implements updateSummaryData
3923         summary.updateSummaryData(groupValue,
3924             // create data object based on configured dataIndex
3925             {description: 22, estimate: 888, rate: 888, due: new Date(), cost: 8});
3926     }, 2000);
3927 });
3928      * </code></pre>
3929      * @param {String} groupValue
3930      * @param {Object} data data object
3931      * @param {Boolean} skipRefresh (Optional) Defaults to false
3932      */
3933     updateSummaryData : function(groupValue, data, skipRefresh){
3934         var json = this.grid.getStore().reader.jsonData;
3935         if(!json.summaryData){
3936             json.summaryData = {};
3937         }
3938         json.summaryData[groupValue] = data;
3939         if(!skipRefresh){
3940             this.refreshSummary(groupValue);
3941         }
3942     },
3943
3944     /**
3945      * Returns the summaryData for the specified groupValue or null.
3946      * @param {String} groupValue
3947      * @return {Object} summaryData
3948      */
3949     getSummaryData : function(groupValue){
3950         var reader = this.grid.getStore().reader,
3951             json = reader.jsonData,
3952             fields = reader.recordType.prototype.fields,
3953             v;
3954             
3955         if(json && json.summaryData){
3956             v = json.summaryData[groupValue];
3957             if(v){
3958                 return reader.extractValues(v, fields.items, fields.length);
3959             }
3960         }
3961         return null;
3962     }
3963 });
3964
3965 //backwards compat
3966 Ext.grid.HybridSummary = Ext.ux.grid.HybridSummary;
3967 Ext.ux.GroupTab = Ext.extend(Ext.Container, {
3968     mainItem: 0,
3969     
3970     expanded: true,
3971     
3972     deferredRender: true,
3973     
3974     activeTab: null,
3975     
3976     idDelimiter: '__',
3977     
3978     headerAsText: false,
3979     
3980     frame: false,
3981     
3982     hideBorders: true,
3983     
3984     initComponent: function(config){
3985         Ext.apply(this, config);
3986         this.frame = false;
3987         
3988         Ext.ux.GroupTab.superclass.initComponent.call(this);
3989         
3990         this.addEvents('activate', 'deactivate', 'changemainitem', 'beforetabchange', 'tabchange');
3991         
3992         this.setLayout(new Ext.layout.CardLayout({
3993             deferredRender: this.deferredRender
3994         }));
3995         
3996         if (!this.stack) {
3997             this.stack = Ext.TabPanel.AccessStack();
3998         }
3999         
4000         this.initItems();
4001         
4002         this.on('beforerender', function(){
4003             this.groupEl = this.ownerCt.getGroupEl(this);
4004         }, this);
4005         
4006         this.on('add', this.onAdd, this, {
4007             target: this
4008         });
4009         this.on('remove', this.onRemove, this, {
4010             target: this
4011         });
4012         
4013         if (this.mainItem !== undefined) {
4014             var item = (typeof this.mainItem == 'object') ? this.mainItem : this.items.get(this.mainItem);
4015             delete this.mainItem;
4016             this.setMainItem(item);
4017         }
4018     },
4019     
4020     /**
4021      * Sets the specified tab as the active tab. This method fires the {@link #beforetabchange} event which
4022      * can return false to cancel the tab change.
4023      * @param {String/Panel} tab The id or tab Panel to activate
4024      */
4025     setActiveTab : function(item){
4026         item = this.getComponent(item);
4027         if(!item){
4028             return false;
4029         }
4030         if(!this.rendered){
4031             this.activeTab = item;
4032             return true;
4033         }
4034         if(this.activeTab != item && this.fireEvent('beforetabchange', this, item, this.activeTab) !== false){
4035             if(this.activeTab && this.activeTab != this.mainItem){
4036                 var oldEl = this.getTabEl(this.activeTab);
4037                 if(oldEl){
4038                     Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');
4039                 }
4040             }
4041             var el = this.getTabEl(item);
4042             Ext.fly(el).addClass('x-grouptabs-strip-active');
4043             this.activeTab = item;
4044             this.stack.add(item);
4045
4046             this.layout.setActiveItem(item);
4047             if(this.layoutOnTabChange && item.doLayout){
4048                 item.doLayout();
4049             }
4050             if(this.scrolling){
4051                 this.scrollToTab(item, this.animScroll);
4052             }
4053
4054             this.fireEvent('tabchange', this, item);
4055             return true;
4056         }
4057         return false;
4058     },
4059     
4060     getTabEl: function(item){
4061         if (item == this.mainItem) {
4062             return this.groupEl;
4063         }
4064         return Ext.TabPanel.prototype.getTabEl.call(this, item);
4065     },
4066     
4067     onRender: function(ct, position){
4068         Ext.ux.GroupTab.superclass.onRender.call(this, ct, position);
4069         
4070         this.strip = Ext.fly(this.groupEl).createChild({
4071             tag: 'ul',
4072             cls: 'x-grouptabs-sub'
4073         });
4074
4075         this.tooltip = new Ext.ToolTip({
4076            target: this.groupEl,
4077            delegate: 'a.x-grouptabs-text',
4078            trackMouse: true,
4079            renderTo: document.body,
4080            listeners: {
4081                beforeshow: function(tip) {
4082                    var item = (tip.triggerElement.parentNode === this.mainItem.tabEl)
4083                        ? this.mainItem
4084                        : this.findById(tip.triggerElement.parentNode.id.split(this.idDelimiter)[1]);
4085
4086                    if(!item.tabTip) {
4087                        return false;
4088                    }
4089                    tip.body.dom.innerHTML = item.tabTip;
4090                },
4091                scope: this
4092            }
4093         });
4094                 
4095         if (!this.itemTpl) {
4096             var tt = new Ext.Template('<li class="{cls}" id="{id}">', '<a onclick="return false;" class="x-grouptabs-text {iconCls}">{text}</a>', '</li>');
4097             tt.disableFormats = true;
4098             tt.compile();
4099             Ext.ux.GroupTab.prototype.itemTpl = tt;
4100         }
4101         
4102         this.items.each(this.initTab, this);
4103     },
4104     
4105     afterRender: function(){
4106         Ext.ux.GroupTab.superclass.afterRender.call(this);
4107         
4108         if (this.activeTab !== undefined) {
4109             var item = (typeof this.activeTab == 'object') ? this.activeTab : this.items.get(this.activeTab);
4110             delete this.activeTab;
4111             this.setActiveTab(item);
4112         }
4113     },
4114     
4115     // private
4116     initTab: function(item, index){
4117         var before = this.strip.dom.childNodes[index];
4118         var p = Ext.TabPanel.prototype.getTemplateArgs.call(this, item);
4119         
4120         if (item === this.mainItem) {
4121             item.tabEl = this.groupEl;
4122             p.cls += ' x-grouptabs-main-item';
4123         }
4124         
4125         var el = before ? this.itemTpl.insertBefore(before, p) : this.itemTpl.append(this.strip, p);
4126         
4127         item.tabEl = item.tabEl || el;
4128                 
4129         item.on('disable', this.onItemDisabled, this);
4130         item.on('enable', this.onItemEnabled, this);
4131         item.on('titlechange', this.onItemTitleChanged, this);
4132         item.on('iconchange', this.onItemIconChanged, this);
4133         item.on('beforeshow', this.onBeforeShowItem, this);
4134     },
4135     
4136     setMainItem: function(item){
4137         item = this.getComponent(item);
4138         if (!item || this.fireEvent('changemainitem', this, item, this.mainItem) === false) {
4139             return;
4140         }
4141         
4142         this.mainItem = item;
4143     },
4144     
4145     getMainItem: function(){
4146         return this.mainItem || null;
4147     },
4148     
4149     // private
4150     onBeforeShowItem: function(item){
4151         if (item != this.activeTab) {
4152             this.setActiveTab(item);
4153             return false;
4154         }
4155     },
4156     
4157     // private
4158     onAdd: function(gt, item, index){
4159         if (this.rendered) {
4160             this.initTab.call(this, item, index);
4161         }
4162     },
4163     
4164     // private
4165     onRemove: function(tp, item){
4166         Ext.destroy(Ext.get(this.getTabEl(item)));
4167         this.stack.remove(item);
4168         item.un('disable', this.onItemDisabled, this);
4169         item.un('enable', this.onItemEnabled, this);
4170         item.un('titlechange', this.onItemTitleChanged, this);
4171         item.un('iconchange', this.onItemIconChanged, this);
4172         item.un('beforeshow', this.onBeforeShowItem, this);
4173         if (item == this.activeTab) {
4174             var next = this.stack.next();
4175             if (next) {
4176                 this.setActiveTab(next);
4177             }
4178             else if (this.items.getCount() > 0) {
4179                 this.setActiveTab(0);
4180             }
4181             else {
4182                 this.activeTab = null;
4183             }
4184         }
4185     },
4186     
4187     // private
4188     onBeforeAdd: function(item){
4189         var existing = item.events ? (this.items.containsKey(item.getItemId()) ? item : null) : this.items.get(item);
4190         if (existing) {
4191             this.setActiveTab(item);
4192             return false;
4193         }
4194         Ext.TabPanel.superclass.onBeforeAdd.apply(this, arguments);
4195         var es = item.elements;
4196         item.elements = es ? es.replace(',header', '') : es;
4197         item.border = (item.border === true);
4198     },
4199     
4200     // private
4201     onItemDisabled: Ext.TabPanel.prototype.onItemDisabled,
4202     onItemEnabled: Ext.TabPanel.prototype.onItemEnabled,
4203     
4204     // private
4205     onItemTitleChanged: function(item){
4206         var el = this.getTabEl(item);
4207         if (el) {
4208             Ext.fly(el).child('a.x-grouptabs-text', true).innerHTML = item.title;
4209         }
4210     },
4211     
4212     //private
4213     onItemIconChanged: function(item, iconCls, oldCls){
4214         var el = this.getTabEl(item);
4215         if (el) {
4216             Ext.fly(el).child('a.x-grouptabs-text').replaceClass(oldCls, iconCls);
4217         }
4218     },
4219     
4220     beforeDestroy: function(){
4221         Ext.TabPanel.prototype.beforeDestroy.call(this);
4222         this.tooltip.destroy();
4223     }
4224 });
4225
4226 Ext.reg('grouptab', Ext.ux.GroupTab);
4227 Ext.ns('Ext.ux');
4228
4229 Ext.ux.GroupTabPanel = Ext.extend(Ext.TabPanel, {
4230     tabPosition: 'left',
4231
4232     alternateColor: false,
4233
4234     alternateCls: 'x-grouptabs-panel-alt',
4235
4236     defaultType: 'grouptab',
4237
4238     deferredRender: false,
4239
4240     activeGroup : null,
4241
4242     initComponent: function(){
4243         Ext.ux.GroupTabPanel.superclass.initComponent.call(this);
4244         
4245         this.addEvents(
4246             'beforegroupchange',
4247             'groupchange'
4248         );
4249         this.elements = 'body,header';
4250         this.stripTarget = 'header';
4251
4252         this.tabPosition = this.tabPosition == 'right' ? 'right' : 'left';
4253
4254         this.addClass('x-grouptabs-panel');
4255
4256         if (this.tabStyle && this.tabStyle != '') {
4257             this.addClass('x-grouptabs-panel-' + this.tabStyle);
4258         }
4259
4260         if (this.alternateColor) {
4261             this.addClass(this.alternateCls);
4262         }
4263
4264         this.on('beforeadd', function(gtp, item, index){
4265             this.initGroup(item, index);
4266         });
4267         this.items.each(function(item){
4268             item.on('tabchange',function(item){
4269                 this.fireEvent('tabchange', this, item.activeTab);
4270             }, this);
4271         },this);
4272     },
4273
4274     initEvents : function() {
4275         this.mon(this.strip, 'mousedown', this.onStripMouseDown, this);
4276     },
4277
4278     onRender: function(ct, position){
4279         Ext.TabPanel.superclass.onRender.call(this, ct, position);
4280         if(this.plain){
4281             var pos = this.tabPosition == 'top' ? 'header' : 'footer';
4282             this[pos].addClass('x-tab-panel-'+pos+'-plain');
4283         }
4284
4285         var st = this[this.stripTarget];
4286
4287         this.stripWrap = st.createChild({cls:'x-tab-strip-wrap ', cn:{
4288             tag:'ul', cls:'x-grouptabs-strip x-grouptabs-tab-strip-'+this.tabPosition}});
4289
4290         var beforeEl = (this.tabPosition=='bottom' ? this.stripWrap : null);
4291         this.strip = new Ext.Element(this.stripWrap.dom.firstChild);
4292
4293         this.header.addClass('x-grouptabs-panel-header');
4294         this.bwrap.addClass('x-grouptabs-bwrap');
4295         this.body.addClass('x-tab-panel-body-'+this.tabPosition + ' x-grouptabs-panel-body');
4296
4297         if (!this.groupTpl) {
4298             var tt = new Ext.Template(
4299                 '<li class="{cls}" id="{id}">',
4300                 '<a class="x-grouptabs-expand" onclick="return false;"></a>',
4301                 '<a class="x-grouptabs-text {iconCls}" href="#" onclick="return false;">',
4302                 '<span>{text}</span></a>',
4303                 '</li>'
4304             );
4305             tt.disableFormats = true;
4306             tt.compile();
4307             Ext.ux.GroupTabPanel.prototype.groupTpl = tt;
4308         }
4309         this.items.each(this.initGroup, this);
4310     },
4311
4312     afterRender: function(){
4313         Ext.ux.GroupTabPanel.superclass.afterRender.call(this);
4314
4315         this.tabJoint = Ext.fly(this.body.dom.parentNode).createChild({
4316             cls: 'x-tab-joint'
4317         });
4318
4319         this.addClass('x-tab-panel-' + this.tabPosition);
4320         this.header.setWidth(this.tabWidth);
4321
4322         if (this.activeGroup !== undefined) {
4323             var group = (typeof this.activeGroup == 'object') ? this.activeGroup : this.items.get(this.activeGroup);
4324             delete this.activeGroup;
4325             this.setActiveGroup(group);
4326             group.setActiveTab(group.getMainItem());
4327         }
4328     },
4329
4330     getGroupEl : Ext.TabPanel.prototype.getTabEl,
4331
4332     // private
4333     findTargets: function(e){
4334         var item = null,
4335             itemEl = e.getTarget('li', this.strip);
4336         if (itemEl) {
4337             item = this.findById(itemEl.id.split(this.idDelimiter)[1]);
4338             if (item.disabled) {
4339                 return {
4340                     expand: null,
4341                     item: null,
4342                     el: null
4343                 };
4344             }
4345         }
4346         return {
4347             expand: e.getTarget('.x-grouptabs-expand', this.strip),
4348             isGroup: !e.getTarget('ul.x-grouptabs-sub', this.strip),
4349             item: item,
4350             el: itemEl
4351         };
4352     },
4353
4354     // private
4355     onStripMouseDown: function(e){
4356         if (e.button != 0) {
4357             return;
4358         }
4359         e.preventDefault();
4360         var t = this.findTargets(e);
4361         if (t.expand) {
4362             this.toggleGroup(t.el);
4363         }
4364         else if (t.item) {
4365             if(t.isGroup) {
4366                 t.item.setActiveTab(t.item.getMainItem());
4367             }
4368             else {
4369                 t.item.ownerCt.setActiveTab(t.item);
4370             }
4371         }
4372     },
4373
4374     expandGroup: function(groupEl){
4375         if(groupEl.isXType) {
4376             groupEl = this.getGroupEl(groupEl);
4377         }
4378         Ext.fly(groupEl).addClass('x-grouptabs-expanded');
4379         this.syncTabJoint();
4380     },
4381
4382     toggleGroup: function(groupEl){
4383         if(groupEl.isXType) {
4384             groupEl = this.getGroupEl(groupEl);
4385         }
4386         Ext.fly(groupEl).toggleClass('x-grouptabs-expanded');
4387         this.syncTabJoint();
4388     },
4389
4390     collapseGroup: function(groupEl){
4391         if(groupEl.isXType) {
4392             groupEl = this.getGroupEl(groupEl);
4393         }
4394         Ext.fly(groupEl).removeClass('x-grouptabs-expanded');
4395         this.syncTabJoint();
4396     },
4397
4398     syncTabJoint: function(groupEl){
4399         if (!this.tabJoint) {
4400             return;
4401         }
4402
4403         groupEl = groupEl || this.getGroupEl(this.activeGroup);
4404         if(groupEl) {
4405             this.tabJoint.setHeight(Ext.fly(groupEl).getHeight() - 2);
4406
4407             var y = Ext.isGecko2 ? 0 : 1;
4408             if (this.tabPosition == 'left'){
4409                 this.tabJoint.alignTo(groupEl, 'tl-tr', [-2,y]);
4410             }
4411             else {
4412                 this.tabJoint.alignTo(groupEl, 'tr-tl', [1,y]);
4413             }
4414         }
4415         else {
4416             this.tabJoint.hide();
4417         }
4418     },
4419
4420     getActiveTab : function() {
4421         if(!this.activeGroup) return null;
4422         return this.activeGroup.getTabEl(this.activeGroup.activeTab) || null;
4423     },
4424
4425     onResize: function(){
4426         Ext.ux.GroupTabPanel.superclass.onResize.apply(this, arguments);
4427         this.syncTabJoint();
4428     },
4429
4430     createCorner: function(el, pos){
4431         return Ext.fly(el).createChild({
4432             cls: 'x-grouptabs-corner x-grouptabs-corner-' + pos
4433         });
4434     },
4435
4436     initGroup: function(group, index){
4437         var before = this.strip.dom.childNodes[index],
4438             p = this.getTemplateArgs(group);
4439         if (index === 0) {
4440             p.cls += ' x-tab-first';
4441         }
4442         p.cls += ' x-grouptabs-main';
4443         p.text = group.getMainItem().title;
4444
4445         var el = before ? this.groupTpl.insertBefore(before, p) : this.groupTpl.append(this.strip, p),
4446             tl = this.createCorner(el, 'top-' + this.tabPosition),
4447             bl = this.createCorner(el, 'bottom-' + this.tabPosition);
4448
4449         group.tabEl = el;
4450         if (group.expanded) {
4451             this.expandGroup(el);
4452         }
4453
4454         if (Ext.isIE6 || (Ext.isIE && !Ext.isStrict)){
4455             bl.setLeft('-10px');
4456             bl.setBottom('-5px');
4457             tl.setLeft('-10px');
4458             tl.setTop('-5px');
4459         }
4460
4461         this.mon(group, {
4462             scope: this,
4463             changemainitem: this.onGroupChangeMainItem,
4464             beforetabchange: this.onGroupBeforeTabChange
4465         });
4466     },
4467
4468     setActiveGroup : function(group) {
4469         group = this.getComponent(group);
4470         if(!group){
4471             return false;
4472         }
4473         if(!this.rendered){
4474             this.activeGroup = group;
4475             return true;
4476         }
4477         if(this.activeGroup != group && this.fireEvent('beforegroupchange', this, group, this.activeGroup) !== false){
4478             if(this.activeGroup){
4479                 this.activeGroup.activeTab = null;
4480                 var oldEl = this.getGroupEl(this.activeGroup);
4481                 if(oldEl){
4482                     Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');
4483                 }
4484             }
4485
4486             var groupEl = this.getGroupEl(group);
4487             Ext.fly(groupEl).addClass('x-grouptabs-strip-active');
4488
4489             this.activeGroup = group;
4490             this.stack.add(group);
4491
4492             this.layout.setActiveItem(group);
4493             this.syncTabJoint(groupEl);
4494
4495             this.fireEvent('groupchange', this, group);
4496             return true;
4497         }
4498         return false;
4499     },
4500
4501     onGroupBeforeTabChange: function(group, newTab, oldTab){
4502         if(group !== this.activeGroup || newTab !== oldTab) {
4503             this.strip.select('.x-grouptabs-sub > li.x-grouptabs-strip-active', true).removeClass('x-grouptabs-strip-active');
4504         }
4505         this.expandGroup(this.getGroupEl(group));
4506         if(group !== this.activeGroup) {
4507             return this.setActiveGroup(group);
4508         }
4509     },
4510
4511     getFrameHeight: function(){
4512         var h = this.el.getFrameWidth('tb');
4513         h += (this.tbar ? this.tbar.getHeight() : 0) +
4514         (this.bbar ? this.bbar.getHeight() : 0);
4515
4516         return h;
4517     },
4518
4519     adjustBodyWidth: function(w){
4520         return w - this.tabWidth;
4521     }
4522 });
4523
4524 Ext.reg('grouptabpanel', Ext.ux.GroupTabPanel);
4525 /*
4526  * Note that this control will most likely remain as an example, and not as a core Ext form
4527  * control.  However, the API will be changing in a future release and so should not yet be
4528  * treated as a final, stable API at this time.
4529  */
4530
4531 /**
4532  * @class Ext.ux.form.ItemSelector
4533  * @extends Ext.form.Field
4534  * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
4535  *
4536  *  @history
4537  *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)
4538  *
4539  * @constructor
4540  * Create a new ItemSelector
4541  * @param {Object} config Configuration options
4542  * @xtype itemselector 
4543  */
4544 Ext.ux.form.ItemSelector = Ext.extend(Ext.form.Field,  {
4545     hideNavIcons:false,
4546     imagePath:"",
4547     iconUp:"up2.gif",
4548     iconDown:"down2.gif",
4549     iconLeft:"left2.gif",
4550     iconRight:"right2.gif",
4551     iconTop:"top2.gif",
4552     iconBottom:"bottom2.gif",
4553     drawUpIcon:true,
4554     drawDownIcon:true,
4555     drawLeftIcon:true,
4556     drawRightIcon:true,
4557     drawTopIcon:true,
4558     drawBotIcon:true,
4559     delimiter:',',
4560     bodyStyle:null,
4561     border:false,
4562     defaultAutoCreate:{tag: "div"},
4563     /**
4564      * @cfg {Array} multiselects An array of {@link Ext.ux.form.MultiSelect} config objects, with at least all required parameters (e.g., store)
4565      */
4566     multiselects:null,
4567
4568     initComponent: function(){
4569         Ext.ux.form.ItemSelector.superclass.initComponent.call(this);
4570         this.addEvents({
4571             'rowdblclick' : true,
4572             'change' : true
4573         });
4574     },
4575
4576     onRender: function(ct, position){
4577         Ext.ux.form.ItemSelector.superclass.onRender.call(this, ct, position);
4578
4579         // Internal default configuration for both multiselects
4580         var msConfig = [{
4581             legend: 'Available',
4582             draggable: true,
4583             droppable: true,
4584             width: 100,
4585             height: 100
4586         },{
4587             legend: 'Selected',
4588             droppable: true,
4589             draggable: true,
4590             width: 100,
4591             height: 100
4592         }];
4593
4594         this.fromMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[0], msConfig[0]));
4595         this.fromMultiselect.on('dblclick', this.onRowDblClick, this);
4596
4597         this.toMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[1], msConfig[1]));
4598         this.toMultiselect.on('dblclick', this.onRowDblClick, this);
4599
4600         var p = new Ext.Panel({
4601             bodyStyle:this.bodyStyle,
4602             border:this.border,
4603             layout:"table",
4604             layoutConfig:{columns:3}
4605         });
4606
4607         p.add(this.fromMultiselect);
4608         var icons = new Ext.Panel({header:false});
4609         p.add(icons);
4610         p.add(this.toMultiselect);
4611         p.render(this.el);
4612         icons.el.down('.'+icons.bwrapCls).remove();
4613
4614         // ICON HELL!!!
4615         if (this.imagePath!="" && this.imagePath.charAt(this.imagePath.length-1)!="/")
4616             this.imagePath+="/";
4617         this.iconUp = this.imagePath + (this.iconUp || 'up2.gif');
4618         this.iconDown = this.imagePath + (this.iconDown || 'down2.gif');
4619         this.iconLeft = this.imagePath + (this.iconLeft || 'left2.gif');
4620         this.iconRight = this.imagePath + (this.iconRight || 'right2.gif');
4621         this.iconTop = this.imagePath + (this.iconTop || 'top2.gif');
4622         this.iconBottom = this.imagePath + (this.iconBottom || 'bottom2.gif');
4623         var el=icons.getEl();
4624         this.toTopIcon = el.createChild({tag:'img', src:this.iconTop, style:{cursor:'pointer', margin:'2px'}});
4625         el.createChild({tag: 'br'});
4626         this.upIcon = el.createChild({tag:'img', src:this.iconUp, style:{cursor:'pointer', margin:'2px'}});
4627         el.createChild({tag: 'br'});
4628         this.addIcon = el.createChild({tag:'img', src:this.iconRight, style:{cursor:'pointer', margin:'2px'}});
4629         el.createChild({tag: 'br'});
4630         this.removeIcon = el.createChild({tag:'img', src:this.iconLeft, style:{cursor:'pointer', margin:'2px'}});
4631         el.createChild({tag: 'br'});
4632         this.downIcon = el.createChild({tag:'img', src:this.iconDown, style:{cursor:'pointer', margin:'2px'}});
4633         el.createChild({tag: 'br'});
4634         this.toBottomIcon = el.createChild({tag:'img', src:this.iconBottom, style:{cursor:'pointer', margin:'2px'}});
4635         this.toTopIcon.on('click', this.toTop, this);
4636         this.upIcon.on('click', this.up, this);
4637         this.downIcon.on('click', this.down, this);
4638         this.toBottomIcon.on('click', this.toBottom, this);
4639         this.addIcon.on('click', this.fromTo, this);
4640         this.removeIcon.on('click', this.toFrom, this);
4641         if (!this.drawUpIcon || this.hideNavIcons) { this.upIcon.dom.style.display='none'; }
4642         if (!this.drawDownIcon || this.hideNavIcons) { this.downIcon.dom.style.display='none'; }
4643         if (!this.drawLeftIcon || this.hideNavIcons) { this.addIcon.dom.style.display='none'; }
4644         if (!this.drawRightIcon || this.hideNavIcons) { this.removeIcon.dom.style.display='none'; }
4645         if (!this.drawTopIcon || this.hideNavIcons) { this.toTopIcon.dom.style.display='none'; }
4646         if (!this.drawBotIcon || this.hideNavIcons) { this.toBottomIcon.dom.style.display='none'; }
4647
4648         var tb = p.body.first();
4649         this.el.setWidth(p.body.first().getWidth());
4650         p.body.removeClass();
4651
4652         this.hiddenName = this.name;
4653         var hiddenTag = {tag: "input", type: "hidden", value: "", name: this.name};
4654         this.hiddenField = this.el.createChild(hiddenTag);
4655     },
4656     
4657     doLayout: function(){
4658         if(this.rendered){
4659             this.fromMultiselect.fs.doLayout();
4660             this.toMultiselect.fs.doLayout();
4661         }
4662     },
4663
4664     afterRender: function(){
4665         Ext.ux.form.ItemSelector.superclass.afterRender.call(this);
4666
4667         this.toStore = this.toMultiselect.store;
4668         this.toStore.on('add', this.valueChanged, this);
4669         this.toStore.on('remove', this.valueChanged, this);
4670         this.toStore.on('load', this.valueChanged, this);
4671         this.valueChanged(this.toStore);
4672     },
4673
4674     toTop : function() {
4675         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
4676         var records = [];
4677         if (selectionsArray.length > 0) {
4678             selectionsArray.sort();
4679             for (var i=0; i<selectionsArray.length; i++) {
4680                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
4681                 records.push(record);
4682             }
4683             selectionsArray = [];
4684             for (var i=records.length-1; i>-1; i--) {
4685                 record = records[i];
4686                 this.toMultiselect.view.store.remove(record);
4687                 this.toMultiselect.view.store.insert(0, record);
4688                 selectionsArray.push(((records.length - 1) - i));
4689             }
4690         }
4691         this.toMultiselect.view.refresh();
4692         this.toMultiselect.view.select(selectionsArray);
4693     },
4694
4695     toBottom : function() {
4696         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
4697         var records = [];
4698         if (selectionsArray.length > 0) {
4699             selectionsArray.sort();
4700             for (var i=0; i<selectionsArray.length; i++) {
4701                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
4702                 records.push(record);
4703             }
4704             selectionsArray = [];
4705             for (var i=0; i<records.length; i++) {
4706                 record = records[i];
4707                 this.toMultiselect.view.store.remove(record);
4708                 this.toMultiselect.view.store.add(record);
4709                 selectionsArray.push((this.toMultiselect.view.store.getCount()) - (records.length - i));
4710             }
4711         }
4712         this.toMultiselect.view.refresh();
4713         this.toMultiselect.view.select(selectionsArray);
4714     },
4715
4716     up : function() {
4717         var record = null;
4718         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
4719         selectionsArray.sort();
4720         var newSelectionsArray = [];
4721         if (selectionsArray.length > 0) {
4722             for (var i=0; i<selectionsArray.length; i++) {
4723                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
4724                 if ((selectionsArray[i] - 1) >= 0) {
4725                     this.toMultiselect.view.store.remove(record);
4726                     this.toMultiselect.view.store.insert(selectionsArray[i] - 1, record);
4727                     newSelectionsArray.push(selectionsArray[i] - 1);
4728                 }
4729             }
4730             this.toMultiselect.view.refresh();
4731             this.toMultiselect.view.select(newSelectionsArray);
4732         }
4733     },
4734
4735     down : function() {
4736         var record = null;
4737         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
4738         selectionsArray.sort();
4739         selectionsArray.reverse();
4740         var newSelectionsArray = [];
4741         if (selectionsArray.length > 0) {
4742             for (var i=0; i<selectionsArray.length; i++) {
4743                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
4744                 if ((selectionsArray[i] + 1) < this.toMultiselect.view.store.getCount()) {
4745                     this.toMultiselect.view.store.remove(record);
4746                     this.toMultiselect.view.store.insert(selectionsArray[i] + 1, record);
4747                     newSelectionsArray.push(selectionsArray[i] + 1);
4748                 }
4749             }
4750             this.toMultiselect.view.refresh();
4751             this.toMultiselect.view.select(newSelectionsArray);
4752         }
4753     },
4754
4755     fromTo : function() {
4756         var selectionsArray = this.fromMultiselect.view.getSelectedIndexes();
4757         var records = [];
4758         if (selectionsArray.length > 0) {
4759             for (var i=0; i<selectionsArray.length; i++) {
4760                 record = this.fromMultiselect.view.store.getAt(selectionsArray[i]);
4761                 records.push(record);
4762             }
4763             if(!this.allowDup)selectionsArray = [];
4764             for (var i=0; i<records.length; i++) {
4765                 record = records[i];
4766                 if(this.allowDup){
4767                     var x=new Ext.data.Record();
4768                     record.id=x.id;
4769                     delete x;
4770                     this.toMultiselect.view.store.add(record);
4771                 }else{
4772                     this.fromMultiselect.view.store.remove(record);
4773                     this.toMultiselect.view.store.add(record);
4774                     selectionsArray.push((this.toMultiselect.view.store.getCount() - 1));
4775                 }
4776             }
4777         }
4778         this.toMultiselect.view.refresh();
4779         this.fromMultiselect.view.refresh();
4780         var si = this.toMultiselect.store.sortInfo;
4781         if(si){
4782             this.toMultiselect.store.sort(si.field, si.direction);
4783         }
4784         this.toMultiselect.view.select(selectionsArray);
4785     },
4786
4787     toFrom : function() {
4788         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
4789         var records = [];
4790         if (selectionsArray.length > 0) {
4791             for (var i=0; i<selectionsArray.length; i++) {
4792                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
4793                 records.push(record);
4794             }
4795             selectionsArray = [];
4796             for (var i=0; i<records.length; i++) {
4797                 record = records[i];
4798                 this.toMultiselect.view.store.remove(record);
4799                 if(!this.allowDup){
4800                     this.fromMultiselect.view.store.add(record);
4801                     selectionsArray.push((this.fromMultiselect.view.store.getCount() - 1));
4802                 }
4803             }
4804         }
4805         this.fromMultiselect.view.refresh();
4806         this.toMultiselect.view.refresh();
4807         var si = this.fromMultiselect.store.sortInfo;
4808         if (si){
4809             this.fromMultiselect.store.sort(si.field, si.direction);
4810         }
4811         this.fromMultiselect.view.select(selectionsArray);
4812     },
4813
4814     valueChanged: function(store) {
4815         var record = null;
4816         var values = [];
4817         for (var i=0; i<store.getCount(); i++) {
4818             record = store.getAt(i);
4819             values.push(record.get(this.toMultiselect.valueField));
4820         }
4821         this.hiddenField.dom.value = values.join(this.delimiter);
4822         this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);
4823     },
4824
4825     getValue : function() {
4826         return this.hiddenField.dom.value;
4827     },
4828
4829     onRowDblClick : function(vw, index, node, e) {
4830         if (vw == this.toMultiselect.view){
4831             this.toFrom();
4832         } else if (vw == this.fromMultiselect.view) {
4833             this.fromTo();
4834         }
4835         return this.fireEvent('rowdblclick', vw, index, node, e);
4836     },
4837
4838     reset: function(){
4839         range = this.toMultiselect.store.getRange();
4840         this.toMultiselect.store.removeAll();
4841         this.fromMultiselect.store.add(range);
4842         var si = this.fromMultiselect.store.sortInfo;
4843         if (si){
4844             this.fromMultiselect.store.sort(si.field, si.direction);
4845         }
4846         this.valueChanged(this.toMultiselect.store);
4847     }
4848 });
4849
4850 Ext.reg('itemselector', Ext.ux.form.ItemSelector);
4851
4852 //backwards compat
4853 Ext.ux.ItemSelector = Ext.ux.form.ItemSelector;
4854 /*!
4855  * Ext JS Library 3.3.0
4856  * Copyright(c) 2006-2010 Ext JS, Inc.
4857  * licensing@extjs.com
4858  * http://www.extjs.com/license
4859  */
4860 Ext.ns('Ext.ux.grid');
4861
4862 Ext.ux.grid.LockingGridView = Ext.extend(Ext.grid.GridView, {
4863     lockText : 'Lock',
4864     unlockText : 'Unlock',
4865     rowBorderWidth : 1,
4866     lockedBorderWidth : 1,
4867
4868     /*
4869      * This option ensures that height between the rows is synchronized
4870      * between the locked and unlocked sides. This option only needs to be used
4871      * when the row heights aren't predictable.
4872      */
4873     syncHeights: false,
4874
4875     initTemplates : function(){
4876         var ts = this.templates || {};
4877
4878         if (!ts.masterTpl) {
4879             ts.masterTpl = new Ext.Template(
4880                 '<div class="x-grid3" hidefocus="true">',
4881                     '<div class="x-grid3-locked">',
4882                         '<div class="x-grid3-header"><div class="x-grid3-header-inner"><div class="x-grid3-header-offset" style="{lstyle}">{lockedHeader}</div></div><div class="x-clear"></div></div>',
4883                         '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{lstyle}">{lockedBody}</div><div class="x-grid3-scroll-spacer"></div></div>',
4884                     '</div>',
4885                     '<div class="x-grid3-viewport x-grid3-unlocked">',
4886                         '<div class="x-grid3-header"><div class="x-grid3-header-inner"><div class="x-grid3-header-offset" style="{ostyle}">{header}</div></div><div class="x-clear"></div></div>',
4887                         '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{bstyle}">{body}</div><a href="#" class="x-grid3-focus" tabIndex="-1"></a></div>',
4888                     '</div>',
4889                     '<div class="x-grid3-resize-marker">&#160;</div>',
4890                     '<div class="x-grid3-resize-proxy">&#160;</div>',
4891                 '</div>'
4892             );
4893         }
4894
4895         this.templates = ts;
4896
4897         Ext.ux.grid.LockingGridView.superclass.initTemplates.call(this);
4898     },
4899
4900     getEditorParent : function(ed){
4901         return this.el.dom;
4902     },
4903
4904     initElements : function(){
4905         var el             = Ext.get(this.grid.getGridEl().dom.firstChild),
4906             lockedWrap     = el.child('div.x-grid3-locked'),
4907             lockedHd       = lockedWrap.child('div.x-grid3-header'),
4908             lockedScroller = lockedWrap.child('div.x-grid3-scroller'),
4909             mainWrap       = el.child('div.x-grid3-viewport'),
4910             mainHd         = mainWrap.child('div.x-grid3-header'),
4911             scroller       = mainWrap.child('div.x-grid3-scroller');
4912             
4913         if (this.grid.hideHeaders) {
4914             lockedHd.setDisplayed(false);
4915             mainHd.setDisplayed(false);
4916         }
4917         
4918         if(this.forceFit){
4919             scroller.setStyle('overflow-x', 'hidden');
4920         }
4921         
4922         Ext.apply(this, {
4923             el      : el,
4924             mainWrap: mainWrap,
4925             mainHd  : mainHd,
4926             innerHd : mainHd.dom.firstChild,
4927             scroller: scroller,
4928             mainBody: scroller.child('div.x-grid3-body'),
4929             focusEl : scroller.child('a'),
4930             resizeMarker: el.child('div.x-grid3-resize-marker'),
4931             resizeProxy : el.child('div.x-grid3-resize-proxy'),
4932             lockedWrap: lockedWrap,
4933             lockedHd: lockedHd,
4934             lockedScroller: lockedScroller,
4935             lockedBody: lockedScroller.child('div.x-grid3-body'),
4936             lockedInnerHd: lockedHd.child('div.x-grid3-header-inner', true)
4937         });
4938         
4939         this.focusEl.swallowEvent('click', true);
4940     },
4941
4942     getLockedRows : function(){
4943         return this.hasRows() ? this.lockedBody.dom.childNodes : [];
4944     },
4945
4946     getLockedRow : function(row){
4947         return this.getLockedRows()[row];
4948     },
4949
4950     getCell : function(row, col){
4951         var lockedLen = this.cm.getLockedCount();
4952         if(col < lockedLen){
4953             return this.getLockedRow(row).getElementsByTagName('td')[col];
4954         }
4955         return Ext.ux.grid.LockingGridView.superclass.getCell.call(this, row, col - lockedLen);
4956     },
4957
4958     getHeaderCell : function(index){
4959         var lockedLen = this.cm.getLockedCount();
4960         if(index < lockedLen){
4961             return this.lockedHd.dom.getElementsByTagName('td')[index];
4962         }
4963         return Ext.ux.grid.LockingGridView.superclass.getHeaderCell.call(this, index - lockedLen);
4964     },
4965
4966     addRowClass : function(row, cls){
4967         var lockedRow = this.getLockedRow(row);
4968         if(lockedRow){
4969             this.fly(lockedRow).addClass(cls);
4970         }
4971         Ext.ux.grid.LockingGridView.superclass.addRowClass.call(this, row, cls);
4972     },
4973
4974     removeRowClass : function(row, cls){
4975         var lockedRow = this.getLockedRow(row);
4976         if(lockedRow){
4977             this.fly(lockedRow).removeClass(cls);
4978         }
4979         Ext.ux.grid.LockingGridView.superclass.removeRowClass.call(this, row, cls);
4980     },
4981
4982     removeRow : function(row) {
4983         Ext.removeNode(this.getLockedRow(row));
4984         Ext.ux.grid.LockingGridView.superclass.removeRow.call(this, row);
4985     },
4986
4987     removeRows : function(firstRow, lastRow){
4988         var lockedBody = this.lockedBody.dom,
4989             rowIndex = firstRow;
4990         for(; rowIndex <= lastRow; rowIndex++){
4991             Ext.removeNode(lockedBody.childNodes[firstRow]);
4992         }
4993         Ext.ux.grid.LockingGridView.superclass.removeRows.call(this, firstRow, lastRow);
4994     },
4995
4996     syncScroll : function(e){
4997         this.lockedScroller.dom.scrollTop = this.scroller.dom.scrollTop;
4998         Ext.ux.grid.LockingGridView.superclass.syncScroll.call(this, e);
4999     },
5000
5001     updateSortIcon : function(col, dir){
5002         var sortClasses = this.sortClasses,
5003             lockedHeaders = this.lockedHd.select('td').removeClass(sortClasses),
5004             headers = this.mainHd.select('td').removeClass(sortClasses),
5005             lockedLen = this.cm.getLockedCount(),
5006             cls = sortClasses[dir == 'DESC' ? 1 : 0];
5007             
5008         if(col < lockedLen){
5009             lockedHeaders.item(col).addClass(cls);
5010         }else{
5011             headers.item(col - lockedLen).addClass(cls);
5012         }
5013     },
5014
5015     updateAllColumnWidths : function(){
5016         var tw = this.getTotalWidth(),
5017             clen = this.cm.getColumnCount(),
5018             lw = this.getLockedWidth(),
5019             llen = this.cm.getLockedCount(),
5020             ws = [], len, i;
5021         this.updateLockedWidth();
5022         for(i = 0; i < clen; i++){
5023             ws[i] = this.getColumnWidth(i);
5024             var hd = this.getHeaderCell(i);
5025             hd.style.width = ws[i];
5026         }
5027         var lns = this.getLockedRows(), ns = this.getRows(), row, trow, j;
5028         for(i = 0, len = ns.length; i < len; i++){
5029             row = lns[i];
5030             row.style.width = lw;
5031             if(row.firstChild){
5032                 row.firstChild.style.width = lw;
5033                 trow = row.firstChild.rows[0];
5034                 for (j = 0; j < llen; j++) {
5035                    trow.childNodes[j].style.width = ws[j];
5036                 }
5037             }
5038             row = ns[i];
5039             row.style.width = tw;
5040             if(row.firstChild){
5041                 row.firstChild.style.width = tw;
5042                 trow = row.firstChild.rows[0];
5043                 for (j = llen; j < clen; j++) {
5044                    trow.childNodes[j - llen].style.width = ws[j];
5045                 }
5046             }
5047         }
5048         this.onAllColumnWidthsUpdated(ws, tw);
5049         this.syncHeaderHeight();
5050     },
5051
5052     updateColumnWidth : function(col, width){
5053         var w = this.getColumnWidth(col),
5054             llen = this.cm.getLockedCount(),
5055             ns, rw, c, row;
5056         this.updateLockedWidth();
5057         if(col < llen){
5058             ns = this.getLockedRows();
5059             rw = this.getLockedWidth();
5060             c = col;
5061         }else{
5062             ns = this.getRows();
5063             rw = this.getTotalWidth();
5064             c = col - llen;
5065         }
5066         var hd = this.getHeaderCell(col);
5067         hd.style.width = w;
5068         for(var i = 0, len = ns.length; i < len; i++){
5069             row = ns[i];
5070             row.style.width = rw;
5071             if(row.firstChild){
5072                 row.firstChild.style.width = rw;
5073                 row.firstChild.rows[0].childNodes[c].style.width = w;
5074             }
5075         }
5076         this.onColumnWidthUpdated(col, w, this.getTotalWidth());
5077         this.syncHeaderHeight();
5078     },
5079
5080     updateColumnHidden : function(col, hidden){
5081         var llen = this.cm.getLockedCount(),
5082             ns, rw, c, row,
5083             display = hidden ? 'none' : '';
5084         this.updateLockedWidth();
5085         if(col < llen){
5086             ns = this.getLockedRows();
5087             rw = this.getLockedWidth();
5088             c = col;
5089         }else{
5090             ns = this.getRows();
5091             rw = this.getTotalWidth();
5092             c = col - llen;
5093         }
5094         var hd = this.getHeaderCell(col);
5095         hd.style.display = display;
5096         for(var i = 0, len = ns.length; i < len; i++){
5097             row = ns[i];
5098             row.style.width = rw;
5099             if(row.firstChild){
5100                 row.firstChild.style.width = rw;
5101                 row.firstChild.rows[0].childNodes[c].style.display = display;
5102             }
5103         }
5104         this.onColumnHiddenUpdated(col, hidden, this.getTotalWidth());
5105         delete this.lastViewWidth;
5106         this.layout();
5107     },
5108
5109     doRender : function(cs, rs, ds, startRow, colCount, stripe){
5110         var ts = this.templates, ct = ts.cell, rt = ts.row, last = colCount-1,
5111             tstyle = 'width:'+this.getTotalWidth()+';',
5112             lstyle = 'width:'+this.getLockedWidth()+';',
5113             buf = [], lbuf = [], cb, lcb, c, p = {}, rp = {}, r;
5114         for(var j = 0, len = rs.length; j < len; j++){
5115             r = rs[j]; cb = []; lcb = [];
5116             var rowIndex = (j+startRow);
5117             for(var i = 0; i < colCount; i++){
5118                 c = cs[i];
5119                 p.id = c.id;
5120                 p.css = (i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '')) +
5121                     (this.cm.config[i].cellCls ? ' ' + this.cm.config[i].cellCls : '');
5122                 p.attr = p.cellAttr = '';
5123                 p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
5124                 p.style = c.style;
5125                 if(Ext.isEmpty(p.value)){
5126                     p.value = '&#160;';
5127                 }
5128                 if(this.markDirty && r.dirty && Ext.isDefined(r.modified[c.name])){
5129                     p.css += ' x-grid3-dirty-cell';
5130                 }
5131                 if(c.locked){
5132                     lcb[lcb.length] = ct.apply(p);
5133                 }else{
5134                     cb[cb.length] = ct.apply(p);
5135                 }
5136             }
5137             var alt = [];
5138             if(stripe && ((rowIndex+1) % 2 === 0)){
5139                 alt[0] = 'x-grid3-row-alt';
5140             }
5141             if(r.dirty){
5142                 alt[1] = ' x-grid3-dirty-row';
5143             }
5144             rp.cols = colCount;
5145             if(this.getRowClass){
5146                 alt[2] = this.getRowClass(r, rowIndex, rp, ds);
5147             }
5148             rp.alt = alt.join(' ');
5149             rp.cells = cb.join('');
5150             rp.tstyle = tstyle;
5151             buf[buf.length] = rt.apply(rp);
5152             rp.cells = lcb.join('');
5153             rp.tstyle = lstyle;
5154             lbuf[lbuf.length] = rt.apply(rp);
5155         }
5156         return [buf.join(''), lbuf.join('')];
5157     },
5158     processRows : function(startRow, skipStripe){
5159         if(!this.ds || this.ds.getCount() < 1){
5160             return;
5161         }
5162         var rows = this.getRows(),
5163             lrows = this.getLockedRows(),
5164             row, lrow;
5165         skipStripe = skipStripe || !this.grid.stripeRows;
5166         startRow = startRow || 0;
5167         for(var i = 0, len = rows.length; i < len; ++i){
5168             row = rows[i];
5169             lrow = lrows[i];
5170             row.rowIndex = i;
5171             lrow.rowIndex = i;
5172             if(!skipStripe){
5173                 row.className = row.className.replace(this.rowClsRe, ' ');
5174                 lrow.className = lrow.className.replace(this.rowClsRe, ' ');
5175                 if ((i + 1) % 2 === 0){
5176                     row.className += ' x-grid3-row-alt';
5177                     lrow.className += ' x-grid3-row-alt';
5178                 }
5179             }
5180             this.syncRowHeights(row, lrow);
5181         }
5182         if(startRow === 0){
5183             Ext.fly(rows[0]).addClass(this.firstRowCls);
5184             Ext.fly(lrows[0]).addClass(this.firstRowCls);
5185         }
5186         Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
5187         Ext.fly(lrows[lrows.length - 1]).addClass(this.lastRowCls);
5188     },
5189     
5190     syncRowHeights: function(row1, row2){
5191         if(this.syncHeights){
5192             var el1 = Ext.get(row1),
5193                 el2 = Ext.get(row2),
5194                 h1 = el1.getHeight(),
5195                 h2 = el2.getHeight();
5196
5197             if(h1 > h2){
5198                 el2.setHeight(h1);
5199             }else if(h2 > h1){
5200                 el1.setHeight(h2);
5201             }
5202         }
5203     },
5204
5205     afterRender : function(){
5206         if(!this.ds || !this.cm){
5207             return;
5208         }
5209         var bd = this.renderRows() || ['&#160;', '&#160;'];
5210         this.mainBody.dom.innerHTML = bd[0];
5211         this.lockedBody.dom.innerHTML = bd[1];
5212         this.processRows(0, true);
5213         if(this.deferEmptyText !== true){
5214             this.applyEmptyText();
5215         }
5216         this.grid.fireEvent('viewready', this.grid);
5217     },
5218
5219     renderUI : function(){        
5220         var templates = this.templates,
5221             header = this.renderHeaders(),
5222             body = templates.body.apply({rows:'&#160;'});
5223
5224         return templates.masterTpl.apply({
5225             body  : body,
5226             header: header[0],
5227             ostyle: 'width:' + this.getOffsetWidth() + ';',
5228             bstyle: 'width:' + this.getTotalWidth()  + ';',
5229             lockedBody: body,
5230             lockedHeader: header[1],
5231             lstyle: 'width:'+this.getLockedWidth()+';'
5232         });
5233     },
5234     
5235     afterRenderUI: function(){
5236         var g = this.grid;
5237         this.initElements();
5238         Ext.fly(this.innerHd).on('click', this.handleHdDown, this);
5239         Ext.fly(this.lockedInnerHd).on('click', this.handleHdDown, this);
5240         this.mainHd.on({
5241             scope: this,
5242             mouseover: this.handleHdOver,
5243             mouseout: this.handleHdOut,
5244             mousemove: this.handleHdMove
5245         });
5246         this.lockedHd.on({
5247             scope: this,
5248             mouseover: this.handleHdOver,
5249             mouseout: this.handleHdOut,
5250             mousemove: this.handleHdMove
5251         });
5252         this.scroller.on('scroll', this.syncScroll,  this);
5253         if(g.enableColumnResize !== false){
5254             this.splitZone = new Ext.grid.GridView.SplitDragZone(g, this.mainHd.dom);
5255             this.splitZone.setOuterHandleElId(Ext.id(this.lockedHd.dom));
5256             this.splitZone.setOuterHandleElId(Ext.id(this.mainHd.dom));
5257         }
5258         if(g.enableColumnMove){
5259             this.columnDrag = new Ext.grid.GridView.ColumnDragZone(g, this.innerHd);
5260             this.columnDrag.setOuterHandleElId(Ext.id(this.lockedInnerHd));
5261             this.columnDrag.setOuterHandleElId(Ext.id(this.innerHd));
5262             this.columnDrop = new Ext.grid.HeaderDropZone(g, this.mainHd.dom);
5263         }
5264         if(g.enableHdMenu !== false){
5265             this.hmenu = new Ext.menu.Menu({id: g.id + '-hctx'});
5266             this.hmenu.add(
5267                 {itemId: 'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'},
5268                 {itemId: 'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
5269             );
5270             if(this.grid.enableColLock !== false){
5271                 this.hmenu.add('-',
5272                     {itemId: 'lock', text: this.lockText, cls: 'xg-hmenu-lock'},
5273                     {itemId: 'unlock', text: this.unlockText, cls: 'xg-hmenu-unlock'}
5274                 );
5275             }
5276             if(g.enableColumnHide !== false){
5277                 this.colMenu = new Ext.menu.Menu({id:g.id + '-hcols-menu'});
5278                 this.colMenu.on({
5279                     scope: this,
5280                     beforeshow: this.beforeColMenuShow,
5281                     itemclick: this.handleHdMenuClick
5282                 });
5283                 this.hmenu.add('-', {
5284                     itemId:'columns',
5285                     hideOnClick: false,
5286                     text: this.columnsText,
5287                     menu: this.colMenu,
5288                     iconCls: 'x-cols-icon'
5289                 });
5290             }
5291             this.hmenu.on('itemclick', this.handleHdMenuClick, this);
5292         }
5293         if(g.trackMouseOver){
5294             this.mainBody.on({
5295                 scope: this,
5296                 mouseover: this.onRowOver,
5297                 mouseout: this.onRowOut
5298             });
5299             this.lockedBody.on({
5300                 scope: this,
5301                 mouseover: this.onRowOver,
5302                 mouseout: this.onRowOut
5303             });
5304         }
5305
5306         if(g.enableDragDrop || g.enableDrag){
5307             this.dragZone = new Ext.grid.GridDragZone(g, {
5308                 ddGroup : g.ddGroup || 'GridDD'
5309             });
5310         }
5311         this.updateHeaderSortState();    
5312     },
5313
5314     layout : function(){
5315         if(!this.mainBody){
5316             return;
5317         }
5318         var g = this.grid;
5319         var c = g.getGridEl();
5320         var csize = c.getSize(true);
5321         var vw = csize.width;
5322         if(!g.hideHeaders && (vw < 20 || csize.height < 20)){
5323             return;
5324         }
5325         this.syncHeaderHeight();
5326         if(g.autoHeight){
5327             this.scroller.dom.style.overflow = 'visible';
5328             this.lockedScroller.dom.style.overflow = 'visible';
5329             if(Ext.isWebKit){
5330                 this.scroller.dom.style.position = 'static';
5331                 this.lockedScroller.dom.style.position = 'static';
5332             }
5333         }else{
5334             this.el.setSize(csize.width, csize.height);
5335             var hdHeight = this.mainHd.getHeight();
5336             var vh = csize.height - (hdHeight);
5337         }
5338         this.updateLockedWidth();
5339         if(this.forceFit){
5340             if(this.lastViewWidth != vw){
5341                 this.fitColumns(false, false);
5342                 this.lastViewWidth = vw;
5343             }
5344         }else {
5345             this.autoExpand();
5346             this.syncHeaderScroll();
5347         }
5348         this.onLayout(vw, vh);
5349     },
5350
5351     getOffsetWidth : function() {
5352         return (this.cm.getTotalWidth() - this.cm.getTotalLockedWidth() + this.getScrollOffset()) + 'px';
5353     },
5354
5355     renderHeaders : function(){
5356         var cm = this.cm,
5357             ts = this.templates,
5358             ct = ts.hcell,
5359             cb = [], lcb = [],
5360             p = {},
5361             len = cm.getColumnCount(),
5362             last = len - 1;
5363         for(var i = 0; i < len; i++){
5364             p.id = cm.getColumnId(i);
5365             p.value = cm.getColumnHeader(i) || '';
5366             p.style = this.getColumnStyle(i, true);
5367             p.tooltip = this.getColumnTooltip(i);
5368             p.css = (i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '')) +
5369                 (cm.config[i].headerCls ? ' ' + cm.config[i].headerCls : '');
5370             if(cm.config[i].align == 'right'){
5371                 p.istyle = 'padding-right:16px';
5372             } else {
5373                 delete p.istyle;
5374             }
5375             if(cm.isLocked(i)){
5376                 lcb[lcb.length] = ct.apply(p);
5377             }else{
5378                 cb[cb.length] = ct.apply(p);
5379             }
5380         }
5381         return [ts.header.apply({cells: cb.join(''), tstyle:'width:'+this.getTotalWidth()+';'}),
5382                 ts.header.apply({cells: lcb.join(''), tstyle:'width:'+this.getLockedWidth()+';'})];
5383     },
5384
5385     updateHeaders : function(){
5386         var hd = this.renderHeaders();
5387         this.innerHd.firstChild.innerHTML = hd[0];
5388         this.innerHd.firstChild.style.width = this.getOffsetWidth();
5389         this.innerHd.firstChild.firstChild.style.width = this.getTotalWidth();
5390         this.lockedInnerHd.firstChild.innerHTML = hd[1];
5391         var lw = this.getLockedWidth();
5392         this.lockedInnerHd.firstChild.style.width = lw;
5393         this.lockedInnerHd.firstChild.firstChild.style.width = lw;
5394     },
5395
5396     getResolvedXY : function(resolved){
5397         if(!resolved){
5398             return null;
5399         }
5400         var c = resolved.cell, r = resolved.row;
5401         return c ? Ext.fly(c).getXY() : [this.scroller.getX(), Ext.fly(r).getY()];
5402     },
5403
5404     syncFocusEl : function(row, col, hscroll){
5405         Ext.ux.grid.LockingGridView.superclass.syncFocusEl.call(this, row, col, col < this.cm.getLockedCount() ? false : hscroll);
5406     },
5407
5408     ensureVisible : function(row, col, hscroll){
5409         return Ext.ux.grid.LockingGridView.superclass.ensureVisible.call(this, row, col, col < this.cm.getLockedCount() ? false : hscroll);
5410     },
5411
5412     insertRows : function(dm, firstRow, lastRow, isUpdate){
5413         var last = dm.getCount() - 1;
5414         if(!isUpdate && firstRow === 0 && lastRow >= last){
5415             this.refresh();
5416         }else{
5417             if(!isUpdate){
5418                 this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
5419             }
5420             var html = this.renderRows(firstRow, lastRow),
5421                 before = this.getRow(firstRow);
5422             if(before){
5423                 if(firstRow === 0){
5424                     this.removeRowClass(0, this.firstRowCls);
5425                 }
5426                 Ext.DomHelper.insertHtml('beforeBegin', before, html[0]);
5427                 before = this.getLockedRow(firstRow);
5428                 Ext.DomHelper.insertHtml('beforeBegin', before, html[1]);
5429             }else{
5430                 this.removeRowClass(last - 1, this.lastRowCls);
5431                 Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html[0]);
5432                 Ext.DomHelper.insertHtml('beforeEnd', this.lockedBody.dom, html[1]);
5433             }
5434             if(!isUpdate){
5435                 this.fireEvent('rowsinserted', this, firstRow, lastRow);
5436                 this.processRows(firstRow);
5437             }else if(firstRow === 0 || firstRow >= last){
5438                 this.addRowClass(firstRow, firstRow === 0 ? this.firstRowCls : this.lastRowCls);
5439             }
5440         }
5441         this.syncFocusEl(firstRow);
5442     },
5443
5444     getColumnStyle : function(col, isHeader){
5445         var style = !isHeader ? this.cm.config[col].cellStyle || this.cm.config[col].css || '' : this.cm.config[col].headerStyle || '';
5446         style += 'width:'+this.getColumnWidth(col)+';';
5447         if(this.cm.isHidden(col)){
5448             style += 'display:none;';
5449         }
5450         var align = this.cm.config[col].align;
5451         if(align){
5452             style += 'text-align:'+align+';';
5453         }
5454         return style;
5455     },
5456
5457     getLockedWidth : function() {
5458         return this.cm.getTotalLockedWidth() + 'px';
5459     },
5460
5461     getTotalWidth : function() {
5462         return (this.cm.getTotalWidth() - this.cm.getTotalLockedWidth()) + 'px';
5463     },
5464
5465     getColumnData : function(){
5466         var cs = [], cm = this.cm, colCount = cm.getColumnCount();
5467         for(var i = 0; i < colCount; i++){
5468             var name = cm.getDataIndex(i);
5469             cs[i] = {
5470                 name : (!Ext.isDefined(name) ? this.ds.fields.get(i).name : name),
5471                 renderer : cm.getRenderer(i),
5472                 id : cm.getColumnId(i),
5473                 style : this.getColumnStyle(i),
5474                 locked : cm.isLocked(i)
5475             };
5476         }
5477         return cs;
5478     },
5479
5480     renderBody : function(){
5481         var markup = this.renderRows() || ['&#160;', '&#160;'];
5482         return [this.templates.body.apply({rows: markup[0]}), this.templates.body.apply({rows: markup[1]})];
5483     },
5484     
5485     refreshRow: function(record){
5486         var store = this.ds, 
5487             colCount = this.cm.getColumnCount(), 
5488             columns = this.getColumnData(), 
5489             last = colCount - 1, 
5490             cls = ['x-grid3-row'], 
5491             rowParams = {
5492                 tstyle: String.format("width: {0};", this.getTotalWidth())
5493             }, 
5494             lockedRowParams = {
5495                 tstyle: String.format("width: {0};", this.getLockedWidth())
5496             }, 
5497             colBuffer = [], 
5498             lockedColBuffer = [], 
5499             cellTpl = this.templates.cell, 
5500             rowIndex, 
5501             row, 
5502             lockedRow, 
5503             column, 
5504             meta, 
5505             css, 
5506             i;
5507         
5508         if (Ext.isNumber(record)) {
5509             rowIndex = record;
5510             record = store.getAt(rowIndex);
5511         } else {
5512             rowIndex = store.indexOf(record);
5513         }
5514         
5515         if (!record || rowIndex < 0) {
5516             return;
5517         }
5518         
5519         for (i = 0; i < colCount; i++) {
5520             column = columns[i];
5521             
5522             if (i == 0) {
5523                 css = 'x-grid3-cell-first';
5524             } else {
5525                 css = (i == last) ? 'x-grid3-cell-last ' : '';
5526             }
5527             
5528             meta = {
5529                 id: column.id,
5530                 style: column.style,
5531                 css: css,
5532                 attr: "",
5533                 cellAttr: ""
5534             };
5535             
5536             meta.value = column.renderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store);
5537             
5538             if (Ext.isEmpty(meta.value)) {
5539                 meta.value = ' ';
5540             }
5541             
5542             if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') {
5543                 meta.css += ' x-grid3-dirty-cell';
5544             }
5545             
5546             if (column.locked) {
5547                 lockedColBuffer[i] = cellTpl.apply(meta);
5548             } else {
5549                 colBuffer[i] = cellTpl.apply(meta);
5550             }
5551         }
5552         
5553         row = this.getRow(rowIndex);
5554         row.className = '';
5555         lockedRow = this.getLockedRow(rowIndex);
5556         lockedRow.className = '';
5557         
5558         if (this.grid.stripeRows && ((rowIndex + 1) % 2 === 0)) {
5559             cls.push('x-grid3-row-alt');
5560         }
5561         
5562         if (this.getRowClass) {
5563             rowParams.cols = colCount;
5564             cls.push(this.getRowClass(record, rowIndex, rowParams, store));
5565         }
5566         
5567         // Unlocked rows
5568         this.fly(row).addClass(cls).setStyle(rowParams.tstyle);
5569         rowParams.cells = colBuffer.join("");
5570         row.innerHTML = this.templates.rowInner.apply(rowParams);
5571         
5572         // Locked rows
5573         this.fly(lockedRow).addClass(cls).setStyle(lockedRowParams.tstyle);
5574         lockedRowParams.cells = lockedColBuffer.join("");
5575         lockedRow.innerHTML = this.templates.rowInner.apply(lockedRowParams);
5576         lockedRow.rowIndex = rowIndex;
5577         this.syncRowHeights(row, lockedRow);  
5578         this.fireEvent('rowupdated', this, rowIndex, record);
5579     },
5580
5581     refresh : function(headersToo){
5582         this.fireEvent('beforerefresh', this);
5583         this.grid.stopEditing(true);
5584         var result = this.renderBody();
5585         this.mainBody.update(result[0]).setWidth(this.getTotalWidth());
5586         this.lockedBody.update(result[1]).setWidth(this.getLockedWidth());
5587         if(headersToo === true){
5588             this.updateHeaders();
5589             this.updateHeaderSortState();
5590         }
5591         this.processRows(0, true);
5592         this.layout();
5593         this.applyEmptyText();
5594         this.fireEvent('refresh', this);
5595     },
5596
5597     onDenyColumnLock : function(){
5598
5599     },
5600
5601     initData : function(ds, cm){
5602         if(this.cm){
5603             this.cm.un('columnlockchange', this.onColumnLock, this);
5604         }
5605         Ext.ux.grid.LockingGridView.superclass.initData.call(this, ds, cm);
5606         if(this.cm){
5607             this.cm.on('columnlockchange', this.onColumnLock, this);
5608         }
5609     },
5610
5611     onColumnLock : function(){
5612         this.refresh(true);
5613     },
5614
5615     handleHdMenuClick : function(item){
5616         var index = this.hdCtxIndex,
5617             cm = this.cm,
5618             id = item.getItemId(),
5619             llen = cm.getLockedCount();
5620         switch(id){
5621             case 'lock':
5622                 if(cm.getColumnCount(true) <= llen + 1){
5623                     this.onDenyColumnLock();
5624                     return undefined;
5625                 }
5626                 cm.setLocked(index, true);
5627                 if(llen != index){
5628                     cm.moveColumn(index, llen);
5629                     this.grid.fireEvent('columnmove', index, llen);
5630                 }
5631             break;
5632             case 'unlock':
5633                 if(llen - 1 != index){
5634                     cm.setLocked(index, false, true);
5635                     cm.moveColumn(index, llen - 1);
5636                     this.grid.fireEvent('columnmove', index, llen - 1);
5637                 }else{
5638                     cm.setLocked(index, false);
5639                 }
5640             break;
5641             default:
5642                 return Ext.ux.grid.LockingGridView.superclass.handleHdMenuClick.call(this, item);
5643         }
5644         return true;
5645     },
5646
5647     handleHdDown : function(e, t){
5648         Ext.ux.grid.LockingGridView.superclass.handleHdDown.call(this, e, t);
5649         if(this.grid.enableColLock !== false){
5650             if(Ext.fly(t).hasClass('x-grid3-hd-btn')){
5651                 var hd = this.findHeaderCell(t),
5652                     index = this.getCellIndex(hd),
5653                     ms = this.hmenu.items, cm = this.cm;
5654                 ms.get('lock').setDisabled(cm.isLocked(index));
5655                 ms.get('unlock').setDisabled(!cm.isLocked(index));
5656             }
5657         }
5658     },
5659
5660     syncHeaderHeight: function(){
5661         var hrow = Ext.fly(this.innerHd).child('tr', true),
5662             lhrow = Ext.fly(this.lockedInnerHd).child('tr', true);
5663             
5664         hrow.style.height = 'auto';
5665         lhrow.style.height = 'auto';
5666         var hd = hrow.offsetHeight,
5667             lhd = lhrow.offsetHeight,
5668             height = Math.max(lhd, hd) + 'px';
5669             
5670         hrow.style.height = height;
5671         lhrow.style.height = height;
5672
5673     },
5674
5675     updateLockedWidth: function(){
5676         var lw = this.cm.getTotalLockedWidth(),
5677             tw = this.cm.getTotalWidth() - lw,
5678             csize = this.grid.getGridEl().getSize(true),
5679             lp = Ext.isBorderBox ? 0 : this.lockedBorderWidth,
5680             rp = Ext.isBorderBox ? 0 : this.rowBorderWidth,
5681             vw = (csize.width - lw - lp - rp) + 'px',
5682             so = this.getScrollOffset();
5683         if(!this.grid.autoHeight){
5684             var vh = (csize.height - this.mainHd.getHeight()) + 'px';
5685             this.lockedScroller.dom.style.height = vh;
5686             this.scroller.dom.style.height = vh;
5687         }
5688         this.lockedWrap.dom.style.width = (lw + rp) + 'px';
5689         this.scroller.dom.style.width = vw;
5690         this.mainWrap.dom.style.left = (lw + lp + rp) + 'px';
5691         if(this.innerHd){
5692             this.lockedInnerHd.firstChild.style.width = lw + 'px';
5693             this.lockedInnerHd.firstChild.firstChild.style.width = lw + 'px';
5694             this.innerHd.style.width = vw;
5695             this.innerHd.firstChild.style.width = (tw + rp + so) + 'px';
5696             this.innerHd.firstChild.firstChild.style.width = tw + 'px';
5697         }
5698         if(this.mainBody){
5699             this.lockedBody.dom.style.width = (lw + rp) + 'px';
5700             this.mainBody.dom.style.width = (tw + rp) + 'px';
5701         }
5702     }
5703 });
5704
5705 Ext.ux.grid.LockingColumnModel = Ext.extend(Ext.grid.ColumnModel, {
5706     /**
5707      * Returns true if the given column index is currently locked
5708      * @param {Number} colIndex The column index
5709      * @return {Boolean} True if the column is locked
5710      */
5711     isLocked : function(colIndex){
5712         return this.config[colIndex].locked === true;
5713     },
5714
5715     /**
5716      * Locks or unlocks a given column
5717      * @param {Number} colIndex The column index
5718      * @param {Boolean} value True to lock, false to unlock
5719      * @param {Boolean} suppressEvent Pass false to cause the columnlockchange event not to fire
5720      */
5721     setLocked : function(colIndex, value, suppressEvent){
5722         if (this.isLocked(colIndex) == value) {
5723             return;
5724         }
5725         this.config[colIndex].locked = value;
5726         if (!suppressEvent) {
5727             this.fireEvent('columnlockchange', this, colIndex, value);
5728         }
5729     },
5730
5731     /**
5732      * Returns the total width of all locked columns
5733      * @return {Number} The width of all locked columns
5734      */
5735     getTotalLockedWidth : function(){
5736         var totalWidth = 0;
5737         for (var i = 0, len = this.config.length; i < len; i++) {
5738             if (this.isLocked(i) && !this.isHidden(i)) {
5739                 totalWidth += this.getColumnWidth(i);
5740             }
5741         }
5742
5743         return totalWidth;
5744     },
5745
5746     /**
5747      * Returns the total number of locked columns
5748      * @return {Number} The number of locked columns
5749      */
5750     getLockedCount : function() {
5751         var len = this.config.length;
5752
5753         for (var i = 0; i < len; i++) {
5754             if (!this.isLocked(i)) {
5755                 return i;
5756             }
5757         }
5758
5759         //if we get to this point all of the columns are locked so we return the total
5760         return len;
5761     },
5762
5763     /**
5764      * Moves a column from one position to another
5765      * @param {Number} oldIndex The current column index
5766      * @param {Number} newIndex The destination column index
5767      */
5768     moveColumn : function(oldIndex, newIndex){
5769         var oldLocked = this.isLocked(oldIndex),
5770             newLocked = this.isLocked(newIndex);
5771
5772         if (oldIndex < newIndex && oldLocked && !newLocked) {
5773             this.setLocked(oldIndex, false, true);
5774         } else if (oldIndex > newIndex && !oldLocked && newLocked) {
5775             this.setLocked(oldIndex, true, true);
5776         }
5777
5778         Ext.ux.grid.LockingColumnModel.superclass.moveColumn.apply(this, arguments);
5779     }
5780 });Ext.ns('Ext.ux.form');
5781
5782 /**
5783  * @class Ext.ux.form.MultiSelect
5784  * @extends Ext.form.Field
5785  * A control that allows selection and form submission of multiple list items.
5786  *
5787  *  @history
5788  *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)
5789  *    2008-06-19 bpm Docs and demo code clean up
5790  *
5791  * @constructor
5792  * Create a new MultiSelect
5793  * @param {Object} config Configuration options
5794  * @xtype multiselect
5795  */
5796 Ext.ux.form.MultiSelect = Ext.extend(Ext.form.Field,  {
5797     /**
5798      * @cfg {String} legend Wraps the object with a fieldset and specified legend.
5799      */
5800     /**
5801      * @cfg {Ext.ListView} view The {@link Ext.ListView} used to render the multiselect list.
5802      */
5803     /**
5804      * @cfg {String/Array} dragGroup The ddgroup name(s) for the MultiSelect DragZone (defaults to undefined).
5805      */
5806     /**
5807      * @cfg {String/Array} dropGroup The ddgroup name(s) for the MultiSelect DropZone (defaults to undefined).
5808      */
5809     /**
5810      * @cfg {Boolean} ddReorder Whether the items in the MultiSelect list are drag/drop reorderable (defaults to false).
5811      */
5812     ddReorder:false,
5813     /**
5814      * @cfg {Object/Array} tbar The top toolbar of the control. This can be a {@link Ext.Toolbar} object, a
5815      * toolbar config, or an array of buttons/button configs to be added to the toolbar.
5816      */
5817     /**
5818      * @cfg {String} appendOnly True if the list should only allow append drops when drag/drop is enabled
5819      * (use for lists which are sorted, defaults to false).
5820      */
5821     appendOnly:false,
5822     /**
5823      * @cfg {Number} width Width in pixels of the control (defaults to 100).
5824      */
5825     width:100,
5826     /**
5827      * @cfg {Number} height Height in pixels of the control (defaults to 100).
5828      */
5829     height:100,
5830     /**
5831      * @cfg {String/Number} displayField Name/Index of the desired display field in the dataset (defaults to 0).
5832      */
5833     displayField:0,
5834     /**
5835      * @cfg {String/Number} valueField Name/Index of the desired value field in the dataset (defaults to 1).
5836      */
5837     valueField:1,
5838     /**
5839      * @cfg {Boolean} allowBlank False to require at least one item in the list to be selected, true to allow no
5840      * selection (defaults to true).
5841      */
5842     allowBlank:true,
5843     /**
5844      * @cfg {Number} minSelections Minimum number of selections allowed (defaults to 0).
5845      */
5846     minSelections:0,
5847     /**
5848      * @cfg {Number} maxSelections Maximum number of selections allowed (defaults to Number.MAX_VALUE).
5849      */
5850     maxSelections:Number.MAX_VALUE,
5851     /**
5852      * @cfg {String} blankText Default text displayed when the control contains no items (defaults to the same value as
5853      * {@link Ext.form.TextField#blankText}.
5854      */
5855     blankText:Ext.form.TextField.prototype.blankText,
5856     /**
5857      * @cfg {String} minSelectionsText Validation message displayed when {@link #minSelections} is not met (defaults to 'Minimum {0}
5858      * item(s) required').  The {0} token will be replaced by the value of {@link #minSelections}.
5859      */
5860     minSelectionsText:'Minimum {0} item(s) required',
5861     /**
5862      * @cfg {String} maxSelectionsText Validation message displayed when {@link #maxSelections} is not met (defaults to 'Maximum {0}
5863      * item(s) allowed').  The {0} token will be replaced by the value of {@link #maxSelections}.
5864      */
5865     maxSelectionsText:'Maximum {0} item(s) allowed',
5866     /**
5867      * @cfg {String} delimiter The string used to delimit between items when set or returned as a string of values
5868      * (defaults to ',').
5869      */
5870     delimiter:',',
5871     /**
5872      * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to <tt>undefined</tt>).
5873      * Acceptable values for this property are:
5874      * <div class="mdetail-params"><ul>
5875      * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
5876      * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.
5877      * <div class="mdetail-params"><ul>
5878      * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
5879      * A 1-dimensional array will automatically be expanded (each array item will be the combo
5880      * {@link #valueField value} and {@link #displayField text})</div></li>
5881      * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">
5882      * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
5883      * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.
5884      * </div></li></ul></div></li></ul></div>
5885      */
5886
5887     // private
5888     defaultAutoCreate : {tag: "div"},
5889
5890     // private
5891     initComponent: function(){
5892         Ext.ux.form.MultiSelect.superclass.initComponent.call(this);
5893
5894         if(Ext.isArray(this.store)){
5895             if (Ext.isArray(this.store[0])){
5896                 this.store = new Ext.data.ArrayStore({
5897                     fields: ['value','text'],
5898                     data: this.store
5899                 });
5900                 this.valueField = 'value';
5901             }else{
5902                 this.store = new Ext.data.ArrayStore({
5903                     fields: ['text'],
5904                     data: this.store,
5905                     expandData: true
5906                 });
5907                 this.valueField = 'text';
5908             }
5909             this.displayField = 'text';
5910         } else {
5911             this.store = Ext.StoreMgr.lookup(this.store);
5912         }
5913
5914         this.addEvents({
5915             'dblclick' : true,
5916             'click' : true,
5917             'change' : true,
5918             'drop' : true
5919         });
5920     },
5921
5922     // private
5923     onRender: function(ct, position){
5924         Ext.ux.form.MultiSelect.superclass.onRender.call(this, ct, position);
5925
5926         var fs = this.fs = new Ext.form.FieldSet({
5927             renderTo: this.el,
5928             title: this.legend,
5929             height: this.height,
5930             width: this.width,
5931             style: "padding:0;",
5932             tbar: this.tbar
5933         });
5934         fs.body.addClass('ux-mselect');
5935
5936         this.view = new Ext.ListView({
5937             multiSelect: true,
5938             store: this.store,
5939             columns: [{ header: 'Value', width: 1, dataIndex: this.displayField }],
5940             hideHeaders: true
5941         });
5942
5943         fs.add(this.view);
5944
5945         this.view.on('click', this.onViewClick, this);
5946         this.view.on('beforeclick', this.onViewBeforeClick, this);
5947         this.view.on('dblclick', this.onViewDblClick, this);
5948
5949         this.hiddenName = this.name || Ext.id();
5950         var hiddenTag = { tag: "input", type: "hidden", value: "", name: this.hiddenName };
5951         this.hiddenField = this.el.createChild(hiddenTag);
5952         this.hiddenField.dom.disabled = this.hiddenName != this.name;
5953         fs.doLayout();
5954     },
5955
5956     // private
5957     afterRender: function(){
5958         Ext.ux.form.MultiSelect.superclass.afterRender.call(this);
5959
5960         if (this.ddReorder && !this.dragGroup && !this.dropGroup){
5961             this.dragGroup = this.dropGroup = 'MultiselectDD-' + Ext.id();
5962         }
5963
5964         if (this.draggable || this.dragGroup){
5965             this.dragZone = new Ext.ux.form.MultiSelect.DragZone(this, {
5966                 ddGroup: this.dragGroup
5967             });
5968         }
5969         if (this.droppable || this.dropGroup){
5970             this.dropZone = new Ext.ux.form.MultiSelect.DropZone(this, {
5971                 ddGroup: this.dropGroup
5972             });
5973         }
5974     },
5975
5976     // private
5977     onViewClick: function(vw, index, node, e) {
5978         this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);
5979         this.hiddenField.dom.value = this.getValue();
5980         this.fireEvent('click', this, e);
5981         this.validate();
5982     },
5983
5984     // private
5985     onViewBeforeClick: function(vw, index, node, e) {
5986         if (this.disabled || this.readOnly) {
5987             return false;
5988         }
5989     },
5990
5991     // private
5992     onViewDblClick : function(vw, index, node, e) {
5993         return this.fireEvent('dblclick', vw, index, node, e);
5994     },
5995
5996     /**
5997      * Returns an array of data values for the selected items in the list. The values will be separated
5998      * by {@link #delimiter}.
5999      * @return {Array} value An array of string data values
6000      */
6001     getValue: function(valueField){
6002         var returnArray = [];
6003         var selectionsArray = this.view.getSelectedIndexes();
6004         if (selectionsArray.length == 0) {return '';}
6005         for (var i=0; i<selectionsArray.length; i++) {
6006             returnArray.push(this.store.getAt(selectionsArray[i]).get((valueField != null) ? valueField : this.valueField));
6007         }
6008         return returnArray.join(this.delimiter);
6009     },
6010
6011     /**
6012      * Sets a delimited string (using {@link #delimiter}) or array of data values into the list.
6013      * @param {String/Array} values The values to set
6014      */
6015     setValue: function(values) {
6016         var index;
6017         var selections = [];
6018         this.view.clearSelections();
6019         this.hiddenField.dom.value = '';
6020
6021         if (!values || (values == '')) { return; }
6022
6023         if (!Ext.isArray(values)) { values = values.split(this.delimiter); }
6024         for (var i=0; i<values.length; i++) {
6025             index = this.view.store.indexOf(this.view.store.query(this.valueField,
6026                 new RegExp('^' + values[i] + '$', "i")).itemAt(0));
6027             selections.push(index);
6028         }
6029         this.view.select(selections);
6030         this.hiddenField.dom.value = this.getValue();
6031         this.validate();
6032     },
6033
6034     // inherit docs
6035     reset : function() {
6036         this.setValue('');
6037     },
6038
6039     // inherit docs
6040     getRawValue: function(valueField) {
6041         var tmp = this.getValue(valueField);
6042         if (tmp.length) {
6043             tmp = tmp.split(this.delimiter);
6044         }
6045         else {
6046             tmp = [];
6047         }
6048         return tmp;
6049     },
6050
6051     // inherit docs
6052     setRawValue: function(values){
6053         setValue(values);
6054     },
6055
6056     // inherit docs
6057     validateValue : function(value){
6058         if (value.length < 1) { // if it has no value
6059              if (this.allowBlank) {
6060                  this.clearInvalid();
6061                  return true;
6062              } else {
6063                  this.markInvalid(this.blankText);
6064                  return false;
6065              }
6066         }
6067         if (value.length < this.minSelections) {
6068             this.markInvalid(String.format(this.minSelectionsText, this.minSelections));
6069             return false;
6070         }
6071         if (value.length > this.maxSelections) {
6072             this.markInvalid(String.format(this.maxSelectionsText, this.maxSelections));
6073             return false;
6074         }
6075         return true;
6076     },
6077
6078     // inherit docs
6079     disable: function(){
6080         this.disabled = true;
6081         this.hiddenField.dom.disabled = true;
6082         this.fs.disable();
6083     },
6084
6085     // inherit docs
6086     enable: function(){
6087         this.disabled = false;
6088         this.hiddenField.dom.disabled = false;
6089         this.fs.enable();
6090     },
6091
6092     // inherit docs
6093     destroy: function(){
6094         Ext.destroy(this.fs, this.dragZone, this.dropZone);
6095         Ext.ux.form.MultiSelect.superclass.destroy.call(this);
6096     }
6097 });
6098
6099
6100 Ext.reg('multiselect', Ext.ux.form.MultiSelect);
6101
6102 //backwards compat
6103 Ext.ux.Multiselect = Ext.ux.form.MultiSelect;
6104
6105
6106 Ext.ux.form.MultiSelect.DragZone = function(ms, config){
6107     this.ms = ms;
6108     this.view = ms.view;
6109     var ddGroup = config.ddGroup || 'MultiselectDD';
6110     var dd;
6111     if (Ext.isArray(ddGroup)){
6112         dd = ddGroup.shift();
6113     } else {
6114         dd = ddGroup;
6115         ddGroup = null;
6116     }
6117     Ext.ux.form.MultiSelect.DragZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });
6118     this.setDraggable(ddGroup);
6119 };
6120
6121 Ext.extend(Ext.ux.form.MultiSelect.DragZone, Ext.dd.DragZone, {
6122     onInitDrag : function(x, y){
6123         var el = Ext.get(this.dragData.ddel.cloneNode(true));
6124         this.proxy.update(el.dom);
6125         el.setWidth(el.child('em').getWidth());
6126         this.onStartDrag(x, y);
6127         return true;
6128     },
6129
6130     // private
6131     collectSelection: function(data) {
6132         data.repairXY = Ext.fly(this.view.getSelectedNodes()[0]).getXY();
6133         var i = 0;
6134         this.view.store.each(function(rec){
6135             if (this.view.isSelected(i)) {
6136                 var n = this.view.getNode(i);
6137                 var dragNode = n.cloneNode(true);
6138                 dragNode.id = Ext.id();
6139                 data.ddel.appendChild(dragNode);
6140                 data.records.push(this.view.store.getAt(i));
6141                 data.viewNodes.push(n);
6142             }
6143             i++;
6144         }, this);
6145     },
6146
6147     // override
6148     onEndDrag: function(data, e) {
6149         var d = Ext.get(this.dragData.ddel);
6150         if (d && d.hasClass("multi-proxy")) {
6151             d.remove();
6152         }
6153     },
6154
6155     // override
6156     getDragData: function(e){
6157         var target = this.view.findItemFromChild(e.getTarget());
6158         if(target) {
6159             if (!this.view.isSelected(target) && !e.ctrlKey && !e.shiftKey) {
6160                 this.view.select(target);
6161                 this.ms.setValue(this.ms.getValue());
6162             }
6163             if (this.view.getSelectionCount() == 0 || e.ctrlKey || e.shiftKey) return false;
6164             var dragData = {
6165                 sourceView: this.view,
6166                 viewNodes: [],
6167                 records: []
6168             };
6169             if (this.view.getSelectionCount() == 1) {
6170                 var i = this.view.getSelectedIndexes()[0];
6171                 var n = this.view.getNode(i);
6172                 dragData.viewNodes.push(dragData.ddel = n);
6173                 dragData.records.push(this.view.store.getAt(i));
6174                 dragData.repairXY = Ext.fly(n).getXY();
6175             } else {
6176                 dragData.ddel = document.createElement('div');
6177                 dragData.ddel.className = 'multi-proxy';
6178                 this.collectSelection(dragData);
6179             }
6180             return dragData;
6181         }
6182         return false;
6183     },
6184
6185     // override the default repairXY.
6186     getRepairXY : function(e){
6187         return this.dragData.repairXY;
6188     },
6189
6190     // private
6191     setDraggable: function(ddGroup){
6192         if (!ddGroup) return;
6193         if (Ext.isArray(ddGroup)) {
6194             Ext.each(ddGroup, this.setDraggable, this);
6195             return;
6196         }
6197         this.addToGroup(ddGroup);
6198     }
6199 });
6200
6201 Ext.ux.form.MultiSelect.DropZone = function(ms, config){
6202     this.ms = ms;
6203     this.view = ms.view;
6204     var ddGroup = config.ddGroup || 'MultiselectDD';
6205     var dd;
6206     if (Ext.isArray(ddGroup)){
6207         dd = ddGroup.shift();
6208     } else {
6209         dd = ddGroup;
6210         ddGroup = null;
6211     }
6212     Ext.ux.form.MultiSelect.DropZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });
6213     this.setDroppable(ddGroup);
6214 };
6215
6216 Ext.extend(Ext.ux.form.MultiSelect.DropZone, Ext.dd.DropZone, {
6217     /**
6218      * Part of the Ext.dd.DropZone interface. If no target node is found, the
6219      * whole Element becomes the target, and this causes the drop gesture to append.
6220      */
6221     getTargetFromEvent : function(e) {
6222         var target = e.getTarget();
6223         return target;
6224     },
6225
6226     // private
6227     getDropPoint : function(e, n, dd){
6228         if (n == this.ms.fs.body.dom) { return "below"; }
6229         var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight;
6230         var c = t + (b - t) / 2;
6231         var y = Ext.lib.Event.getPageY(e);
6232         if(y <= c) {
6233             return "above";
6234         }else{
6235             return "below";
6236         }
6237     },
6238
6239     // private
6240     isValidDropPoint: function(pt, n, data) {
6241         if (!data.viewNodes || (data.viewNodes.length != 1)) {
6242             return true;
6243         }
6244         var d = data.viewNodes[0];
6245         if (d == n) {
6246             return false;
6247         }
6248         if ((pt == "below") && (n.nextSibling == d)) {
6249             return false;
6250         }
6251         if ((pt == "above") && (n.previousSibling == d)) {
6252             return false;
6253         }
6254         return true;
6255     },
6256
6257     // override
6258     onNodeEnter : function(n, dd, e, data){
6259         return false;
6260     },
6261
6262     // override
6263     onNodeOver : function(n, dd, e, data){
6264         var dragElClass = this.dropNotAllowed;
6265         var pt = this.getDropPoint(e, n, dd);
6266         if (this.isValidDropPoint(pt, n, data)) {
6267             if (this.ms.appendOnly) {
6268                 return "x-tree-drop-ok-below";
6269             }
6270
6271             // set the insert point style on the target node
6272             if (pt) {
6273                 var targetElClass;
6274                 if (pt == "above"){
6275                     dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";
6276                     targetElClass = "x-view-drag-insert-above";
6277                 } else {
6278                     dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";
6279                     targetElClass = "x-view-drag-insert-below";
6280                 }
6281                 if (this.lastInsertClass != targetElClass){
6282                     Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass);
6283                     this.lastInsertClass = targetElClass;
6284                 }
6285             }
6286         }
6287         return dragElClass;
6288     },
6289
6290     // private
6291     onNodeOut : function(n, dd, e, data){
6292         this.removeDropIndicators(n);
6293     },
6294
6295     // private
6296     onNodeDrop : function(n, dd, e, data){
6297         if (this.ms.fireEvent("drop", this, n, dd, e, data) === false) {
6298             return false;
6299         }
6300         var pt = this.getDropPoint(e, n, dd);
6301         if (n != this.ms.fs.body.dom)
6302             n = this.view.findItemFromChild(n);
6303
6304         if(this.ms.appendOnly) {
6305             insertAt = this.view.store.getCount();
6306         } else {
6307             insertAt = n == this.ms.fs.body.dom ? this.view.store.getCount() - 1 : this.view.indexOf(n);
6308             if (pt == "below") {
6309                 insertAt++;
6310             }
6311         }
6312
6313         var dir = false;
6314
6315         // Validate if dragging within the same MultiSelect
6316         if (data.sourceView == this.view) {
6317             // If the first element to be inserted below is the target node, remove it
6318             if (pt == "below") {
6319                 if (data.viewNodes[0] == n) {
6320                     data.viewNodes.shift();
6321                 }
6322             } else {  // If the last element to be inserted above is the target node, remove it
6323                 if (data.viewNodes[data.viewNodes.length - 1] == n) {
6324                     data.viewNodes.pop();
6325                 }
6326             }
6327
6328             // Nothing to drop...
6329             if (!data.viewNodes.length) {
6330                 return false;
6331             }
6332
6333             // If we are moving DOWN, then because a store.remove() takes place first,
6334             // the insertAt must be decremented.
6335             if (insertAt > this.view.store.indexOf(data.records[0])) {
6336                 dir = 'down';
6337                 insertAt--;
6338             }
6339         }
6340
6341         for (var i = 0; i < data.records.length; i++) {
6342             var r = data.records[i];
6343             if (data.sourceView) {
6344                 data.sourceView.store.remove(r);
6345             }
6346             this.view.store.insert(dir == 'down' ? insertAt : insertAt++, r);
6347             var si = this.view.store.sortInfo;
6348             if(si){
6349                 this.view.store.sort(si.field, si.direction);
6350             }
6351         }
6352         return true;
6353     },
6354
6355     // private
6356     removeDropIndicators : function(n){
6357         if(n){
6358             Ext.fly(n).removeClass([
6359                 "x-view-drag-insert-above",
6360                 "x-view-drag-insert-left",
6361                 "x-view-drag-insert-right",
6362                 "x-view-drag-insert-below"]);
6363             this.lastInsertClass = "_noclass";
6364         }
6365     },
6366
6367     // private
6368     setDroppable: function(ddGroup){
6369         if (!ddGroup) return;
6370         if (Ext.isArray(ddGroup)) {
6371             Ext.each(ddGroup, this.setDroppable, this);
6372             return;
6373         }
6374         this.addToGroup(ddGroup);
6375     }
6376 });
6377
6378 /* Fix for Opera, which does not seem to include the map function on Array's */
6379 if (!Array.prototype.map) {
6380     Array.prototype.map = function(fun){
6381         var len = this.length;
6382         if (typeof fun != 'function') {
6383             throw new TypeError();
6384         }
6385         var res = new Array(len);
6386         var thisp = arguments[1];
6387         for (var i = 0; i < len; i++) {
6388             if (i in this) {
6389                 res[i] = fun.call(thisp, this[i], i, this);
6390             }
6391         }
6392         return res;
6393     };
6394 }
6395
6396 Ext.ns('Ext.ux.data');
6397
6398 /**
6399  * @class Ext.ux.data.PagingMemoryProxy
6400  * @extends Ext.data.MemoryProxy
6401  * <p>Paging Memory Proxy, allows to use paging grid with in memory dataset</p>
6402  */
6403 Ext.ux.data.PagingMemoryProxy = Ext.extend(Ext.data.MemoryProxy, {
6404     constructor : function(data){
6405         Ext.ux.data.PagingMemoryProxy.superclass.constructor.call(this);
6406         this.data = data;
6407     },
6408     doRequest : function(action, rs, params, reader, callback, scope, options){
6409         params = params ||
6410         {};
6411         var result;
6412         try {
6413             result = reader.readRecords(this.data);
6414         } 
6415         catch (e) {
6416             this.fireEvent('loadexception', this, options, null, e);
6417             callback.call(scope, null, options, false);
6418             return;
6419         }
6420         
6421         // filtering
6422         if (params.filter !== undefined) {
6423             result.records = result.records.filter(function(el){
6424                 if (typeof(el) == 'object') {
6425                     var att = params.filterCol || 0;
6426                     return String(el.data[att]).match(params.filter) ? true : false;
6427                 }
6428                 else {
6429                     return String(el).match(params.filter) ? true : false;
6430                 }
6431             });
6432             result.totalRecords = result.records.length;
6433         }
6434         
6435         // sorting
6436         if (params.sort !== undefined) {
6437             // use integer as params.sort to specify column, since arrays are not named
6438             // params.sort=0; would also match a array without columns
6439             var dir = String(params.dir).toUpperCase() == 'DESC' ? -1 : 1;
6440             var fn = function(v1, v2){
6441                 return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
6442             };
6443             result.records.sort(function(a, b){
6444                 var v = 0;
6445                 if (typeof(a) == 'object') {
6446                     v = fn(a.data[params.sort], b.data[params.sort]) * dir;
6447                 }
6448                 else {
6449                     v = fn(a, b) * dir;
6450                 }
6451                 if (v == 0) {
6452                     v = (a.index < b.index ? -1 : 1);
6453                 }
6454                 return v;
6455             });
6456         }
6457         // paging (use undefined cause start can also be 0 (thus false))
6458         if (params.start !== undefined && params.limit !== undefined) {
6459             result.records = result.records.slice(params.start, params.start + params.limit);
6460         }
6461         callback.call(scope, result, options, true);
6462     }
6463 });
6464
6465 //backwards compat.
6466 Ext.data.PagingMemoryProxy = Ext.ux.data.PagingMemoryProxy;
6467 Ext.ux.PanelResizer = Ext.extend(Ext.util.Observable, {
6468     minHeight: 0,
6469     maxHeight:10000000,
6470
6471     constructor: function(config){
6472         Ext.apply(this, config);
6473         this.events = {};
6474         Ext.ux.PanelResizer.superclass.constructor.call(this, config);
6475     },
6476
6477     init : function(p){
6478         this.panel = p;
6479
6480         if(this.panel.elements.indexOf('footer')==-1){
6481             p.elements += ',footer';
6482         }
6483         p.on('render', this.onRender, this);
6484     },
6485
6486     onRender : function(p){
6487         this.handle = p.footer.createChild({cls:'x-panel-resize'});
6488
6489         this.tracker = new Ext.dd.DragTracker({
6490             onStart: this.onDragStart.createDelegate(this),
6491             onDrag: this.onDrag.createDelegate(this),
6492             onEnd: this.onDragEnd.createDelegate(this),
6493             tolerance: 3,
6494             autoStart: 300
6495         });
6496         this.tracker.initEl(this.handle);
6497         p.on('beforedestroy', this.tracker.destroy, this.tracker);
6498     },
6499
6500         // private
6501     onDragStart: function(e){
6502         this.dragging = true;
6503         this.startHeight = this.panel.el.getHeight();
6504         this.fireEvent('dragstart', this, e);
6505     },
6506
6507         // private
6508     onDrag: function(e){
6509         this.panel.setHeight((this.startHeight-this.tracker.getOffset()[1]).constrain(this.minHeight, this.maxHeight));
6510         this.fireEvent('drag', this, e);
6511     },
6512
6513         // private
6514     onDragEnd: function(e){
6515         this.dragging = false;
6516         this.fireEvent('dragend', this, e);
6517     }
6518 });
6519 Ext.preg('panelresizer', Ext.ux.PanelResizer);Ext.ux.Portal = Ext.extend(Ext.Panel, {
6520     layout : 'column',
6521     autoScroll : true,
6522     cls : 'x-portal',
6523     defaultType : 'portalcolumn',
6524     
6525     initComponent : function(){
6526         Ext.ux.Portal.superclass.initComponent.call(this);
6527         this.addEvents({
6528             validatedrop:true,
6529             beforedragover:true,
6530             dragover:true,
6531             beforedrop:true,
6532             drop:true
6533         });
6534     },
6535
6536     initEvents : function(){
6537         Ext.ux.Portal.superclass.initEvents.call(this);
6538         this.dd = new Ext.ux.Portal.DropZone(this, this.dropConfig);
6539     },
6540     
6541     beforeDestroy : function() {
6542         if(this.dd){
6543             this.dd.unreg();
6544         }
6545         Ext.ux.Portal.superclass.beforeDestroy.call(this);
6546     }
6547 });
6548
6549 Ext.reg('portal', Ext.ux.Portal);
6550
6551 Ext.ux.Portal.DropZone = Ext.extend(Ext.dd.DropTarget, {
6552     
6553     constructor : function(portal, cfg){
6554         this.portal = portal;
6555         Ext.dd.ScrollManager.register(portal.body);
6556         Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg);
6557         portal.body.ddScrollConfig = this.ddScrollConfig;
6558     },
6559     
6560     ddScrollConfig : {
6561         vthresh: 50,
6562         hthresh: -1,
6563         animate: true,
6564         increment: 200
6565     },
6566
6567     createEvent : function(dd, e, data, col, c, pos){
6568         return {
6569             portal: this.portal,
6570             panel: data.panel,
6571             columnIndex: col,
6572             column: c,
6573             position: pos,
6574             data: data,
6575             source: dd,
6576             rawEvent: e,
6577             status: this.dropAllowed
6578         };
6579     },
6580
6581     notifyOver : function(dd, e, data){
6582         var xy = e.getXY(), portal = this.portal, px = dd.proxy;
6583
6584         // case column widths
6585         if(!this.grid){
6586             this.grid = this.getGrid();
6587         }
6588
6589         // handle case scroll where scrollbars appear during drag
6590         var cw = portal.body.dom.clientWidth;
6591         if(!this.lastCW){
6592             this.lastCW = cw;
6593         }else if(this.lastCW != cw){
6594             this.lastCW = cw;
6595             portal.doLayout();
6596             this.grid = this.getGrid();
6597         }
6598
6599         // determine column
6600         var col = 0, xs = this.grid.columnX, cmatch = false;
6601         for(var len = xs.length; col < len; col++){
6602             if(xy[0] < (xs[col].x + xs[col].w)){
6603                 cmatch = true;
6604                 break;
6605             }
6606         }
6607         // no match, fix last index
6608         if(!cmatch){
6609             col--;
6610         }
6611
6612         // find insert position
6613         var p, match = false, pos = 0,
6614             c = portal.items.itemAt(col),
6615             items = c.items.items, overSelf = false;
6616
6617         for(var len = items.length; pos < len; pos++){
6618             p = items[pos];
6619             var h = p.el.getHeight();
6620             if(h === 0){
6621                 overSelf = true;
6622             }
6623             else if((p.el.getY()+(h/2)) > xy[1]){
6624                 match = true;
6625                 break;
6626             }
6627         }
6628
6629         pos = (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0);
6630         var overEvent = this.createEvent(dd, e, data, col, c, pos);
6631
6632         if(portal.fireEvent('validatedrop', overEvent) !== false &&
6633            portal.fireEvent('beforedragover', overEvent) !== false){
6634
6635             // make sure proxy width is fluid
6636             px.getProxy().setWidth('auto');
6637
6638             if(p){
6639                 px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null);
6640             }else{
6641                 px.moveProxy(c.el.dom, null);
6642             }
6643
6644             this.lastPos = {c: c, col: col, p: overSelf || (match && p) ? pos : false};
6645             this.scrollPos = portal.body.getScroll();
6646
6647             portal.fireEvent('dragover', overEvent);
6648
6649             return overEvent.status;
6650         }else{
6651             return overEvent.status;
6652         }
6653
6654     },
6655
6656     notifyOut : function(){
6657         delete this.grid;
6658     },
6659
6660     notifyDrop : function(dd, e, data){
6661         delete this.grid;
6662         if(!this.lastPos){
6663             return;
6664         }
6665         var c = this.lastPos.c, 
6666             col = this.lastPos.col, 
6667             pos = this.lastPos.p,
6668             panel = dd.panel,
6669             dropEvent = this.createEvent(dd, e, data, col, c,
6670                 pos !== false ? pos : c.items.getCount());
6671
6672         if(this.portal.fireEvent('validatedrop', dropEvent) !== false &&
6673            this.portal.fireEvent('beforedrop', dropEvent) !== false){
6674
6675             dd.proxy.getProxy().remove();
6676             panel.el.dom.parentNode.removeChild(dd.panel.el.dom);
6677             
6678             if(pos !== false){
6679                 c.insert(pos, panel);
6680             }else{
6681                 c.add(panel);
6682             }
6683             
6684             c.doLayout();
6685
6686             this.portal.fireEvent('drop', dropEvent);
6687
6688             // scroll position is lost on drop, fix it
6689             var st = this.scrollPos.top;
6690             if(st){
6691                 var d = this.portal.body.dom;
6692                 setTimeout(function(){
6693                     d.scrollTop = st;
6694                 }, 10);
6695             }
6696
6697         }
6698         delete this.lastPos;
6699     },
6700
6701     // internal cache of body and column coords
6702     getGrid : function(){
6703         var box = this.portal.bwrap.getBox();
6704         box.columnX = [];
6705         this.portal.items.each(function(c){
6706              box.columnX.push({x: c.el.getX(), w: c.el.getWidth()});
6707         });
6708         return box;
6709     },
6710
6711     // unregister the dropzone from ScrollManager
6712     unreg: function() {
6713         Ext.dd.ScrollManager.unregister(this.portal.body);
6714         Ext.ux.Portal.DropZone.superclass.unreg.call(this);
6715     }
6716 });
6717 Ext.ux.PortalColumn = Ext.extend(Ext.Container, {
6718     layout : 'anchor',
6719     //autoEl : 'div',//already defined by Ext.Component
6720     defaultType : 'portlet',
6721     cls : 'x-portal-column'
6722 });
6723
6724 Ext.reg('portalcolumn', Ext.ux.PortalColumn);
6725 Ext.ux.Portlet = Ext.extend(Ext.Panel, {
6726     anchor : '100%',
6727     frame : true,
6728     collapsible : true,
6729     draggable : true,
6730     cls : 'x-portlet'
6731 });
6732
6733 Ext.reg('portlet', Ext.ux.Portlet);
6734 /**
6735 * @class Ext.ux.ProgressBarPager
6736 * @extends Object 
6737 * Plugin (ptype = 'tabclosemenu') for displaying a progressbar inside of a paging toolbar instead of plain text
6738
6739 * @ptype progressbarpager 
6740 * @constructor
6741 * Create a new ItemSelector
6742 * @param {Object} config Configuration options
6743 * @xtype itemselector 
6744 */
6745 Ext.ux.ProgressBarPager  = Ext.extend(Object, {
6746         /**
6747         * @cfg {Integer} progBarWidth
6748         * <p>The default progress bar width.  Default is 225.</p>
6749         */
6750         progBarWidth   : 225,
6751         /**
6752         * @cfg {String} defaultText
6753         * <p>The text to display while the store is loading.  Default is 'Loading...'</p>
6754         */
6755         defaultText    : 'Loading...',
6756         /**
6757         * @cfg {Object} defaultAnimCfg 
6758         * <p>A {@link Ext.Fx Ext.Fx} configuration object.  Default is  { duration : 1, easing : 'bounceOut' }.</p>
6759         */
6760         defaultAnimCfg : {
6761                 duration   : 1,
6762                 easing     : 'bounceOut'        
6763         },                                                                                                
6764         constructor : function(config) {
6765                 if (config) {
6766                         Ext.apply(this, config);
6767                 }
6768         },
6769         //public
6770         init : function (parent) {
6771                 
6772                 if(parent.displayInfo){
6773                         this.parent = parent;
6774                         var ind  = parent.items.indexOf(parent.displayItem);
6775                         parent.remove(parent.displayItem, true);
6776                         this.progressBar = new Ext.ProgressBar({
6777                                 text    : this.defaultText,
6778                                 width   : this.progBarWidth,
6779                                 animate :  this.defaultAnimCfg
6780                         });                                     
6781                    
6782                         parent.displayItem = this.progressBar;
6783                         
6784                         parent.add(parent.displayItem); 
6785                         parent.doLayout();
6786                         Ext.apply(parent, this.parentOverrides);                
6787                         
6788                         this.progressBar.on('render', function(pb) {
6789                 pb.mon(pb.getEl().applyStyles('cursor:pointer'), 'click', this.handleProgressBarClick, this);
6790             }, this, {single: true});
6791                                                 
6792                 }
6793                   
6794         },
6795         // private
6796         // This method handles the click for the progress bar
6797         handleProgressBarClick : function(e){
6798                 var parent = this.parent,
6799                     displayItem = parent.displayItem,
6800                     box = this.progressBar.getBox(),
6801                     xy = e.getXY(),
6802                     position = xy[0]-box.x,
6803                     pages = Math.ceil(parent.store.getTotalCount()/parent.pageSize),
6804                     newpage = Math.ceil(position/(displayItem.width/pages));
6805             
6806                 parent.changePage(newpage);
6807         },
6808         
6809         // private, overriddes
6810         parentOverrides  : {
6811                 // private
6812                 // This method updates the information via the progress bar.
6813                 updateInfo : function(){
6814                         if(this.displayItem){
6815                                 var count = this.store.getCount(),
6816                                     pgData = this.getPageData(),
6817                                     pageNum = this.readPage(pgData),
6818                                     msg = count == 0 ?
6819                                         this.emptyMsg :
6820                                         String.format(
6821                                                 this.displayMsg,
6822                                                 this.cursor+1, this.cursor+count, this.store.getTotalCount()
6823                                         );
6824                                         
6825                                 pageNum = pgData.activePage; ;  
6826                                 
6827                                 var pct = pageNum / pgData.pages;       
6828                                 
6829                                 this.displayItem.updateProgress(pct, msg, this.animate || this.defaultAnimConfig);
6830                         }
6831                 }
6832         }
6833 });
6834 Ext.preg('progressbarpager', Ext.ux.ProgressBarPager);
6835
6836 Ext.ns('Ext.ux.grid');
6837
6838 /**
6839  * @class Ext.ux.grid.RowEditor
6840  * @extends Ext.Panel
6841  * Plugin (ptype = 'roweditor') that adds the ability to rapidly edit full rows in a grid.
6842  * A validation mode may be enabled which uses AnchorTips to notify the user of all
6843  * validation errors at once.
6844  *
6845  * @ptype roweditor
6846  */
6847 Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
6848     floating: true,
6849     shadow: false,
6850     layout: 'hbox',
6851     cls: 'x-small-editor',
6852     buttonAlign: 'center',
6853     baseCls: 'x-row-editor',
6854     elements: 'header,footer,body',
6855     frameWidth: 5,
6856     buttonPad: 3,
6857     clicksToEdit: 'auto',
6858     monitorValid: true,
6859     focusDelay: 250,
6860     errorSummary: true,
6861
6862     saveText: 'Save',
6863     cancelText: 'Cancel',
6864     commitChangesText: 'You need to commit or cancel your changes',
6865     errorText: 'Errors',
6866
6867     defaults: {
6868         normalWidth: true
6869     },
6870
6871     initComponent: function(){
6872         Ext.ux.grid.RowEditor.superclass.initComponent.call(this);
6873         this.addEvents(
6874             /**
6875              * @event beforeedit
6876              * Fired before the row editor is activated.
6877              * If the listener returns <tt>false</tt> the editor will not be activated.
6878              * @param {Ext.ux.grid.RowEditor} roweditor This object
6879              * @param {Number} rowIndex The rowIndex of the row just edited
6880              */
6881             'beforeedit',
6882             /**
6883              * @event canceledit
6884              * Fired when the editor is cancelled.
6885              * @param {Ext.ux.grid.RowEditor} roweditor This object
6886              * @param {Boolean} forced True if the cancel button is pressed, false is the editor was invalid.
6887              */
6888             'canceledit',
6889             /**
6890              * @event validateedit
6891              * Fired after a row is edited and passes validation.
6892              * If the listener returns <tt>false</tt> changes to the record will not be set.
6893              * @param {Ext.ux.grid.RowEditor} roweditor This object
6894              * @param {Object} changes Object with changes made to the record.
6895              * @param {Ext.data.Record} r The Record that was edited.
6896              * @param {Number} rowIndex The rowIndex of the row just edited
6897              */
6898             'validateedit',
6899             /**
6900              * @event afteredit
6901              * Fired after a row is edited and passes validation.  This event is fired
6902              * after the store's update event is fired with this edit.
6903              * @param {Ext.ux.grid.RowEditor} roweditor This object
6904              * @param {Object} changes Object with changes made to the record.
6905              * @param {Ext.data.Record} r The Record that was edited.
6906              * @param {Number} rowIndex The rowIndex of the row just edited
6907              */
6908             'afteredit'
6909         );
6910     },
6911
6912     init: function(grid){
6913         this.grid = grid;
6914         this.ownerCt = grid;
6915         if(this.clicksToEdit === 2){
6916             grid.on('rowdblclick', this.onRowDblClick, this);
6917         }else{
6918             grid.on('rowclick', this.onRowClick, this);
6919             if(Ext.isIE){
6920                 grid.on('rowdblclick', this.onRowDblClick, this);
6921             }
6922         }
6923
6924         // stopEditing without saving when a record is removed from Store.
6925         grid.getStore().on('remove', function() {
6926             this.stopEditing(false);
6927         },this);
6928
6929         grid.on({
6930             scope: this,
6931             keydown: this.onGridKey,
6932             columnresize: this.verifyLayout,
6933             columnmove: this.refreshFields,
6934             reconfigure: this.refreshFields,
6935             beforedestroy : this.beforedestroy,
6936             destroy : this.destroy,
6937             bodyscroll: {
6938                 buffer: 250,
6939                 fn: this.positionButtons
6940             }
6941         });
6942         grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {delay:1});
6943         grid.getView().on('refresh', this.stopEditing.createDelegate(this, []));
6944     },
6945
6946     beforedestroy: function() {
6947         this.stopMonitoring();
6948         this.grid.getStore().un('remove', this.onStoreRemove, this);
6949         this.stopEditing(false);
6950         Ext.destroy(this.btns, this.tooltip);
6951     },
6952
6953     refreshFields: function(){
6954         this.initFields();
6955         this.verifyLayout();
6956     },
6957
6958     isDirty: function(){
6959         var dirty;
6960         this.items.each(function(f){
6961             if(String(this.values[f.id]) !== String(f.getValue())){
6962                 dirty = true;
6963                 return false;
6964             }
6965         }, this);
6966         return dirty;
6967     },
6968
6969     startEditing: function(rowIndex, doFocus){
6970         if(this.editing && this.isDirty()){
6971             this.showTooltip(this.commitChangesText);
6972             return;
6973         }
6974         if(Ext.isObject(rowIndex)){
6975             rowIndex = this.grid.getStore().indexOf(rowIndex);
6976         }
6977         if(this.fireEvent('beforeedit', this, rowIndex) !== false){
6978             this.editing = true;
6979             var g = this.grid, view = g.getView(),
6980                 row = view.getRow(rowIndex),
6981                 record = g.store.getAt(rowIndex);
6982
6983             this.record = record;
6984             this.rowIndex = rowIndex;
6985             this.values = {};
6986             if(!this.rendered){
6987                 this.render(view.getEditorParent());
6988             }
6989             var w = Ext.fly(row).getWidth();
6990             this.setSize(w);
6991             if(!this.initialized){
6992                 this.initFields();
6993             }
6994             var cm = g.getColumnModel(), fields = this.items.items, f, val;
6995             for(var i = 0, len = cm.getColumnCount(); i < len; i++){
6996                 val = this.preEditValue(record, cm.getDataIndex(i));
6997                 f = fields[i];
6998                 f.setValue(val);
6999                 this.values[f.id] = Ext.isEmpty(val) ? '' : val;
7000             }
7001             this.verifyLayout(true);
7002             if(!this.isVisible()){
7003                 this.setPagePosition(Ext.fly(row).getXY());
7004             } else{
7005                 this.el.setXY(Ext.fly(row).getXY(), {duration:0.15});
7006             }
7007             if(!this.isVisible()){
7008                 this.show().doLayout();
7009             }
7010             if(doFocus !== false){
7011                 this.doFocus.defer(this.focusDelay, this);
7012             }
7013         }
7014     },
7015
7016     stopEditing : function(saveChanges){
7017         this.editing = false;
7018         if(!this.isVisible()){
7019             return;
7020         }
7021         if(saveChanges === false || !this.isValid()){
7022             this.hide();
7023             this.fireEvent('canceledit', this, saveChanges === false);
7024             return;
7025         }
7026         var changes = {},
7027             r = this.record,
7028             hasChange = false,
7029             cm = this.grid.colModel,
7030             fields = this.items.items;
7031         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
7032             if(!cm.isHidden(i)){
7033                 var dindex = cm.getDataIndex(i);
7034                 if(!Ext.isEmpty(dindex)){
7035                     var oldValue = r.data[dindex],
7036                         value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
7037                     if(String(oldValue) !== String(value)){
7038                         changes[dindex] = value;
7039                         hasChange = true;
7040                     }
7041                 }
7042             }
7043         }
7044         if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){
7045             r.beginEdit();
7046             Ext.iterate(changes, function(name, value){
7047                 r.set(name, value);
7048             });
7049             r.endEdit();
7050             this.fireEvent('afteredit', this, changes, r, this.rowIndex);
7051         }
7052         this.hide();
7053     },
7054
7055     verifyLayout: function(force){
7056         if(this.el && (this.isVisible() || force === true)){
7057             var row = this.grid.getView().getRow(this.rowIndex);
7058             this.setSize(Ext.fly(row).getWidth(), Ext.isIE ? Ext.fly(row).getHeight() + 9 : undefined);
7059             var cm = this.grid.colModel, fields = this.items.items;
7060             for(var i = 0, len = cm.getColumnCount(); i < len; i++){
7061                 if(!cm.isHidden(i)){
7062                     var adjust = 0;
7063                     if(i === (len - 1)){
7064                         adjust += 3; // outer padding
7065                     } else{
7066                         adjust += 1;
7067                     }
7068                     fields[i].show();
7069                     fields[i].setWidth(cm.getColumnWidth(i) - adjust);
7070                 } else{
7071                     fields[i].hide();
7072                 }
7073             }
7074             this.doLayout();
7075             this.positionButtons();
7076         }
7077     },
7078
7079     slideHide : function(){
7080         this.hide();
7081     },
7082
7083     initFields: function(){
7084         var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
7085         this.removeAll(false);
7086         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
7087             var c = cm.getColumnAt(i),
7088                 ed = c.getEditor();
7089             if(!ed){
7090                 ed = c.displayEditor || new Ext.form.DisplayField();
7091             }
7092             if(i == 0){
7093                 ed.margins = pm('0 1 2 1');
7094             } else if(i == len - 1){
7095                 ed.margins = pm('0 0 2 1');
7096             } else{
7097                 if (Ext.isIE) {
7098                     ed.margins = pm('0 0 2 0');
7099                 }
7100                 else {
7101                     ed.margins = pm('0 1 2 0');
7102                 }
7103             }
7104             ed.setWidth(cm.getColumnWidth(i));
7105             ed.column = c;
7106             if(ed.ownerCt !== this){
7107                 ed.on('focus', this.ensureVisible, this);
7108                 ed.on('specialkey', this.onKey, this);
7109             }
7110             this.insert(i, ed);
7111         }
7112         this.initialized = true;
7113     },
7114
7115     onKey: function(f, e){
7116         if(e.getKey() === e.ENTER){
7117             this.stopEditing(true);
7118             e.stopPropagation();
7119         }
7120     },
7121
7122     onGridKey: function(e){
7123         if(e.getKey() === e.ENTER && !this.isVisible()){
7124             var r = this.grid.getSelectionModel().getSelected();
7125             if(r){
7126                 var index = this.grid.store.indexOf(r);
7127                 this.startEditing(index);
7128                 e.stopPropagation();
7129             }
7130         }
7131     },
7132
7133     ensureVisible: function(editor){
7134         if(this.isVisible()){
7135              this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
7136         }
7137     },
7138
7139     onRowClick: function(g, rowIndex, e){
7140         if(this.clicksToEdit == 'auto'){
7141             var li = this.lastClickIndex;
7142             this.lastClickIndex = rowIndex;
7143             if(li != rowIndex && !this.isVisible()){
7144                 return;
7145             }
7146         }
7147         this.startEditing(rowIndex, false);
7148         this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
7149     },
7150
7151     onRowDblClick: function(g, rowIndex, e){
7152         this.startEditing(rowIndex, false);
7153         this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
7154     },
7155
7156     onRender: function(){
7157         Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments);
7158         this.el.swallowEvent(['keydown', 'keyup', 'keypress']);
7159         this.btns = new Ext.Panel({
7160             baseCls: 'x-plain',
7161             cls: 'x-btns',
7162             elements:'body',
7163             layout: 'table',
7164             width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE
7165             items: [{
7166                 ref: 'saveBtn',
7167                 itemId: 'saveBtn',
7168                 xtype: 'button',
7169                 text: this.saveText,
7170                 width: this.minButtonWidth,
7171                 handler: this.stopEditing.createDelegate(this, [true])
7172             }, {
7173                 xtype: 'button',
7174                 text: this.cancelText,
7175                 width: this.minButtonWidth,
7176                 handler: this.stopEditing.createDelegate(this, [false])
7177             }]
7178         });
7179         this.btns.render(this.bwrap);
7180     },
7181
7182     afterRender: function(){
7183         Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
7184         this.positionButtons();
7185         if(this.monitorValid){
7186             this.startMonitoring();
7187         }
7188     },
7189
7190     onShow: function(){
7191         if(this.monitorValid){
7192             this.startMonitoring();
7193         }
7194         Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments);
7195     },
7196
7197     onHide: function(){
7198         Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments);
7199         this.stopMonitoring();
7200         this.grid.getView().focusRow(this.rowIndex);
7201     },
7202
7203     positionButtons: function(){
7204         if(this.btns){
7205             var g = this.grid,
7206                 h = this.el.dom.clientHeight,
7207                 view = g.getView(),
7208                 scroll = view.scroller.dom.scrollLeft,
7209                 bw = this.btns.getWidth(),
7210                 width = Math.min(g.getWidth(), g.getColumnModel().getTotalWidth());
7211
7212             this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2});
7213         }
7214     },
7215
7216     // private
7217     preEditValue : function(r, field){
7218         var value = r.data[field];
7219         return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
7220     },
7221
7222     // private
7223     postEditValue : function(value, originalValue, r, field){
7224         return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
7225     },
7226
7227     doFocus: function(pt){
7228         if(this.isVisible()){
7229             var index = 0,
7230                 cm = this.grid.getColumnModel(),
7231                 c;
7232             if(pt){
7233                 index = this.getTargetColumnIndex(pt);
7234             }
7235             for(var i = index||0, len = cm.getColumnCount(); i < len; i++){
7236                 c = cm.getColumnAt(i);
7237                 if(!c.hidden && c.getEditor()){
7238                     c.getEditor().focus();
7239                     break;
7240                 }
7241             }
7242         }
7243     },
7244
7245     getTargetColumnIndex: function(pt){
7246         var grid = this.grid,
7247             v = grid.view,
7248             x = pt.left,
7249             cms = grid.colModel.config,
7250             i = 0,
7251             match = false;
7252         for(var len = cms.length, c; c = cms[i]; i++){
7253             if(!c.hidden){
7254                 if(Ext.fly(v.getHeaderCell(i)).getRegion().right >= x){
7255                     match = i;
7256                     break;
7257                 }
7258             }
7259         }
7260         return match;
7261     },
7262
7263     startMonitoring : function(){
7264         if(!this.bound && this.monitorValid){
7265             this.bound = true;
7266             Ext.TaskMgr.start({
7267                 run : this.bindHandler,
7268                 interval : this.monitorPoll || 200,
7269                 scope: this
7270             });
7271         }
7272     },
7273
7274     stopMonitoring : function(){
7275         this.bound = false;
7276         if(this.tooltip){
7277             this.tooltip.hide();
7278         }
7279     },
7280
7281     isValid: function(){
7282         var valid = true;
7283         this.items.each(function(f){
7284             if(!f.isValid(true)){
7285                 valid = false;
7286                 return false;
7287             }
7288         });
7289         return valid;
7290     },
7291
7292     // private
7293     bindHandler : function(){
7294         if(!this.bound){
7295             return false; // stops binding
7296         }
7297         var valid = this.isValid();
7298         if(!valid && this.errorSummary){
7299             this.showTooltip(this.getErrorText().join(''));
7300         }
7301         this.btns.saveBtn.setDisabled(!valid);
7302         this.fireEvent('validation', this, valid);
7303     },
7304
7305     lastVisibleColumn : function() {
7306         var i = this.items.getCount() - 1,
7307             c;
7308         for(; i >= 0; i--) {
7309             c = this.items.items[i];
7310             if (!c.hidden) {
7311                 return c;
7312             }
7313         }
7314     },
7315
7316     showTooltip: function(msg){
7317         var t = this.tooltip;
7318         if(!t){
7319             t = this.tooltip = new Ext.ToolTip({
7320                 maxWidth: 600,
7321                 cls: 'errorTip',
7322                 width: 300,
7323                 title: this.errorText,
7324                 autoHide: false,
7325                 anchor: 'left',
7326                 anchorToTarget: true,
7327                 mouseOffset: [40,0]
7328             });
7329         }
7330         var v = this.grid.getView(),
7331             top = parseInt(this.el.dom.style.top, 10),
7332             scroll = v.scroller.dom.scrollTop,
7333             h = this.el.getHeight();
7334
7335         if(top + h >= scroll){
7336             t.initTarget(this.lastVisibleColumn().getEl());
7337             if(!t.rendered){
7338                 t.show();
7339                 t.hide();
7340             }
7341             t.body.update(msg);
7342             t.doAutoWidth(20);
7343             t.show();
7344         }else if(t.rendered){
7345             t.hide();
7346         }
7347     },
7348
7349     getErrorText: function(){
7350         var data = ['<ul>'];
7351         this.items.each(function(f){
7352             if(!f.isValid(true)){
7353                 data.push('<li>', f.getActiveError(), '</li>');
7354             }
7355         });
7356         data.push('</ul>');
7357         return data;
7358     }
7359 });
7360 Ext.preg('roweditor', Ext.ux.grid.RowEditor);
7361 Ext.ns('Ext.ux.grid');
7362
7363 /**
7364  * @class Ext.ux.grid.RowExpander
7365  * @extends Ext.util.Observable
7366  * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables
7367  * a second row body which expands/contracts.  The expand/contract behavior is configurable to react
7368  * on clicking of the column, double click of the row, and/or hitting enter while a row is selected.
7369  *
7370  * @ptype rowexpander
7371  */
7372 Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, {
7373     /**
7374      * @cfg {Boolean} expandOnEnter
7375      * <tt>true</tt> to toggle selected row(s) between expanded/collapsed when the enter
7376      * key is pressed (defaults to <tt>true</tt>).
7377      */
7378     expandOnEnter : true,
7379     /**
7380      * @cfg {Boolean} expandOnDblClick
7381      * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked
7382      * (defaults to <tt>true</tt>).
7383      */
7384     expandOnDblClick : true,
7385
7386     header : '',
7387     width : 20,
7388     sortable : false,
7389     fixed : true,
7390     hideable: false,
7391     menuDisabled : true,
7392     dataIndex : '',
7393     id : 'expander',
7394     lazyRender : true,
7395     enableCaching : true,
7396
7397     constructor: function(config){
7398         Ext.apply(this, config);
7399
7400         this.addEvents({
7401             /**
7402              * @event beforeexpand
7403              * Fires before the row expands. Have the listener return false to prevent the row from expanding.
7404              * @param {Object} this RowExpander object.
7405              * @param {Object} Ext.data.Record Record for the selected row.
7406              * @param {Object} body body element for the secondary row.
7407              * @param {Number} rowIndex The current row index.
7408              */
7409             beforeexpand: true,
7410             /**
7411              * @event expand
7412              * Fires after the row expands.
7413              * @param {Object} this RowExpander object.
7414              * @param {Object} Ext.data.Record Record for the selected row.
7415              * @param {Object} body body element for the secondary row.
7416              * @param {Number} rowIndex The current row index.
7417              */
7418             expand: true,
7419             /**
7420              * @event beforecollapse
7421              * Fires before the row collapses. Have the listener return false to prevent the row from collapsing.
7422              * @param {Object} this RowExpander object.
7423              * @param {Object} Ext.data.Record Record for the selected row.
7424              * @param {Object} body body element for the secondary row.
7425              * @param {Number} rowIndex The current row index.
7426              */
7427             beforecollapse: true,
7428             /**
7429              * @event collapse
7430              * Fires after the row collapses.
7431              * @param {Object} this RowExpander object.
7432              * @param {Object} Ext.data.Record Record for the selected row.
7433              * @param {Object} body body element for the secondary row.
7434              * @param {Number} rowIndex The current row index.
7435              */
7436             collapse: true
7437         });
7438
7439         Ext.ux.grid.RowExpander.superclass.constructor.call(this);
7440
7441         if(this.tpl){
7442             if(typeof this.tpl == 'string'){
7443                 this.tpl = new Ext.Template(this.tpl);
7444             }
7445             this.tpl.compile();
7446         }
7447
7448         this.state = {};
7449         this.bodyContent = {};
7450     },
7451
7452     getRowClass : function(record, rowIndex, p, ds){
7453         p.cols = p.cols-1;
7454         var content = this.bodyContent[record.id];
7455         if(!content && !this.lazyRender){
7456             content = this.getBodyContent(record, rowIndex);
7457         }
7458         if(content){
7459             p.body = content;
7460         }
7461         return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';
7462     },
7463
7464     init : function(grid){
7465         this.grid = grid;
7466
7467         var view = grid.getView();
7468         view.getRowClass = this.getRowClass.createDelegate(this);
7469
7470         view.enableRowBody = true;
7471
7472
7473         grid.on('render', this.onRender, this);
7474         grid.on('destroy', this.onDestroy, this);
7475     },
7476
7477     // @private
7478     onRender: function() {
7479         var grid = this.grid;
7480         var mainBody = grid.getView().mainBody;
7481         mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'});
7482         if (this.expandOnEnter) {
7483             this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), {
7484                 'enter' : this.onEnter,
7485                 scope: this
7486             });
7487         }
7488         if (this.expandOnDblClick) {
7489             grid.on('rowdblclick', this.onRowDblClick, this);
7490         }
7491     },
7492     
7493     // @private    
7494     onDestroy: function() {
7495         if(this.keyNav){
7496             this.keyNav.disable();
7497             delete this.keyNav;
7498         }
7499         /*
7500          * A majority of the time, the plugin will be destroyed along with the grid,
7501          * which means the mainBody won't be available. On the off chance that the plugin
7502          * isn't destroyed with the grid, take care of removing the listener.
7503          */
7504         var mainBody = this.grid.getView().mainBody;
7505         if(mainBody){
7506             mainBody.un('mousedown', this.onMouseDown, this);
7507         }
7508     },
7509     // @private
7510     onRowDblClick: function(grid, rowIdx, e) {
7511         this.toggleRow(rowIdx);
7512     },
7513
7514     onEnter: function(e) {
7515         var g = this.grid;
7516         var sm = g.getSelectionModel();
7517         var sels = sm.getSelections();
7518         for (var i = 0, len = sels.length; i < len; i++) {
7519             var rowIdx = g.getStore().indexOf(sels[i]);
7520             this.toggleRow(rowIdx);
7521         }
7522     },
7523
7524     getBodyContent : function(record, index){
7525         if(!this.enableCaching){
7526             return this.tpl.apply(record.data);
7527         }
7528         var content = this.bodyContent[record.id];
7529         if(!content){
7530             content = this.tpl.apply(record.data);
7531             this.bodyContent[record.id] = content;
7532         }
7533         return content;
7534     },
7535
7536     onMouseDown : function(e, t){
7537         e.stopEvent();
7538         var row = e.getTarget('.x-grid3-row');
7539         this.toggleRow(row);
7540     },
7541
7542     renderer : function(v, p, record){
7543         p.cellAttr = 'rowspan="2"';
7544         return '<div class="x-grid3-row-expander">&#160;</div>';
7545     },
7546
7547     beforeExpand : function(record, body, rowIndex){
7548         if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){
7549             if(this.tpl && this.lazyRender){
7550                 body.innerHTML = this.getBodyContent(record, rowIndex);
7551             }
7552             return true;
7553         }else{
7554             return false;
7555         }
7556     },
7557
7558     toggleRow : function(row){
7559         if(typeof row == 'number'){
7560             row = this.grid.view.getRow(row);
7561         }
7562         this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row);
7563     },
7564
7565     expandRow : function(row){
7566         if(typeof row == 'number'){
7567             row = this.grid.view.getRow(row);
7568         }
7569         var record = this.grid.store.getAt(row.rowIndex);
7570         var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
7571         if(this.beforeExpand(record, body, row.rowIndex)){
7572             this.state[record.id] = true;
7573             Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');
7574             this.fireEvent('expand', this, record, body, row.rowIndex);
7575         }
7576     },
7577
7578     collapseRow : function(row){
7579         if(typeof row == 'number'){
7580             row = this.grid.view.getRow(row);
7581         }
7582         var record = this.grid.store.getAt(row.rowIndex);
7583         var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);
7584         if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){
7585             this.state[record.id] = false;
7586             Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');
7587             this.fireEvent('collapse', this, record, body, row.rowIndex);
7588         }
7589     }
7590 });
7591
7592 Ext.preg('rowexpander', Ext.ux.grid.RowExpander);
7593
7594 //backwards compat
7595 Ext.grid.RowExpander = Ext.ux.grid.RowExpander;// We are adding these custom layouts to a namespace that does not
7596 // exist by default in Ext, so we have to add the namespace first:
7597 Ext.ns('Ext.ux.layout');
7598
7599 /**
7600  * @class Ext.ux.layout.RowLayout
7601  * @extends Ext.layout.ContainerLayout
7602  * <p>This is the layout style of choice for creating structural layouts in a multi-row format where the height of
7603  * each row can be specified as a percentage or fixed height.  Row widths can also be fixed, percentage or auto.
7604  * This class is intended to be extended or created via the layout:'ux.row' {@link Ext.Container#layout} config,
7605  * and should generally not need to be created directly via the new keyword.</p>
7606  * <p>RowLayout does not have any direct config options (other than inherited ones), but it does support a
7607  * specific config property of <b><tt>rowHeight</tt></b> that can be included in the config of any panel added to it.  The
7608  * layout will use the rowHeight (if present) or height of each panel during layout to determine how to size each panel.
7609  * If height or rowHeight is not specified for a given panel, its height will default to the panel's height (or auto).</p>
7610  * <p>The height property is always evaluated as pixels, and must be a number greater than or equal to 1.
7611  * The rowHeight property is always evaluated as a percentage, and must be a decimal value greater than 0 and
7612  * less than 1 (e.g., .25).</p>
7613  * <p>The basic rules for specifying row heights are pretty simple.  The logic makes two passes through the
7614  * set of contained panels.  During the first layout pass, all panels that either have a fixed height or none
7615  * specified (auto) are skipped, but their heights are subtracted from the overall container height.  During the second
7616  * pass, all panels with rowHeights are assigned pixel heights in proportion to their percentages based on
7617  * the total <b>remaining</b> container height.  In other words, percentage height panels are designed to fill the space
7618  * left over by all the fixed-height and/or auto-height panels.  Because of this, while you can specify any number of rows
7619  * with different percentages, the rowHeights must always add up to 1 (or 100%) when added together, otherwise your
7620  * layout may not render as expected.  Example usage:</p>
7621  * <pre><code>
7622 // All rows are percentages -- they must add up to 1
7623 var p = new Ext.Panel({
7624     title: 'Row Layout - Percentage Only',
7625     layout:'ux.row',
7626     items: [{
7627         title: 'Row 1',
7628         rowHeight: .25
7629     },{
7630         title: 'Row 2',
7631         rowHeight: .6
7632     },{
7633         title: 'Row 3',
7634         rowHeight: .15
7635     }]
7636 });
7637
7638 // Mix of height and rowHeight -- all rowHeight values must add
7639 // up to 1. The first row will take up exactly 120px, and the last two
7640 // rows will fill the remaining container height.
7641 var p = new Ext.Panel({
7642     title: 'Row Layout - Mixed',
7643     layout:'ux.row',
7644     items: [{
7645         title: 'Row 1',
7646         height: 120,
7647         // standard panel widths are still supported too:
7648         width: '50%' // or 200
7649     },{
7650         title: 'Row 2',
7651         rowHeight: .8,
7652         width: 300
7653     },{
7654         title: 'Row 3',
7655         rowHeight: .2
7656     }]
7657 });
7658 </code></pre>
7659  */
7660 Ext.ux.layout.RowLayout = Ext.extend(Ext.layout.ContainerLayout, {
7661     // private
7662     monitorResize:true,
7663
7664     type: 'row',
7665
7666     // private
7667     allowContainerRemove: false,
7668
7669     // private
7670     isValidParent : function(c, target){
7671         return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
7672     },
7673
7674     getLayoutTargetSize : function() {
7675         var target = this.container.getLayoutTarget(), ret;
7676         if (target) {
7677             ret = target.getViewSize();
7678
7679             // IE in strict mode will return a height of 0 on the 1st pass of getViewSize.
7680             // Use getStyleSize to verify the 0 height, the adjustment pass will then work properly
7681             // with getViewSize
7682             if (Ext.isIE && Ext.isStrict && ret.height == 0){
7683                 ret =  target.getStyleSize();
7684             }
7685
7686             ret.width -= target.getPadding('lr');
7687             ret.height -= target.getPadding('tb');
7688         }
7689         return ret;
7690     },
7691
7692     renderAll : function(ct, target) {
7693         if(!this.innerCt){
7694             // the innerCt prevents wrapping and shuffling while
7695             // the container is resizing
7696             this.innerCt = target.createChild({cls:'x-column-inner'});
7697             this.innerCt.createChild({cls:'x-clear'});
7698         }
7699         Ext.layout.ColumnLayout.superclass.renderAll.call(this, ct, this.innerCt);
7700     },
7701
7702     // private
7703     onLayout : function(ct, target){
7704         var rs = ct.items.items,
7705             len = rs.length,
7706             r,
7707             m,
7708             i,
7709             margins = [];
7710
7711         this.renderAll(ct, target);
7712
7713         var size = this.getLayoutTargetSize();
7714
7715         if(size.width < 1 && size.height < 1){ // display none?
7716             return;
7717         }
7718
7719         var h = size.height,
7720             ph = h;
7721
7722         this.innerCt.setSize({height:h});
7723
7724         // some rows can be percentages while others are fixed
7725         // so we need to make 2 passes
7726
7727         for(i = 0; i < len; i++){
7728             r = rs[i];
7729             m = r.getPositionEl().getMargins('tb');
7730             margins[i] = m;
7731             if(!r.rowHeight){
7732                 ph -= (r.getHeight() + m);
7733             }
7734         }
7735
7736         ph = ph < 0 ? 0 : ph;
7737
7738         for(i = 0; i < len; i++){
7739             r = rs[i];
7740             m = margins[i];
7741             if(r.rowHeight){
7742                 r.setSize({height: Math.floor(r.rowHeight*ph) - m});
7743             }
7744         }
7745
7746         // Browsers differ as to when they account for scrollbars.  We need to re-measure to see if the scrollbar
7747         // spaces were accounted for properly.  If not, re-layout.
7748         if (Ext.isIE) {
7749             if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) {
7750                 var ts = this.getLayoutTargetSize();
7751                 if (ts.width != size.width){
7752                     this.adjustmentPass = true;
7753                     this.onLayout(ct, target);
7754                 }
7755             }
7756         }
7757         delete this.adjustmentPass;
7758     }
7759
7760     /**
7761      * @property activeItem
7762      * @hide
7763      */
7764 });
7765
7766 Ext.Container.LAYOUTS['ux.row'] = Ext.ux.layout.RowLayout;
7767 Ext.ns('Ext.ux.form');
7768
7769 Ext.ux.form.SearchField = Ext.extend(Ext.form.TwinTriggerField, {
7770     initComponent : function(){
7771         Ext.ux.form.SearchField.superclass.initComponent.call(this);
7772         this.on('specialkey', function(f, e){
7773             if(e.getKey() == e.ENTER){
7774                 this.onTrigger2Click();
7775             }
7776         }, this);
7777     },
7778
7779     validationEvent:false,
7780     validateOnBlur:false,
7781     trigger1Class:'x-form-clear-trigger',
7782     trigger2Class:'x-form-search-trigger',
7783     hideTrigger1:true,
7784     width:180,
7785     hasSearch : false,
7786     paramName : 'query',
7787
7788     onTrigger1Click : function(){
7789         if(this.hasSearch){
7790             this.el.dom.value = '';
7791             var o = {start: 0};
7792             this.store.baseParams = this.store.baseParams || {};
7793             this.store.baseParams[this.paramName] = '';
7794             this.store.reload({params:o});
7795             this.triggers[0].hide();
7796             this.hasSearch = false;
7797         }
7798     },
7799
7800     onTrigger2Click : function(){
7801         var v = this.getRawValue();
7802         if(v.length < 1){
7803             this.onTrigger1Click();
7804             return;
7805         }
7806         var o = {start: 0};
7807         this.store.baseParams = this.store.baseParams || {};
7808         this.store.baseParams[this.paramName] = v;
7809         this.store.reload({params:o});
7810         this.hasSearch = true;
7811         this.triggers[0].show();
7812     }
7813 });Ext.ns('Ext.ux.form');
7814
7815 /**
7816  * @class Ext.ux.form.SelectBox
7817  * @extends Ext.form.ComboBox
7818  * <p>Makes a ComboBox more closely mimic an HTML SELECT.  Supports clicking and dragging
7819  * through the list, with item selection occurring when the mouse button is released.
7820  * When used will automatically set {@link #editable} to false and call {@link Ext.Element#unselectable}
7821  * on inner elements.  Re-enabling editable after calling this will NOT work.</p>
7822  * @author Corey Gilmore http://extjs.com/forum/showthread.php?t=6392
7823  * @history 2007-07-08 jvs
7824  * Slight mods for Ext 2.0
7825  * @xtype selectbox
7826  */
7827 Ext.ux.form.SelectBox = Ext.extend(Ext.form.ComboBox, {
7828         constructor: function(config){
7829                 this.searchResetDelay = 1000;
7830                 config = config || {};
7831                 config = Ext.apply(config || {}, {
7832                         editable: false,
7833                         forceSelection: true,
7834                         rowHeight: false,
7835                         lastSearchTerm: false,
7836                         triggerAction: 'all',
7837                         mode: 'local'
7838                 });
7839
7840                 Ext.ux.form.SelectBox.superclass.constructor.apply(this, arguments);
7841
7842                 this.lastSelectedIndex = this.selectedIndex || 0;
7843         },
7844
7845         initEvents : function(){
7846                 Ext.ux.form.SelectBox.superclass.initEvents.apply(this, arguments);
7847                 // you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE
7848                 this.el.on('keydown', this.keySearch, this, true);
7849                 this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistory, this);
7850         },
7851
7852         keySearch : function(e, target, options) {
7853                 var raw = e.getKey();
7854                 var key = String.fromCharCode(raw);
7855                 var startIndex = 0;
7856
7857                 if( !this.store.getCount() ) {
7858                         return;
7859                 }
7860
7861                 switch(raw) {
7862                         case Ext.EventObject.HOME:
7863                                 e.stopEvent();
7864                                 this.selectFirst();
7865                                 return;
7866
7867                         case Ext.EventObject.END:
7868                                 e.stopEvent();
7869                                 this.selectLast();
7870                                 return;
7871
7872                         case Ext.EventObject.PAGEDOWN:
7873                                 this.selectNextPage();
7874                                 e.stopEvent();
7875                                 return;
7876
7877                         case Ext.EventObject.PAGEUP:
7878                                 this.selectPrevPage();
7879                                 e.stopEvent();
7880                                 return;
7881                 }
7882
7883                 // skip special keys other than the shift key
7884                 if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) {
7885                         return;
7886                 }
7887                 if( this.lastSearchTerm == key ) {
7888                         startIndex = this.lastSelectedIndex;
7889                 }
7890                 this.search(this.displayField, key, startIndex);
7891                 this.cshTask.delay(this.searchResetDelay);
7892         },
7893
7894         onRender : function(ct, position) {
7895                 this.store.on('load', this.calcRowsPerPage, this);
7896                 Ext.ux.form.SelectBox.superclass.onRender.apply(this, arguments);
7897                 if( this.mode == 'local' ) {
7898             this.initList();
7899                         this.calcRowsPerPage();
7900                 }
7901         },
7902
7903         onSelect : function(record, index, skipCollapse){
7904                 if(this.fireEvent('beforeselect', this, record, index) !== false){
7905                         this.setValue(record.data[this.valueField || this.displayField]);
7906                         if( !skipCollapse ) {
7907                                 this.collapse();
7908                         }
7909                         this.lastSelectedIndex = index + 1;
7910                         this.fireEvent('select', this, record, index);
7911                 }
7912         },
7913
7914         afterRender : function() {
7915                 Ext.ux.form.SelectBox.superclass.afterRender.apply(this, arguments);
7916                 if(Ext.isWebKit) {
7917                         this.el.swallowEvent('mousedown', true);
7918                 }
7919                 this.el.unselectable();
7920                 this.innerList.unselectable();
7921                 this.trigger.unselectable();
7922                 this.innerList.on('mouseup', function(e, target, options) {
7923                         if( target.id && target.id == this.innerList.id ) {
7924                                 return;
7925                         }
7926                         this.onViewClick();
7927                 }, this);
7928
7929                 this.innerList.on('mouseover', function(e, target, options) {
7930                         if( target.id && target.id == this.innerList.id ) {
7931                                 return;
7932                         }
7933                         this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;
7934                         this.cshTask.delay(this.searchResetDelay);
7935                 }, this);
7936
7937                 this.trigger.un('click', this.onTriggerClick, this);
7938                 this.trigger.on('mousedown', function(e, target, options) {
7939                         e.preventDefault();
7940                         this.onTriggerClick();
7941                 }, this);
7942
7943                 this.on('collapse', function(e, target, options) {
7944                         Ext.getDoc().un('mouseup', this.collapseIf, this);
7945                 }, this, true);
7946
7947                 this.on('expand', function(e, target, options) {
7948                         Ext.getDoc().on('mouseup', this.collapseIf, this);
7949                 }, this, true);
7950         },
7951
7952         clearSearchHistory : function() {
7953                 this.lastSelectedIndex = 0;
7954                 this.lastSearchTerm = false;
7955         },
7956
7957         selectFirst : function() {
7958                 this.focusAndSelect(this.store.data.first());
7959         },
7960
7961         selectLast : function() {
7962                 this.focusAndSelect(this.store.data.last());
7963         },
7964
7965         selectPrevPage : function() {
7966                 if( !this.rowHeight ) {
7967                         return;
7968                 }
7969                 var index = Math.max(this.selectedIndex-this.rowsPerPage, 0);
7970                 this.focusAndSelect(this.store.getAt(index));
7971         },
7972
7973         selectNextPage : function() {
7974                 if( !this.rowHeight ) {
7975                         return;
7976                 }
7977                 var index = Math.min(this.selectedIndex+this.rowsPerPage, this.store.getCount() - 1);
7978                 this.focusAndSelect(this.store.getAt(index));
7979         },
7980
7981         search : function(field, value, startIndex) {
7982                 field = field || this.displayField;
7983                 this.lastSearchTerm = value;
7984                 var index = this.store.find.apply(this.store, arguments);
7985                 if( index !== -1 ) {
7986                         this.focusAndSelect(index);
7987                 }
7988         },
7989
7990         focusAndSelect : function(record) {
7991         var index = Ext.isNumber(record) ? record : this.store.indexOf(record);
7992         this.select(index, this.isExpanded());
7993         this.onSelect(this.store.getAt(index), index, this.isExpanded());
7994         },
7995
7996         calcRowsPerPage : function() {
7997                 if( this.store.getCount() ) {
7998                         this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight();
7999                         this.rowsPerPage = this.maxHeight / this.rowHeight;
8000                 } else {
8001                         this.rowHeight = false;
8002                 }
8003         }
8004
8005 });
8006
8007 Ext.reg('selectbox', Ext.ux.form.SelectBox);
8008
8009 //backwards compat
8010 Ext.ux.SelectBox = Ext.ux.form.SelectBox;
8011 /**
8012  * Plugin for PagingToolbar which replaces the textfield input with a slider 
8013  */
8014 Ext.ux.SlidingPager = Ext.extend(Object, {
8015     init : function(pbar){
8016         var idx = pbar.items.indexOf(pbar.inputItem);
8017         Ext.each(pbar.items.getRange(idx - 2, idx + 2), function(c){
8018             c.hide();
8019         });
8020         var slider = new Ext.Slider({
8021             width: 114,
8022             minValue: 1,
8023             maxValue: 1,
8024             plugins: new Ext.slider.Tip({
8025                 getText : function(thumb) {
8026                     return String.format('Page <b>{0}</b> of <b>{1}</b>', thumb.value, thumb.slider.maxValue);
8027                 }
8028             }),
8029             listeners: {
8030                 changecomplete: function(s, v){
8031                     pbar.changePage(v);
8032                 }
8033             }
8034         });
8035         pbar.insert(idx + 1, slider);
8036         pbar.on({
8037             change: function(pb, data){
8038                 slider.setMaxValue(data.pages);
8039                 slider.setValue(data.activePage);
8040             }
8041         });
8042     }
8043 });Ext.ns('Ext.ux.form');
8044
8045 /**
8046  * @class Ext.ux.form.SpinnerField
8047  * @extends Ext.form.NumberField
8048  * Creates a field utilizing Ext.ux.Spinner
8049  * @xtype spinnerfield
8050  */
8051 Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, {
8052     actionMode: 'wrap',
8053     deferHeight: true,
8054     autoSize: Ext.emptyFn,
8055     onBlur: Ext.emptyFn,
8056     adjustSize: Ext.BoxComponent.prototype.adjustSize,
8057
8058         constructor: function(config) {
8059                 var spinnerConfig = Ext.copyTo({}, config, 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass');
8060
8061                 var spl = this.spinner = new Ext.ux.Spinner(spinnerConfig);
8062
8063                 var plugins = config.plugins
8064                         ? (Ext.isArray(config.plugins)
8065                                 ? config.plugins.push(spl)
8066                                 : [config.plugins, spl])
8067                         : spl;
8068
8069                 Ext.ux.form.SpinnerField.superclass.constructor.call(this, Ext.apply(config, {plugins: plugins}));
8070         },
8071
8072     // private
8073     getResizeEl: function(){
8074         return this.wrap;
8075     },
8076
8077     // private
8078     getPositionEl: function(){
8079         return this.wrap;
8080     },
8081
8082     // private
8083     alignErrorIcon: function(){
8084         if (this.wrap) {
8085             this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
8086         }
8087     },
8088
8089     validateBlur: function(){
8090         return true;
8091     }
8092 });
8093
8094 Ext.reg('spinnerfield', Ext.ux.form.SpinnerField);
8095
8096 //backwards compat
8097 Ext.form.SpinnerField = Ext.ux.form.SpinnerField;
8098 /**
8099  * @class Ext.ux.Spinner
8100  * @extends Ext.util.Observable
8101  * Creates a Spinner control utilized by Ext.ux.form.SpinnerField
8102  */
8103 Ext.ux.Spinner = Ext.extend(Ext.util.Observable, {
8104     incrementValue: 1,
8105     alternateIncrementValue: 5,
8106     triggerClass: 'x-form-spinner-trigger',
8107     splitterClass: 'x-form-spinner-splitter',
8108     alternateKey: Ext.EventObject.shiftKey,
8109     defaultValue: 0,
8110     accelerate: false,
8111
8112     constructor: function(config){
8113         Ext.ux.Spinner.superclass.constructor.call(this, config);
8114         Ext.apply(this, config);
8115         this.mimicing = false;
8116     },
8117
8118     init: function(field){
8119         this.field = field;
8120
8121         field.afterMethod('onRender', this.doRender, this);
8122         field.afterMethod('onEnable', this.doEnable, this);
8123         field.afterMethod('onDisable', this.doDisable, this);
8124         field.afterMethod('afterRender', this.doAfterRender, this);
8125         field.afterMethod('onResize', this.doResize, this);
8126         field.afterMethod('onFocus', this.doFocus, this);
8127         field.beforeMethod('onDestroy', this.doDestroy, this);
8128     },
8129
8130     doRender: function(ct, position){
8131         var el = this.el = this.field.getEl();
8132         var f = this.field;
8133
8134         if (!f.wrap) {
8135             f.wrap = this.wrap = el.wrap({
8136                 cls: "x-form-field-wrap"
8137             });
8138         }
8139         else {
8140             this.wrap = f.wrap.addClass('x-form-field-wrap');
8141         }
8142
8143         this.trigger = this.wrap.createChild({
8144             tag: "img",
8145             src: Ext.BLANK_IMAGE_URL,
8146             cls: "x-form-trigger " + this.triggerClass
8147         });
8148
8149         if (!f.width) {
8150             this.wrap.setWidth(el.getWidth() + this.trigger.getWidth());
8151         }
8152
8153         this.splitter = this.wrap.createChild({
8154             tag: 'div',
8155             cls: this.splitterClass,
8156             style: 'width:13px; height:2px;'
8157         });
8158         this.splitter.setRight((Ext.isIE) ? 1 : 2).setTop(10).show();
8159
8160         this.proxy = this.trigger.createProxy('', this.splitter, true);
8161         this.proxy.addClass("x-form-spinner-proxy");
8162         this.proxy.setStyle('left', '0px');
8163         this.proxy.setSize(14, 1);
8164         this.proxy.hide();
8165         this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", {
8166             dragElId: this.proxy.id
8167         });
8168
8169         this.initTrigger();
8170         this.initSpinner();
8171     },
8172
8173     doAfterRender: function(){
8174         var y;
8175         if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) {
8176             this.el.position();
8177             this.el.setY(y);
8178         }
8179     },
8180
8181     doEnable: function(){
8182         if (this.wrap) {
8183             this.disabled = false;
8184             this.wrap.removeClass(this.field.disabledClass);
8185         }
8186     },
8187
8188     doDisable: function(){
8189         if (this.wrap) {
8190                 this.disabled = true;
8191             this.wrap.addClass(this.field.disabledClass);
8192             this.el.removeClass(this.field.disabledClass);
8193         }
8194     },
8195
8196     doResize: function(w, h){
8197         if (typeof w == 'number') {
8198             this.el.setWidth(w - this.trigger.getWidth());
8199         }
8200         this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth());
8201     },
8202
8203     doFocus: function(){
8204         if (!this.mimicing) {
8205             this.wrap.addClass('x-trigger-wrap-focus');
8206             this.mimicing = true;
8207             Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {
8208                 delay: 10
8209             });
8210             this.el.on('keydown', this.checkTab, this);
8211         }
8212     },
8213
8214     // private
8215     checkTab: function(e){
8216         if (e.getKey() == e.TAB) {
8217             this.triggerBlur();
8218         }
8219     },
8220
8221     // private
8222     mimicBlur: function(e){
8223         if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) {
8224             this.triggerBlur();
8225         }
8226     },
8227
8228     // private
8229     triggerBlur: function(){
8230         this.mimicing = false;
8231         Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);
8232         this.el.un("keydown", this.checkTab, this);
8233         this.field.beforeBlur();
8234         this.wrap.removeClass('x-trigger-wrap-focus');
8235         this.field.onBlur.call(this.field);
8236     },
8237
8238     initTrigger: function(){
8239         this.trigger.addClassOnOver('x-form-trigger-over');
8240         this.trigger.addClassOnClick('x-form-trigger-click');
8241     },
8242
8243     initSpinner: function(){
8244         this.field.addEvents({
8245             'spin': true,
8246             'spinup': true,
8247             'spindown': true
8248         });
8249
8250         this.keyNav = new Ext.KeyNav(this.el, {
8251             "up": function(e){
8252                 e.preventDefault();
8253                 this.onSpinUp();
8254             },
8255
8256             "down": function(e){
8257                 e.preventDefault();
8258                 this.onSpinDown();
8259             },
8260
8261             "pageUp": function(e){
8262                 e.preventDefault();
8263                 this.onSpinUpAlternate();
8264             },
8265
8266             "pageDown": function(e){
8267                 e.preventDefault();
8268                 this.onSpinDownAlternate();
8269             },
8270
8271             scope: this
8272         });
8273
8274         this.repeater = new Ext.util.ClickRepeater(this.trigger, {
8275             accelerate: this.accelerate
8276         });
8277         this.field.mon(this.repeater, "click", this.onTriggerClick, this, {
8278             preventDefault: true
8279         });
8280
8281         this.field.mon(this.trigger, {
8282             mouseover: this.onMouseOver,
8283             mouseout: this.onMouseOut,
8284             mousemove: this.onMouseMove,
8285             mousedown: this.onMouseDown,
8286             mouseup: this.onMouseUp,
8287             scope: this,
8288             preventDefault: true
8289         });
8290
8291         this.field.mon(this.wrap, "mousewheel", this.handleMouseWheel, this);
8292
8293         this.dd.setXConstraint(0, 0, 10)
8294         this.dd.setYConstraint(1500, 1500, 10);
8295         this.dd.endDrag = this.endDrag.createDelegate(this);
8296         this.dd.startDrag = this.startDrag.createDelegate(this);
8297         this.dd.onDrag = this.onDrag.createDelegate(this);
8298     },
8299
8300     onMouseOver: function(){
8301         if (this.disabled) {
8302             return;
8303         }
8304         var middle = this.getMiddle();
8305         this.tmpHoverClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown';
8306         this.trigger.addClass(this.tmpHoverClass);
8307     },
8308
8309     //private
8310     onMouseOut: function(){
8311         this.trigger.removeClass(this.tmpHoverClass);
8312     },
8313
8314     //private
8315     onMouseMove: function(){
8316         if (this.disabled) {
8317             return;
8318         }
8319         var middle = this.getMiddle();
8320         if (((Ext.EventObject.getPageY() > middle) && this.tmpHoverClass == "x-form-spinner-overup") ||
8321         ((Ext.EventObject.getPageY() < middle) && this.tmpHoverClass == "x-form-spinner-overdown")) {
8322         }
8323     },
8324
8325     //private
8326     onMouseDown: function(){
8327         if (this.disabled) {
8328             return;
8329         }
8330         var middle = this.getMiddle();
8331         this.tmpClickClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown';
8332         this.trigger.addClass(this.tmpClickClass);
8333     },
8334
8335     //private
8336     onMouseUp: function(){
8337         this.trigger.removeClass(this.tmpClickClass);
8338     },
8339
8340     //private
8341     onTriggerClick: function(){
8342         if (this.disabled || this.el.dom.readOnly) {
8343             return;
8344         }
8345         var middle = this.getMiddle();
8346         var ud = (Ext.EventObject.getPageY() < middle) ? 'Up' : 'Down';
8347         this['onSpin' + ud]();
8348     },
8349
8350     //private
8351     getMiddle: function(){
8352         var t = this.trigger.getTop();
8353         var h = this.trigger.getHeight();
8354         var middle = t + (h / 2);
8355         return middle;
8356     },
8357
8358     //private
8359     //checks if control is allowed to spin
8360     isSpinnable: function(){
8361         if (this.disabled || this.el.dom.readOnly) {
8362             Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly
8363             return false;
8364         }
8365         return true;
8366     },
8367
8368     handleMouseWheel: function(e){
8369         //disable scrolling when not focused
8370         if (this.wrap.hasClass('x-trigger-wrap-focus') == false) {
8371             return;
8372         }
8373
8374         var delta = e.getWheelDelta();
8375         if (delta > 0) {
8376             this.onSpinUp();
8377             e.stopEvent();
8378         }
8379         else
8380             if (delta < 0) {
8381                 this.onSpinDown();
8382                 e.stopEvent();
8383             }
8384     },
8385
8386     //private
8387     startDrag: function(){
8388         this.proxy.show();
8389         this._previousY = Ext.fly(this.dd.getDragEl()).getTop();
8390     },
8391
8392     //private
8393     endDrag: function(){
8394         this.proxy.hide();
8395     },
8396
8397     //private
8398     onDrag: function(){
8399         if (this.disabled) {
8400             return;
8401         }
8402         var y = Ext.fly(this.dd.getDragEl()).getTop();
8403         var ud = '';
8404
8405         if (this._previousY > y) {
8406             ud = 'Up';
8407         } //up
8408         if (this._previousY < y) {
8409             ud = 'Down';
8410         } //down
8411         if (ud != '') {
8412             this['onSpin' + ud]();
8413         }
8414
8415         this._previousY = y;
8416     },
8417
8418     //private
8419     onSpinUp: function(){
8420         if (this.isSpinnable() == false) {
8421             return;
8422         }
8423         if (Ext.EventObject.shiftKey == true) {
8424             this.onSpinUpAlternate();
8425             return;
8426         }
8427         else {
8428             this.spin(false, false);
8429         }
8430         this.field.fireEvent("spin", this);
8431         this.field.fireEvent("spinup", this);
8432     },
8433
8434     //private
8435     onSpinDown: function(){
8436         if (this.isSpinnable() == false) {
8437             return;
8438         }
8439         if (Ext.EventObject.shiftKey == true) {
8440             this.onSpinDownAlternate();
8441             return;
8442         }
8443         else {
8444             this.spin(true, false);
8445         }
8446         this.field.fireEvent("spin", this);
8447         this.field.fireEvent("spindown", this);
8448     },
8449
8450     //private
8451     onSpinUpAlternate: function(){
8452         if (this.isSpinnable() == false) {
8453             return;
8454         }
8455         this.spin(false, true);
8456         this.field.fireEvent("spin", this);
8457         this.field.fireEvent("spinup", this);
8458     },
8459
8460     //private
8461     onSpinDownAlternate: function(){
8462         if (this.isSpinnable() == false) {
8463             return;
8464         }
8465         this.spin(true, true);
8466         this.field.fireEvent("spin", this);
8467         this.field.fireEvent("spindown", this);
8468     },
8469
8470     spin: function(down, alternate){
8471         var v = parseFloat(this.field.getValue());
8472         var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;
8473         (down == true) ? v -= incr : v += incr;
8474
8475         v = (isNaN(v)) ? this.defaultValue : v;
8476         v = this.fixBoundries(v);
8477         this.field.setRawValue(v);
8478     },
8479
8480     fixBoundries: function(value){
8481         var v = value;
8482
8483         if (this.field.minValue != undefined && v < this.field.minValue) {
8484             v = this.field.minValue;
8485         }
8486         if (this.field.maxValue != undefined && v > this.field.maxValue) {
8487             v = this.field.maxValue;
8488         }
8489
8490         return this.fixPrecision(v);
8491     },
8492
8493     // private
8494     fixPrecision: function(value){
8495         var nan = isNaN(value);
8496         if (!this.field.allowDecimals || this.field.decimalPrecision == -1 || nan || !value) {
8497             return nan ? '' : value;
8498         }
8499         return parseFloat(parseFloat(value).toFixed(this.field.decimalPrecision));
8500     },
8501
8502     doDestroy: function(){
8503         if (this.trigger) {
8504             this.trigger.remove();
8505         }
8506         if (this.wrap) {
8507             this.wrap.remove();
8508             delete this.field.wrap;
8509         }
8510
8511         if (this.splitter) {
8512             this.splitter.remove();
8513         }
8514
8515         if (this.dd) {
8516             this.dd.unreg();
8517             this.dd = null;
8518         }
8519
8520         if (this.proxy) {
8521             this.proxy.remove();
8522         }
8523
8524         if (this.repeater) {
8525             this.repeater.purgeListeners();
8526         }
8527         if (this.mimicing){
8528             Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);
8529         }
8530     }
8531 });
8532
8533 //backwards compat
8534 Ext.form.Spinner = Ext.ux.Spinner;Ext.ux.Spotlight = function(config){
8535     Ext.apply(this, config);
8536 }
8537 Ext.ux.Spotlight.prototype = {
8538     active : false,
8539     animate : true,
8540     duration: .25,
8541     easing:'easeNone',
8542
8543     // private
8544     animated : false,
8545
8546     createElements : function(){
8547         var bd = Ext.getBody();
8548
8549         this.right = bd.createChild({cls:'x-spotlight'});
8550         this.left = bd.createChild({cls:'x-spotlight'});
8551         this.top = bd.createChild({cls:'x-spotlight'});
8552         this.bottom = bd.createChild({cls:'x-spotlight'});
8553
8554         this.all = new Ext.CompositeElement([this.right, this.left, this.top, this.bottom]);
8555     },
8556
8557     show : function(el, callback, scope){
8558         if(this.animated){
8559             this.show.defer(50, this, [el, callback, scope]);
8560             return;
8561         }
8562         this.el = Ext.get(el);
8563         if(!this.right){
8564             this.createElements();
8565         }
8566         if(!this.active){
8567             this.all.setDisplayed('');
8568             this.applyBounds(true, false);
8569             this.active = true;
8570             Ext.EventManager.onWindowResize(this.syncSize, this);
8571             this.applyBounds(false, this.animate, false, callback, scope);
8572         }else{
8573             this.applyBounds(false, false, false, callback, scope); // all these booleans look hideous
8574         }
8575     },
8576
8577     hide : function(callback, scope){
8578         if(this.animated){
8579             this.hide.defer(50, this, [callback, scope]);
8580             return;
8581         }
8582         Ext.EventManager.removeResizeListener(this.syncSize, this);
8583         this.applyBounds(true, this.animate, true, callback, scope);
8584     },
8585
8586     doHide : function(){
8587         this.active = false;
8588         this.all.setDisplayed(false);
8589     },
8590
8591     syncSize : function(){
8592         this.applyBounds(false, false);
8593     },
8594
8595     applyBounds : function(basePts, anim, doHide, callback, scope){
8596
8597         var rg = this.el.getRegion();
8598
8599         var dw = Ext.lib.Dom.getViewWidth(true);
8600         var dh = Ext.lib.Dom.getViewHeight(true);
8601
8602         var c = 0, cb = false;
8603         if(anim){
8604             cb = {
8605                 callback: function(){
8606                     c++;
8607                     if(c == 4){
8608                         this.animated = false;
8609                         if(doHide){
8610                             this.doHide();
8611                         }
8612                         Ext.callback(callback, scope, [this]);
8613                     }
8614                 },
8615                 scope: this,
8616                 duration: this.duration,
8617                 easing: this.easing
8618             };
8619             this.animated = true;
8620         }
8621
8622         this.right.setBounds(
8623                 rg.right,
8624                 basePts ? dh : rg.top,
8625                 dw - rg.right,
8626                 basePts ? 0 : (dh - rg.top),
8627                 cb);
8628
8629         this.left.setBounds(
8630                 0,
8631                 0,
8632                 rg.left,
8633                 basePts ? 0 : rg.bottom,
8634                 cb);
8635
8636         this.top.setBounds(
8637                 basePts ? dw : rg.left,
8638                 0,
8639                 basePts ? 0 : dw - rg.left,
8640                 rg.top,
8641                 cb);
8642
8643         this.bottom.setBounds(
8644                 0,
8645                 rg.bottom,
8646                 basePts ? 0 : rg.right,
8647                 dh - rg.bottom,
8648                 cb);
8649
8650         if(!anim){
8651             if(doHide){
8652                 this.doHide();
8653             }
8654             if(callback){
8655                 Ext.callback(callback, scope, [this]);
8656             }
8657         }
8658     },
8659
8660     destroy : function(){
8661         this.doHide();
8662         Ext.destroy(
8663             this.right,
8664             this.left,
8665             this.top,
8666             this.bottom);
8667         delete this.el;
8668         delete this.all;
8669     }
8670 };
8671
8672 //backwards compat
8673 Ext.Spotlight = Ext.ux.Spotlight;/**
8674  * @class Ext.ux.StatusBar
8675  * <p>Basic status bar component that can be used as the bottom toolbar of any {@link Ext.Panel}.  In addition to
8676  * supporting the standard {@link Ext.Toolbar} interface for adding buttons, menus and other items, the StatusBar
8677  * provides a greedy status element that can be aligned to either side and has convenient methods for setting the
8678  * status text and icon.  You can also indicate that something is processing using the {@link #showBusy} method.</p>
8679  * <pre><code>
8680 new Ext.Panel({
8681     title: 'StatusBar',
8682     // etc.
8683     bbar: new Ext.ux.StatusBar({
8684         id: 'my-status',
8685
8686         // defaults to use when the status is cleared:
8687         defaultText: 'Default status text',
8688         defaultIconCls: 'default-icon',
8689
8690         // values to set initially:
8691         text: 'Ready',
8692         iconCls: 'ready-icon',
8693
8694         // any standard Toolbar items:
8695         items: [{
8696             text: 'A Button'
8697         }, '-', 'Plain Text']
8698     })
8699 });
8700
8701 // Update the status bar later in code:
8702 var sb = Ext.getCmp('my-status');
8703 sb.setStatus({
8704     text: 'OK',
8705     iconCls: 'ok-icon',
8706     clear: true // auto-clear after a set interval
8707 });
8708
8709 // Set the status bar to show that something is processing:
8710 sb.showBusy();
8711
8712 // processing....
8713
8714 sb.clearStatus(); // once completeed
8715 </code></pre>
8716  * @extends Ext.Toolbar
8717  * @constructor
8718  * Creates a new StatusBar
8719  * @param {Object/Array} config A config object
8720  */
8721 Ext.ux.StatusBar = Ext.extend(Ext.Toolbar, {
8722     /**
8723      * @cfg {String} statusAlign
8724      * The alignment of the status element within the overall StatusBar layout.  When the StatusBar is rendered,
8725      * it creates an internal div containing the status text and icon.  Any additional Toolbar items added in the
8726      * StatusBar's {@link #items} config, or added via {@link #add} or any of the supported add* methods, will be
8727      * rendered, in added order, to the opposite side.  The status element is greedy, so it will automatically
8728      * expand to take up all sapce left over by any other items.  Example usage:
8729      * <pre><code>
8730 // Create a left-aligned status bar containing a button,
8731 // separator and text item that will be right-aligned (default):
8732 new Ext.Panel({
8733     title: 'StatusBar',
8734     // etc.
8735     bbar: new Ext.ux.StatusBar({
8736         defaultText: 'Default status text',
8737         id: 'status-id',
8738         items: [{
8739             text: 'A Button'
8740         }, '-', 'Plain Text']
8741     })
8742 });
8743
8744 // By adding the statusAlign config, this will create the
8745 // exact same toolbar, except the status and toolbar item
8746 // layout will be reversed from the previous example:
8747 new Ext.Panel({
8748     title: 'StatusBar',
8749     // etc.
8750     bbar: new Ext.ux.StatusBar({
8751         defaultText: 'Default status text',
8752         id: 'status-id',
8753         statusAlign: 'right',
8754         items: [{
8755             text: 'A Button'
8756         }, '-', 'Plain Text']
8757     })
8758 });
8759 </code></pre>
8760      */
8761     /**
8762      * @cfg {String} defaultText
8763      * The default {@link #text} value.  This will be used anytime the status bar is cleared with the
8764      * <tt>useDefaults:true</tt> option (defaults to '').
8765      */
8766     /**
8767      * @cfg {String} defaultIconCls
8768      * The default {@link #iconCls} value (see the iconCls docs for additional details about customizing the icon).
8769      * This will be used anytime the status bar is cleared with the <tt>useDefaults:true</tt> option (defaults to '').
8770      */
8771     /**
8772      * @cfg {String} text
8773      * A string that will be <b>initially</b> set as the status message.  This string
8774      * will be set as innerHTML (html tags are accepted) for the toolbar item.
8775      * If not specified, the value set for <code>{@link #defaultText}</code>
8776      * will be used.
8777      */
8778     /**
8779      * @cfg {String} iconCls
8780      * A CSS class that will be <b>initially</b> set as the status bar icon and is
8781      * expected to provide a background image (defaults to '').
8782      * Example usage:<pre><code>
8783 // Example CSS rule:
8784 .x-statusbar .x-status-custom {
8785     padding-left: 25px;
8786     background: transparent url(images/custom-icon.gif) no-repeat 3px 2px;
8787 }
8788
8789 // Setting a default icon:
8790 var sb = new Ext.ux.StatusBar({
8791     defaultIconCls: 'x-status-custom'
8792 });
8793
8794 // Changing the icon:
8795 sb.setStatus({
8796     text: 'New status',
8797     iconCls: 'x-status-custom'
8798 });
8799 </code></pre>
8800      */
8801
8802     /**
8803      * @cfg {String} cls
8804      * The base class applied to the containing element for this component on render (defaults to 'x-statusbar')
8805      */
8806     cls : 'x-statusbar',
8807     /**
8808      * @cfg {String} busyIconCls
8809      * The default <code>{@link #iconCls}</code> applied when calling
8810      * <code>{@link #showBusy}</code> (defaults to <tt>'x-status-busy'</tt>).
8811      * It can be overridden at any time by passing the <code>iconCls</code>
8812      * argument into <code>{@link #showBusy}</code>.
8813      */
8814     busyIconCls : 'x-status-busy',
8815     /**
8816      * @cfg {String} busyText
8817      * The default <code>{@link #text}</code> applied when calling
8818      * <code>{@link #showBusy}</code> (defaults to <tt>'Loading...'</tt>).
8819      * It can be overridden at any time by passing the <code>text</code>
8820      * argument into <code>{@link #showBusy}</code>.
8821      */
8822     busyText : 'Loading...',
8823     /**
8824      * @cfg {Number} autoClear
8825      * The number of milliseconds to wait after setting the status via
8826      * <code>{@link #setStatus}</code> before automatically clearing the status
8827      * text and icon (defaults to <tt>5000</tt>).  Note that this only applies
8828      * when passing the <tt>clear</tt> argument to <code>{@link #setStatus}</code>
8829      * since that is the only way to defer clearing the status.  This can
8830      * be overridden by specifying a different <tt>wait</tt> value in
8831      * <code>{@link #setStatus}</code>. Calls to <code>{@link #clearStatus}</code>
8832      * always clear the status bar immediately and ignore this value.
8833      */
8834     autoClear : 5000,
8835
8836     /**
8837      * @cfg {String} emptyText
8838      * The text string to use if no text has been set.  Defaults to
8839      * <tt>'&nbsp;'</tt>).  If there are no other items in the toolbar using
8840      * an empty string (<tt>''</tt>) for this value would end up in the toolbar
8841      * height collapsing since the empty string will not maintain the toolbar
8842      * height.  Use <tt>''</tt> if the toolbar should collapse in height
8843      * vertically when no text is specified and there are no other items in
8844      * the toolbar.
8845      */
8846     emptyText : '&nbsp;',
8847
8848     // private
8849     activeThreadId : 0,
8850
8851     // private
8852     initComponent : function(){
8853         if(this.statusAlign=='right'){
8854             this.cls += ' x-status-right';
8855         }
8856         Ext.ux.StatusBar.superclass.initComponent.call(this);
8857     },
8858
8859     // private
8860     afterRender : function(){
8861         Ext.ux.StatusBar.superclass.afterRender.call(this);
8862
8863         var right = this.statusAlign == 'right';
8864         this.currIconCls = this.iconCls || this.defaultIconCls;
8865         this.statusEl = new Ext.Toolbar.TextItem({
8866             cls: 'x-status-text ' + (this.currIconCls || ''),
8867             text: this.text || this.defaultText || ''
8868         });
8869
8870         if(right){
8871             this.add('->');
8872             this.add(this.statusEl);
8873         }else{
8874             this.insert(0, this.statusEl);
8875             this.insert(1, '->');
8876         }
8877         this.doLayout();
8878     },
8879
8880     /**
8881      * Sets the status {@link #text} and/or {@link #iconCls}. Also supports automatically clearing the
8882      * status that was set after a specified interval.
8883      * @param {Object/String} config A config object specifying what status to set, or a string assumed
8884      * to be the status text (and all other options are defaulted as explained below). A config
8885      * object containing any or all of the following properties can be passed:<ul>
8886      * <li><tt>text</tt> {String} : (optional) The status text to display.  If not specified, any current
8887      * status text will remain unchanged.</li>
8888      * <li><tt>iconCls</tt> {String} : (optional) The CSS class used to customize the status icon (see
8889      * {@link #iconCls} for details). If not specified, any current iconCls will remain unchanged.</li>
8890      * <li><tt>clear</tt> {Boolean/Number/Object} : (optional) Allows you to set an internal callback that will
8891      * automatically clear the status text and iconCls after a specified amount of time has passed. If clear is not
8892      * specified, the new status will not be auto-cleared and will stay until updated again or cleared using
8893      * {@link #clearStatus}. If <tt>true</tt> is passed, the status will be cleared using {@link #autoClear},
8894      * {@link #defaultText} and {@link #defaultIconCls} via a fade out animation. If a numeric value is passed,
8895      * it will be used as the callback interval (in milliseconds), overriding the {@link #autoClear} value.
8896      * All other options will be defaulted as with the boolean option.  To customize any other options,
8897      * you can pass an object in the format:<ul>
8898      *    <li><tt>wait</tt> {Number} : (optional) The number of milliseconds to wait before clearing
8899      *    (defaults to {@link #autoClear}).</li>
8900      *    <li><tt>anim</tt> {Number} : (optional) False to clear the status immediately once the callback
8901      *    executes (defaults to true which fades the status out).</li>
8902      *    <li><tt>useDefaults</tt> {Number} : (optional) False to completely clear the status text and iconCls
8903      *    (defaults to true which uses {@link #defaultText} and {@link #defaultIconCls}).</li>
8904      * </ul></li></ul>
8905      * Example usage:<pre><code>
8906 // Simple call to update the text
8907 statusBar.setStatus('New status');
8908
8909 // Set the status and icon, auto-clearing with default options:
8910 statusBar.setStatus({
8911     text: 'New status',
8912     iconCls: 'x-status-custom',
8913     clear: true
8914 });
8915
8916 // Auto-clear with custom options:
8917 statusBar.setStatus({
8918     text: 'New status',
8919     iconCls: 'x-status-custom',
8920     clear: {
8921         wait: 8000,
8922         anim: false,
8923         useDefaults: false
8924     }
8925 });
8926 </code></pre>
8927      * @return {Ext.ux.StatusBar} this
8928      */
8929     setStatus : function(o){
8930         o = o || {};
8931
8932         if(typeof o == 'string'){
8933             o = {text:o};
8934         }
8935         if(o.text !== undefined){
8936             this.setText(o.text);
8937         }
8938         if(o.iconCls !== undefined){
8939             this.setIcon(o.iconCls);
8940         }
8941
8942         if(o.clear){
8943             var c = o.clear,
8944                 wait = this.autoClear,
8945                 defaults = {useDefaults: true, anim: true};
8946
8947             if(typeof c == 'object'){
8948                 c = Ext.applyIf(c, defaults);
8949                 if(c.wait){
8950                     wait = c.wait;
8951                 }
8952             }else if(typeof c == 'number'){
8953                 wait = c;
8954                 c = defaults;
8955             }else if(typeof c == 'boolean'){
8956                 c = defaults;
8957             }
8958
8959             c.threadId = this.activeThreadId;
8960             this.clearStatus.defer(wait, this, [c]);
8961         }
8962         return this;
8963     },
8964
8965     /**
8966      * Clears the status {@link #text} and {@link #iconCls}. Also supports clearing via an optional fade out animation.
8967      * @param {Object} config (optional) A config object containing any or all of the following properties.  If this
8968      * object is not specified the status will be cleared using the defaults below:<ul>
8969      * <li><tt>anim</tt> {Boolean} : (optional) True to clear the status by fading out the status element (defaults
8970      * to false which clears immediately).</li>
8971      * <li><tt>useDefaults</tt> {Boolean} : (optional) True to reset the text and icon using {@link #defaultText} and
8972      * {@link #defaultIconCls} (defaults to false which sets the text to '' and removes any existing icon class).</li>
8973      * </ul>
8974      * @return {Ext.ux.StatusBar} this
8975      */
8976     clearStatus : function(o){
8977         o = o || {};
8978
8979         if(o.threadId && o.threadId !== this.activeThreadId){
8980             // this means the current call was made internally, but a newer
8981             // thread has set a message since this call was deferred.  Since
8982             // we don't want to overwrite a newer message just ignore.
8983             return this;
8984         }
8985
8986         var text = o.useDefaults ? this.defaultText : this.emptyText,
8987             iconCls = o.useDefaults ? (this.defaultIconCls ? this.defaultIconCls : '') : '';
8988
8989         if(o.anim){
8990             // animate the statusEl Ext.Element
8991             this.statusEl.el.fadeOut({
8992                 remove: false,
8993                 useDisplay: true,
8994                 scope: this,
8995                 callback: function(){
8996                     this.setStatus({
8997                             text: text,
8998                             iconCls: iconCls
8999                         });
9000
9001                     this.statusEl.el.show();
9002                 }
9003             });
9004         }else{
9005             // hide/show the el to avoid jumpy text or icon
9006             this.statusEl.hide();
9007                 this.setStatus({
9008                     text: text,
9009                     iconCls: iconCls
9010                 });
9011             this.statusEl.show();
9012         }
9013         return this;
9014     },
9015
9016     /**
9017      * Convenience method for setting the status text directly.  For more flexible options see {@link #setStatus}.
9018      * @param {String} text (optional) The text to set (defaults to '')
9019      * @return {Ext.ux.StatusBar} this
9020      */
9021     setText : function(text){
9022         this.activeThreadId++;
9023         this.text = text || '';
9024         if(this.rendered){
9025             this.statusEl.setText(this.text);
9026         }
9027         return this;
9028     },
9029
9030     /**
9031      * Returns the current status text.
9032      * @return {String} The status text
9033      */
9034     getText : function(){
9035         return this.text;
9036     },
9037
9038     /**
9039      * Convenience method for setting the status icon directly.  For more flexible options see {@link #setStatus}.
9040      * See {@link #iconCls} for complete details about customizing the icon.
9041      * @param {String} iconCls (optional) The icon class to set (defaults to '', and any current icon class is removed)
9042      * @return {Ext.ux.StatusBar} this
9043      */
9044     setIcon : function(cls){
9045         this.activeThreadId++;
9046         cls = cls || '';
9047
9048         if(this.rendered){
9049                 if(this.currIconCls){
9050                     this.statusEl.removeClass(this.currIconCls);
9051                     this.currIconCls = null;
9052                 }
9053                 if(cls.length > 0){
9054                     this.statusEl.addClass(cls);
9055                     this.currIconCls = cls;
9056                 }
9057         }else{
9058             this.currIconCls = cls;
9059         }
9060         return this;
9061     },
9062
9063     /**
9064      * Convenience method for setting the status text and icon to special values that are pre-configured to indicate
9065      * a "busy" state, usually for loading or processing activities.
9066      * @param {Object/String} config (optional) A config object in the same format supported by {@link #setStatus}, or a
9067      * string to use as the status text (in which case all other options for setStatus will be defaulted).  Use the
9068      * <tt>text</tt> and/or <tt>iconCls</tt> properties on the config to override the default {@link #busyText}
9069      * and {@link #busyIconCls} settings. If the config argument is not specified, {@link #busyText} and
9070      * {@link #busyIconCls} will be used in conjunction with all of the default options for {@link #setStatus}.
9071      * @return {Ext.ux.StatusBar} this
9072      */
9073     showBusy : function(o){
9074         if(typeof o == 'string'){
9075             o = {text:o};
9076         }
9077         o = Ext.applyIf(o || {}, {
9078             text: this.busyText,
9079             iconCls: this.busyIconCls
9080         });
9081         return this.setStatus(o);
9082     }
9083 });
9084 Ext.reg('statusbar', Ext.ux.StatusBar);
9085 /**
9086  * @class Ext.ux.TabCloseMenu
9087  * @extends Object 
9088  * Plugin (ptype = 'tabclosemenu') for adding a close context menu to tabs. Note that the menu respects
9089  * the closable configuration on the tab. As such, commands like remove others and remove all will not
9090  * remove items that are not closable.
9091  * 
9092  * @constructor
9093  * @param {Object} config The configuration options
9094  * @ptype tabclosemenu
9095  */
9096 Ext.ux.TabCloseMenu = Ext.extend(Object, {
9097     /**
9098      * @cfg {String} closeTabText
9099      * The text for closing the current tab. Defaults to <tt>'Close Tab'</tt>.
9100      */
9101     closeTabText: 'Close Tab',
9102
9103     /**
9104      * @cfg {String} closeOtherTabsText
9105      * The text for closing all tabs except the current one. Defaults to <tt>'Close Other Tabs'</tt>.
9106      */
9107     closeOtherTabsText: 'Close Other Tabs',
9108     
9109     /**
9110      * @cfg {Boolean} showCloseAll
9111      * Indicates whether to show the 'Close All' option. Defaults to <tt>true</tt>. 
9112      */
9113     showCloseAll: true,
9114
9115     /**
9116      * @cfg {String} closeAllTabsText
9117      * <p>The text for closing all tabs. Defaults to <tt>'Close All Tabs'</tt>.
9118      */
9119     closeAllTabsText: 'Close All Tabs',
9120     
9121     constructor : function(config){
9122         Ext.apply(this, config || {});
9123     },
9124
9125     //public
9126     init : function(tabs){
9127         this.tabs = tabs;
9128         tabs.on({
9129             scope: this,
9130             contextmenu: this.onContextMenu,
9131             destroy: this.destroy
9132         });
9133     },
9134     
9135     destroy : function(){
9136         Ext.destroy(this.menu);
9137         delete this.menu;
9138         delete this.tabs;
9139         delete this.active;    
9140     },
9141
9142     // private
9143     onContextMenu : function(tabs, item, e){
9144         this.active = item;
9145         var m = this.createMenu(),
9146             disableAll = true,
9147             disableOthers = true,
9148             closeAll = m.getComponent('closeall');
9149         
9150         m.getComponent('close').setDisabled(!item.closable);
9151         tabs.items.each(function(){
9152             if(this.closable){
9153                 disableAll = false;
9154                 if(this != item){
9155                     disableOthers = false;
9156                     return false;
9157                 }
9158             }
9159         });
9160         m.getComponent('closeothers').setDisabled(disableOthers);
9161         if(closeAll){
9162             closeAll.setDisabled(disableAll);
9163         }
9164         
9165         e.stopEvent();
9166         m.showAt(e.getPoint());
9167     },
9168     
9169     createMenu : function(){
9170         if(!this.menu){
9171             var items = [{
9172                 itemId: 'close',
9173                 text: this.closeTabText,
9174                 scope: this,
9175                 handler: this.onClose
9176             }];
9177             if(this.showCloseAll){
9178                 items.push('-');
9179             }
9180             items.push({
9181                 itemId: 'closeothers',
9182                 text: this.closeOtherTabsText,
9183                 scope: this,
9184                 handler: this.onCloseOthers
9185             });
9186             if(this.showCloseAll){
9187                 items.push({
9188                     itemId: 'closeall',
9189                     text: this.closeAllTabsText,
9190                     scope: this,
9191                     handler: this.onCloseAll
9192                 });
9193             }
9194             this.menu = new Ext.menu.Menu({
9195                 items: items
9196             });
9197         }
9198         return this.menu;
9199     },
9200     
9201     onClose : function(){
9202         this.tabs.remove(this.active);
9203     },
9204     
9205     onCloseOthers : function(){
9206         this.doClose(true);
9207     },
9208     
9209     onCloseAll : function(){
9210         this.doClose(false);
9211     },
9212     
9213     doClose : function(excludeActive){
9214         var items = [];
9215         this.tabs.items.each(function(item){
9216             if(item.closable){
9217                 if(!excludeActive || item != this.active){
9218                     items.push(item);
9219                 }    
9220             }
9221         }, this);
9222         Ext.each(items, function(item){
9223             this.tabs.remove(item);
9224         }, this);
9225     }
9226 });
9227
9228 Ext.preg('tabclosemenu', Ext.ux.TabCloseMenu);Ext.ns('Ext.ux.grid');
9229
9230 /**
9231  * @class Ext.ux.grid.TableGrid
9232  * @extends Ext.grid.GridPanel
9233  * A Grid which creates itself from an existing HTML table element.
9234  * @history
9235  * 2007-03-01 Original version by Nige "Animal" White
9236  * 2007-03-10 jvs Slightly refactored to reuse existing classes * @constructor
9237  * @param {String/HTMLElement/Ext.Element} table The table element from which this grid will be created -
9238  * The table MUST have some type of size defined for the grid to fill. The container will be
9239  * automatically set to position relative if it isn't already.
9240  * @param {Object} config A config object that sets properties on this grid and has two additional (optional)
9241  * properties: fields and columns which allow for customizing data fields and columns for this grid.
9242  */
9243 Ext.ux.grid.TableGrid = function(table, config){
9244     config = config ||
9245     {};
9246     Ext.apply(this, config);
9247     var cf = config.fields || [], ch = config.columns || [];
9248     table = Ext.get(table);
9249     
9250     var ct = table.insertSibling();
9251     
9252     var fields = [], cols = [];
9253     var headers = table.query("thead th");
9254     for (var i = 0, h; h = headers[i]; i++) {
9255         var text = h.innerHTML;
9256         var name = 'tcol-' + i;
9257         
9258         fields.push(Ext.applyIf(cf[i] ||
9259         {}, {
9260             name: name,
9261             mapping: 'td:nth(' + (i + 1) + ')/@innerHTML'
9262         }));
9263         
9264         cols.push(Ext.applyIf(ch[i] ||
9265         {}, {
9266             'header': text,
9267             'dataIndex': name,
9268             'width': h.offsetWidth,
9269             'tooltip': h.title,
9270             'sortable': true
9271         }));
9272     }
9273     
9274     var ds = new Ext.data.Store({
9275         reader: new Ext.data.XmlReader({
9276             record: 'tbody tr'
9277         }, fields)
9278     });
9279     
9280     ds.loadData(table.dom);
9281     
9282     var cm = new Ext.grid.ColumnModel(cols);
9283     
9284     if (config.width || config.height) {
9285         ct.setSize(config.width || 'auto', config.height || 'auto');
9286     }
9287     else {
9288         ct.setWidth(table.getWidth());
9289     }
9290     
9291     if (config.remove !== false) {
9292         table.remove();
9293     }
9294     
9295     Ext.applyIf(this, {
9296         'ds': ds,
9297         'cm': cm,
9298         'sm': new Ext.grid.RowSelectionModel(),
9299         autoHeight: true,
9300         autoWidth: false
9301     });
9302     Ext.ux.grid.TableGrid.superclass.constructor.call(this, ct, {});
9303 };
9304
9305 Ext.extend(Ext.ux.grid.TableGrid, Ext.grid.GridPanel);
9306
9307 //backwards compat
9308 Ext.grid.TableGrid = Ext.ux.grid.TableGrid;
9309 Ext.ns('Ext.ux');
9310 /**
9311  * @class Ext.ux.TabScrollerMenu
9312  * @extends Object 
9313  * Plugin (ptype = 'tabscrollermenu') for adding a tab scroller menu to tabs.
9314  * @constructor 
9315  * @param {Object} config Configuration options
9316  * @ptype tabscrollermenu
9317  */
9318 Ext.ux.TabScrollerMenu =  Ext.extend(Object, {
9319     /**
9320      * @cfg {Number} pageSize How many items to allow per submenu.
9321      */
9322         pageSize       : 10,
9323     /**
9324      * @cfg {Number} maxText How long should the title of each {@link Ext.menu.Item} be.
9325      */
9326         maxText        : 15,
9327     /**
9328      * @cfg {String} menuPrefixText Text to prefix the submenus.
9329      */    
9330         menuPrefixText : 'Items',
9331         constructor    : function(config) {
9332                 config = config || {};
9333                 Ext.apply(this, config);
9334         },
9335     //private
9336         init : function(tabPanel) {
9337                 Ext.apply(tabPanel, this.parentOverrides);
9338                 
9339                 tabPanel.tabScrollerMenu = this;
9340                 var thisRef = this;
9341                 
9342                 tabPanel.on({
9343                         render : {
9344                                 scope  : tabPanel,
9345                                 single : true,
9346                                 fn     : function() { 
9347                                         var newFn = tabPanel.createScrollers.createSequence(thisRef.createPanelsMenu, this);
9348                                         tabPanel.createScrollers = newFn;
9349                                 }
9350                         }
9351                 });
9352         },
9353         // private && sequeneced
9354         createPanelsMenu : function() {
9355                 var h = this.stripWrap.dom.offsetHeight;
9356                 
9357                 //move the right menu item to the left 18px
9358                 var rtScrBtn = this.header.dom.firstChild;
9359                 Ext.fly(rtScrBtn).applyStyles({
9360                         right : '18px'
9361                 });
9362                 
9363                 var stripWrap = Ext.get(this.strip.dom.parentNode);
9364                 stripWrap.applyStyles({
9365                          'margin-right' : '36px'
9366                 });
9367                 
9368                 // Add the new righthand menu
9369                 var scrollMenu = this.header.insertFirst({
9370                         cls:'x-tab-tabmenu-right'
9371                 });
9372                 scrollMenu.setHeight(h);
9373                 scrollMenu.addClassOnOver('x-tab-tabmenu-over');
9374                 scrollMenu.on('click', this.showTabsMenu, this);        
9375                 
9376                 this.scrollLeft.show = this.scrollLeft.show.createSequence(function() {
9377                         scrollMenu.show();                                                                                                                                               
9378                 });
9379                 
9380                 this.scrollLeft.hide = this.scrollLeft.hide.createSequence(function() {
9381                         scrollMenu.hide();                                                              
9382                 });
9383                 
9384         },
9385     /**
9386      * Returns an the current page size (this.pageSize);
9387      * @return {Number} this.pageSize The current page size.
9388      */
9389         getPageSize : function() {
9390                 return this.pageSize;
9391         },
9392     /**
9393      * Sets the number of menu items per submenu "page size".
9394      * @param {Number} pageSize The page size
9395      */
9396     setPageSize : function(pageSize) {
9397                 this.pageSize = pageSize;
9398         },
9399     /**
9400      * Returns the current maxText length;
9401      * @return {Number} this.maxText The current max text length.
9402      */
9403     getMaxText : function() {
9404                 return this.maxText;
9405         },
9406     /**
9407      * Sets the maximum text size for each menu item.
9408      * @param {Number} t The max text per each menu item.
9409      */
9410     setMaxText : function(t) {
9411                 this.maxText = t;
9412         },
9413     /**
9414      * Returns the current menu prefix text String.;
9415      * @return {String} this.menuPrefixText The current menu prefix text.
9416      */
9417         getMenuPrefixText : function() {
9418                 return this.menuPrefixText;
9419         },
9420     /**
9421      * Sets the menu prefix text String.
9422      * @param {String} t The menu prefix text.
9423      */    
9424         setMenuPrefixText : function(t) {
9425                 this.menuPrefixText = t;
9426         },
9427         // private && applied to the tab panel itself.
9428         parentOverrides : {
9429                 // all execute within the scope of the tab panel
9430                 // private      
9431                 showTabsMenu : function(e) {            
9432                         if  (this.tabsMenu) {
9433                                 this.tabsMenu.destroy();
9434                 this.un('destroy', this.tabsMenu.destroy, this.tabsMenu);
9435                 this.tabsMenu = null;
9436                         }
9437             this.tabsMenu =  new Ext.menu.Menu();
9438             this.on('destroy', this.tabsMenu.destroy, this.tabsMenu);
9439
9440             this.generateTabMenuItems();
9441
9442             var target = Ext.get(e.getTarget());
9443                         var xy     = target.getXY();
9444 //
9445                         //Y param + 24 pixels
9446                         xy[1] += 24;
9447                         
9448                         this.tabsMenu.showAt(xy);
9449                 },
9450                 // private      
9451                 generateTabMenuItems : function() {
9452                         var curActive  = this.getActiveTab();
9453                         var totalItems = this.items.getCount();
9454                         var pageSize   = this.tabScrollerMenu.getPageSize();
9455                         
9456                         
9457                         if (totalItems > pageSize)  {
9458                                 var numSubMenus = Math.floor(totalItems / pageSize);
9459                                 var remainder   = totalItems % pageSize;
9460                                 
9461                                 // Loop through all of the items and create submenus in chunks of 10
9462                                 for (var i = 0 ; i < numSubMenus; i++) {
9463                                         var curPage = (i + 1) * pageSize;
9464                                         var menuItems = [];
9465                                         
9466                                         
9467                                         for (var x = 0; x < pageSize; x++) {                            
9468                                                 index = x + curPage - pageSize;
9469                                                 var item = this.items.get(index);
9470                                                 menuItems.push(this.autoGenMenuItem(item));
9471                                         }
9472                                         
9473                                         this.tabsMenu.add({
9474                                                 text : this.tabScrollerMenu.getMenuPrefixText() + ' '  + (curPage - pageSize + 1) + ' - ' + curPage,
9475                                                 menu : menuItems
9476                                         });
9477                                         
9478                                 }
9479                                 // remaining items
9480                                 if (remainder > 0) {
9481                                         var start = numSubMenus * pageSize;
9482                                         menuItems = [];
9483                                         for (var i = start ; i < totalItems; i ++ ) {                                   
9484                                                 var item = this.items.get(i);
9485                                                 menuItems.push(this.autoGenMenuItem(item));
9486                                         }
9487                                         
9488                                         this.tabsMenu.add({
9489                                                 text : this.tabScrollerMenu.menuPrefixText  + ' ' + (start + 1) + ' - ' + (start + menuItems.length),
9490                                                 menu : menuItems
9491                                         });
9492
9493                                 }
9494                         }
9495                         else {
9496                                 this.items.each(function(item) {
9497                                         if (item.id != curActive.id && !item.hidden) {
9498                         this.tabsMenu.add(this.autoGenMenuItem(item));
9499                                         }
9500                                 }, this);
9501                         }
9502                 },
9503                 // private
9504                 autoGenMenuItem : function(item) {
9505                         var maxText = this.tabScrollerMenu.getMaxText();
9506                         var text    = Ext.util.Format.ellipsis(item.title, maxText);
9507                         
9508                         return {
9509                                 text      : text,
9510                                 handler   : this.showTabFromMenu,
9511                                 scope     : this,
9512                                 disabled  : item.disabled,
9513                                 tabToShow : item,
9514                                 iconCls   : item.iconCls
9515                         }
9516                 
9517                 },
9518                 // private
9519                 showTabFromMenu : function(menuItem) {
9520                         this.setActiveTab(menuItem.tabToShow);
9521                 }       
9522         }       
9523 });
9524
9525 Ext.reg('tabscrollermenu', Ext.ux.TabScrollerMenu);
9526 Ext.ns('Ext.ux.tree');
9527
9528 /**
9529  * @class Ext.ux.tree.XmlTreeLoader
9530  * @extends Ext.tree.TreeLoader
9531  * <p>A TreeLoader that can convert an XML document into a hierarchy of {@link Ext.tree.TreeNode}s.
9532  * Any text value included as a text node in the XML will be added to the parent node as an attribute
9533  * called <tt>innerText</tt>.  Also, the tag name of each XML node will be added to the tree node as
9534  * an attribute called <tt>tagName</tt>.</p>
9535  * <p>By default, this class expects that your source XML will provide the necessary attributes on each
9536  * node as expected by the {@link Ext.tree.TreePanel} to display and load properly.  However, you can
9537  * provide your own custom processing of node attributes by overriding the {@link #processNode} method
9538  * and modifying the attributes as needed before they are used to create the associated TreeNode.</p>
9539  * @constructor
9540  * Creates a new XmlTreeloader.
9541  * @param {Object} config A config object containing config properties.
9542  */
9543 Ext.ux.tree.XmlTreeLoader = Ext.extend(Ext.tree.TreeLoader, {
9544     /**
9545      * @property  XML_NODE_ELEMENT
9546      * XML element node (value 1, read-only)
9547      * @type Number
9548      */
9549     XML_NODE_ELEMENT : 1,
9550     /**
9551      * @property  XML_NODE_TEXT
9552      * XML text node (value 3, read-only)
9553      * @type Number
9554      */
9555     XML_NODE_TEXT : 3,
9556
9557     // private override
9558     processResponse : function(response, node, callback){
9559         var xmlData = response.responseXML,
9560             root = xmlData.documentElement || xmlData;
9561
9562         try{
9563             node.beginUpdate();
9564             node.appendChild(this.parseXml(root));
9565             node.endUpdate();
9566
9567             this.runCallback(callback, scope || node, [node]);
9568         }catch(e){
9569             this.handleFailure(response);
9570         }
9571     },
9572
9573     // private
9574     parseXml : function(node) {
9575         var nodes = [];
9576         Ext.each(node.childNodes, function(n){
9577             if(n.nodeType == this.XML_NODE_ELEMENT){
9578                 var treeNode = this.createNode(n);
9579                 if(n.childNodes.length > 0){
9580                     var child = this.parseXml(n);
9581                     if(typeof child == 'string'){
9582                         treeNode.attributes.innerText = child;
9583                     }else{
9584                         treeNode.appendChild(child);
9585                     }
9586                 }
9587                 nodes.push(treeNode);
9588             }
9589             else if(n.nodeType == this.XML_NODE_TEXT){
9590                 var text = n.nodeValue.trim();
9591                 if(text.length > 0){
9592                     return nodes = text;
9593                 }
9594             }
9595         }, this);
9596
9597         return nodes;
9598     },
9599
9600     // private override
9601     createNode : function(node){
9602         var attr = {
9603             tagName: node.tagName
9604         };
9605
9606         Ext.each(node.attributes, function(a){
9607             attr[a.nodeName] = a.nodeValue;
9608         });
9609
9610         this.processAttributes(attr);
9611
9612         return Ext.ux.tree.XmlTreeLoader.superclass.createNode.call(this, attr);
9613     },
9614
9615     /*
9616      * Template method intended to be overridden by subclasses that need to provide
9617      * custom attribute processing prior to the creation of each TreeNode.  This method
9618      * will be passed a config object containing existing TreeNode attribute name/value
9619      * pairs which can be modified as needed directly (no need to return the object).
9620      */
9621     processAttributes: Ext.emptyFn
9622 });
9623
9624 //backwards compat
9625 Ext.ux.XmlTreeLoader = Ext.ux.tree.XmlTreeLoader;
9626 /**
9627  * @class Ext.ux.ValidationStatus
9628  * A {@link Ext.StatusBar} plugin that provides automatic error notification when the
9629  * associated form contains validation errors.
9630  * @extends Ext.Component
9631  * @constructor
9632  * Creates a new ValiationStatus plugin
9633  * @param {Object} config A config object
9634  */
9635 Ext.ux.ValidationStatus = Ext.extend(Ext.Component, {
9636     /**
9637      * @cfg {String} errorIconCls
9638      * The {@link #iconCls} value to be applied to the status message when there is a
9639      * validation error. Defaults to <tt>'x-status-error'</tt>.
9640      */
9641     errorIconCls : 'x-status-error',
9642     /**
9643      * @cfg {String} errorListCls
9644      * The css class to be used for the error list when there are validation errors.
9645      * Defaults to <tt>'x-status-error-list'</tt>.
9646      */
9647     errorListCls : 'x-status-error-list',
9648     /**
9649      * @cfg {String} validIconCls
9650      * The {@link #iconCls} value to be applied to the status message when the form
9651      * validates. Defaults to <tt>'x-status-valid'</tt>.
9652      */
9653     validIconCls : 'x-status-valid',
9654     
9655     /**
9656      * @cfg {String} showText
9657      * The {@link #text} value to be applied when there is a form validation error.
9658      * Defaults to <tt>'The form has errors (click for details...)'</tt>.
9659      */
9660     showText : 'The form has errors (click for details...)',
9661     /**
9662      * @cfg {String} showText
9663      * The {@link #text} value to display when the error list is displayed.
9664      * Defaults to <tt>'Click again to hide the error list'</tt>.
9665      */
9666     hideText : 'Click again to hide the error list',
9667     /**
9668      * @cfg {String} submitText
9669      * The {@link #text} value to be applied when the form is being submitted.
9670      * Defaults to <tt>'Saving...'</tt>.
9671      */
9672     submitText : 'Saving...',
9673     
9674     // private
9675     init : function(sb){
9676         sb.on('render', function(){
9677             this.statusBar = sb;
9678             this.monitor = true;
9679             this.errors = new Ext.util.MixedCollection();
9680             this.listAlign = (sb.statusAlign=='right' ? 'br-tr?' : 'bl-tl?');
9681             
9682             if(this.form){
9683                 this.form = Ext.getCmp(this.form).getForm();
9684                 this.startMonitoring();
9685                 this.form.on('beforeaction', function(f, action){
9686                     if(action.type == 'submit'){
9687                         // Ignore monitoring while submitting otherwise the field validation
9688                         // events cause the status message to reset too early
9689                         this.monitor = false;
9690                     }
9691                 }, this);
9692                 var startMonitor = function(){
9693                     this.monitor = true;
9694                 };
9695                 this.form.on('actioncomplete', startMonitor, this);
9696                 this.form.on('actionfailed', startMonitor, this);
9697             }
9698         }, this, {single:true});
9699         sb.on({
9700             scope: this,
9701             afterlayout:{
9702                 single: true,
9703                 fn: function(){
9704                     // Grab the statusEl after the first layout.
9705                     sb.statusEl.getEl().on('click', this.onStatusClick, this, {buffer:200});
9706                 } 
9707             }, 
9708             beforedestroy:{
9709                 single: true,
9710                 fn: this.onDestroy
9711             } 
9712         });
9713     },
9714     
9715     // private
9716     startMonitoring : function(){
9717         this.form.items.each(function(f){
9718             f.on('invalid', this.onFieldValidation, this);
9719             f.on('valid', this.onFieldValidation, this);
9720         }, this);
9721     },
9722     
9723     // private
9724     stopMonitoring : function(){
9725         this.form.items.each(function(f){
9726             f.un('invalid', this.onFieldValidation, this);
9727             f.un('valid', this.onFieldValidation, this);
9728         }, this);
9729     },
9730     
9731     // private
9732     onDestroy : function(){
9733         this.stopMonitoring();
9734         this.statusBar.statusEl.un('click', this.onStatusClick, this);
9735         Ext.ux.ValidationStatus.superclass.onDestroy.call(this);
9736     },
9737     
9738     // private
9739     onFieldValidation : function(f, msg){
9740         if(!this.monitor){
9741             return false;
9742         }
9743         if(msg){
9744             this.errors.add(f.id, {field:f, msg:msg});
9745         }else{
9746             this.errors.removeKey(f.id);
9747         }
9748         this.updateErrorList();
9749         if(this.errors.getCount() > 0){
9750             if(this.statusBar.getText() != this.showText){
9751                 this.statusBar.setStatus({text:this.showText, iconCls:this.errorIconCls});
9752             }
9753         }else{
9754             this.statusBar.clearStatus().setIcon(this.validIconCls);
9755         }
9756     },
9757     
9758     // private
9759     updateErrorList : function(){
9760         if(this.errors.getCount() > 0){
9761                 var msg = '<ul>';
9762                 this.errors.each(function(err){
9763                     msg += ('<li id="x-err-'+ err.field.id +'"><a href="#">' + err.msg + '</a></li>');
9764                 }, this);
9765                 this.getMsgEl().update(msg+'</ul>');
9766         }else{
9767             this.getMsgEl().update('');
9768         }
9769     },
9770     
9771     // private
9772     getMsgEl : function(){
9773         if(!this.msgEl){
9774             this.msgEl = Ext.DomHelper.append(Ext.getBody(), {
9775                 cls: this.errorListCls+' x-hide-offsets'
9776             }, true);
9777             
9778             this.msgEl.on('click', function(e){
9779                 var t = e.getTarget('li', 10, true);
9780                 if(t){
9781                     Ext.getCmp(t.id.split('x-err-')[1]).focus();
9782                     this.hideErrors();
9783                 }
9784             }, this, {stopEvent:true}); // prevent anchor click navigation
9785         }
9786         return this.msgEl;
9787     },
9788     
9789     // private
9790     showErrors : function(){
9791         this.updateErrorList();
9792         this.getMsgEl().alignTo(this.statusBar.getEl(), this.listAlign).slideIn('b', {duration:0.3, easing:'easeOut'});
9793         this.statusBar.setText(this.hideText);
9794         this.form.getEl().on('click', this.hideErrors, this, {single:true}); // hide if the user clicks directly into the form
9795     },
9796     
9797     // private
9798     hideErrors : function(){
9799         var el = this.getMsgEl();
9800         if(el.isVisible()){
9801                 el.slideOut('b', {duration:0.2, easing:'easeIn'});
9802                 this.statusBar.setText(this.showText);
9803         }
9804         this.form.getEl().un('click', this.hideErrors, this);
9805     },
9806     
9807     // private
9808     onStatusClick : function(){
9809         if(this.getMsgEl().isVisible()){
9810             this.hideErrors();
9811         }else if(this.errors.getCount() > 0){
9812             this.showErrors();
9813         }
9814     }
9815 });(function() {
9816     Ext.override(Ext.list.Column, {
9817         init : function() {    
9818             var types = Ext.data.Types,
9819                 st = this.sortType;
9820                     
9821             if(this.type){
9822                 if(Ext.isString(this.type)){
9823                     this.type = Ext.data.Types[this.type.toUpperCase()] || types.AUTO;
9824                 }
9825             }else{
9826                 this.type = types.AUTO;
9827             }
9828
9829             // named sortTypes are supported, here we look them up
9830             if(Ext.isString(st)){
9831                 this.sortType = Ext.data.SortTypes[st];
9832             }else if(Ext.isEmpty(st)){
9833                 this.sortType = this.type.sortType;
9834             }
9835         }
9836     });
9837
9838     Ext.tree.Column = Ext.extend(Ext.list.Column, {});
9839     Ext.tree.NumberColumn = Ext.extend(Ext.list.NumberColumn, {});
9840     Ext.tree.DateColumn = Ext.extend(Ext.list.DateColumn, {});
9841     Ext.tree.BooleanColumn = Ext.extend(Ext.list.BooleanColumn, {});
9842
9843     Ext.reg('tgcolumn', Ext.tree.Column);
9844     Ext.reg('tgnumbercolumn', Ext.tree.NumberColumn);
9845     Ext.reg('tgdatecolumn', Ext.tree.DateColumn);
9846     Ext.reg('tgbooleancolumn', Ext.tree.BooleanColumn);
9847 })();
9848 /**
9849  * @class Ext.ux.tree.TreeGridNodeUI
9850  * @extends Ext.tree.TreeNodeUI
9851  */
9852 Ext.ux.tree.TreeGridNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
9853     isTreeGridNodeUI: true,
9854
9855     renderElements : function(n, a, targetNode, bulkRender){
9856         var t = n.getOwnerTree(),
9857             cols = t.columns,
9858             c = cols[0],
9859             i, buf, len;
9860
9861         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
9862
9863         buf = [
9864              '<tbody class="x-tree-node">',
9865                 '<tr ext:tree-node-id="', n.id ,'" class="x-tree-node-el x-tree-node-leaf ', a.cls, '">',
9866                     '<td class="x-treegrid-col">',
9867                         '<span class="x-tree-node-indent">', this.indentMarkup, "</span>",
9868                         '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
9869                         '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon', (a.icon ? " x-tree-node-inline-icon" : ""), (a.iconCls ? " "+a.iconCls : ""), '" unselectable="on" />',
9870                         '<a hidefocus="on" class="x-tree-node-anchor" href="', a.href ? a.href : '#', '" tabIndex="1" ',
9871                             a.hrefTarget ? ' target="'+a.hrefTarget+'"' : '', '>',
9872                         '<span unselectable="on">', (c.tpl ? c.tpl.apply(a) : a[c.dataIndex] || c.text), '</span></a>',
9873                     '</td>'
9874         ];
9875
9876         for(i = 1, len = cols.length; i < len; i++){
9877             c = cols[i];
9878             buf.push(
9879                     '<td class="x-treegrid-col ', (c.cls ? c.cls : ''), '">',
9880                         '<div unselectable="on" class="x-treegrid-text"', (c.align ? ' style="text-align: ' + c.align + ';"' : ''), '>',
9881                             (c.tpl ? c.tpl.apply(a) : a[c.dataIndex]),
9882                         '</div>',
9883                     '</td>'
9884             );
9885         }
9886
9887         buf.push(
9888             '</tr><tr class="x-tree-node-ct"><td colspan="', cols.length, '">',
9889             '<table class="x-treegrid-node-ct-table" cellpadding="0" cellspacing="0" style="table-layout: fixed; display: none; width: ', t.innerCt.getWidth() ,'px;"><colgroup>'
9890         );
9891         for(i = 0, len = cols.length; i<len; i++) {
9892             buf.push('<col style="width: ', (cols[i].hidden ? 0 : cols[i].width) ,'px;" />');
9893         }
9894         buf.push('</colgroup></table></td></tr></tbody>');
9895
9896         if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
9897             this.wrap = Ext.DomHelper.insertHtml("beforeBegin", n.nextSibling.ui.getEl(), buf.join(''));
9898         }else{
9899             this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(''));
9900         }
9901
9902         this.elNode = this.wrap.childNodes[0];
9903         this.ctNode = this.wrap.childNodes[1].firstChild.firstChild;
9904         var cs = this.elNode.firstChild.childNodes;
9905         this.indentNode = cs[0];
9906         this.ecNode = cs[1];
9907         this.iconNode = cs[2];
9908         this.anchor = cs[3];
9909         this.textNode = cs[3].firstChild;
9910     },
9911
9912     // private
9913     animExpand : function(cb){
9914         this.ctNode.style.display = "";
9915         Ext.ux.tree.TreeGridNodeUI.superclass.animExpand.call(this, cb);
9916     }
9917 });
9918
9919 Ext.ux.tree.TreeGridRootNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
9920     isTreeGridNodeUI: true,
9921
9922     // private
9923     render : function(){
9924         if(!this.rendered){
9925             this.wrap = this.ctNode = this.node.ownerTree.innerCt.dom;
9926             this.node.expanded = true;
9927         }
9928
9929         if(Ext.isWebKit) {
9930             // weird table-layout: fixed issue in webkit
9931             var ct = this.ctNode;
9932             ct.style.tableLayout = null;
9933             (function() {
9934                 ct.style.tableLayout = 'fixed';
9935             }).defer(1);
9936         }
9937     },
9938
9939     destroy : function(){
9940         if(this.elNode){
9941             Ext.dd.Registry.unregister(this.elNode.id);
9942         }
9943         delete this.node;
9944     },
9945
9946     collapse : Ext.emptyFn,
9947     expand : Ext.emptyFn
9948 });/**
9949  * @class Ext.tree.ColumnResizer
9950  * @extends Ext.util.Observable
9951  */
9952 Ext.tree.ColumnResizer = Ext.extend(Ext.util.Observable, {
9953     /**
9954      * @cfg {Number} minWidth The minimum width the column can be dragged to.
9955      * Defaults to <tt>14</tt>.
9956      */
9957     minWidth: 14,
9958
9959     constructor: function(config){
9960         Ext.apply(this, config);
9961         Ext.tree.ColumnResizer.superclass.constructor.call(this);
9962     },
9963
9964     init : function(tree){
9965         this.tree = tree;
9966         tree.on('render', this.initEvents, this);
9967     },
9968
9969     initEvents : function(tree){
9970         tree.mon(tree.innerHd, 'mousemove', this.handleHdMove, this);
9971         this.tracker = new Ext.dd.DragTracker({
9972             onBeforeStart: this.onBeforeStart.createDelegate(this),
9973             onStart: this.onStart.createDelegate(this),
9974             onDrag: this.onDrag.createDelegate(this),
9975             onEnd: this.onEnd.createDelegate(this),
9976             tolerance: 3,
9977             autoStart: 300
9978         });
9979         this.tracker.initEl(tree.innerHd);
9980         tree.on('beforedestroy', this.tracker.destroy, this.tracker);
9981     },
9982
9983     handleHdMove : function(e, t){
9984         var hw = 5,
9985             x = e.getPageX(),
9986             hd = e.getTarget('.x-treegrid-hd', 3, true);
9987         
9988         if(hd){                                 
9989             var r = hd.getRegion(),
9990                 ss = hd.dom.style,
9991                 pn = hd.dom.parentNode;
9992             
9993             if(x - r.left <= hw && hd.dom !== pn.firstChild) {
9994                 var ps = hd.dom.previousSibling;
9995                 while(ps && Ext.fly(ps).hasClass('x-treegrid-hd-hidden')) {
9996                     ps = ps.previousSibling;
9997                 }
9998                 if(ps) {                    
9999                     this.activeHd = Ext.get(ps);
10000                                 ss.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize';
10001                 }
10002             } else if(r.right - x <= hw) {
10003                 var ns = hd.dom;
10004                 while(ns && Ext.fly(ns).hasClass('x-treegrid-hd-hidden')) {
10005                     ns = ns.previousSibling;
10006                 }
10007                 if(ns) {
10008                     this.activeHd = Ext.get(ns);
10009                                 ss.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize';                    
10010                 }
10011             } else{
10012                 delete this.activeHd;
10013                 ss.cursor = '';
10014             }
10015         }
10016     },
10017
10018     onBeforeStart : function(e){
10019         this.dragHd = this.activeHd;
10020         return !!this.dragHd;
10021     },
10022
10023     onStart : function(e){
10024         this.dragHeadersDisabled = this.tree.headersDisabled;
10025         this.tree.headersDisabled = true;
10026         this.proxy = this.tree.body.createChild({cls:'x-treegrid-resizer'});
10027         this.proxy.setHeight(this.tree.body.getHeight());
10028
10029         var x = this.tracker.getXY()[0];
10030
10031         this.hdX = this.dragHd.getX();
10032         this.hdIndex = this.tree.findHeaderIndex(this.dragHd);
10033
10034         this.proxy.setX(this.hdX);
10035         this.proxy.setWidth(x-this.hdX);
10036
10037         this.maxWidth = this.tree.outerCt.getWidth() - this.tree.innerBody.translatePoints(this.hdX).left;
10038     },
10039
10040     onDrag : function(e){
10041         var cursorX = this.tracker.getXY()[0];
10042         this.proxy.setWidth((cursorX-this.hdX).constrain(this.minWidth, this.maxWidth));
10043     },
10044
10045     onEnd : function(e){
10046         var nw = this.proxy.getWidth(),
10047             tree = this.tree,
10048             disabled = this.dragHeadersDisabled;
10049         
10050         this.proxy.remove();
10051         delete this.dragHd;
10052         
10053         tree.columns[this.hdIndex].width = nw;
10054         tree.updateColumnWidths();
10055         
10056         setTimeout(function(){
10057             tree.headersDisabled = disabled;
10058         }, 100);
10059     }
10060 });Ext.ns('Ext.ux.tree');
10061
10062 /**
10063  * @class Ext.ux.tree.TreeGridSorter
10064  * @extends Ext.tree.TreeSorter
10065  * Provides sorting of nodes in a {@link Ext.ux.tree.TreeGrid}.  The TreeGridSorter automatically monitors events on the
10066  * associated TreeGrid that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange).
10067  * Example usage:<br />
10068  * <pre><code>
10069  new Ext.ux.tree.TreeGridSorter(myTreeGrid, {
10070      folderSort: true,
10071      dir: "desc",
10072      sortType: function(node) {
10073          // sort by a custom, typed attribute:
10074          return parseInt(node.id, 10);
10075      }
10076  });
10077  </code></pre>
10078  * @constructor
10079  * @param {TreeGrid} tree
10080  * @param {Object} config
10081  */
10082 Ext.ux.tree.TreeGridSorter = Ext.extend(Ext.tree.TreeSorter, {
10083     /**
10084      * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to <tt>['sort-asc', 'sort-desc']</tt>)
10085      */
10086     sortClasses : ['sort-asc', 'sort-desc'],
10087     /**
10088      * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to <tt>'Sort Ascending'</tt>)
10089      */
10090     sortAscText : 'Sort Ascending',
10091     /**
10092      * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to <tt>'Sort Descending'</tt>)
10093      */
10094     sortDescText : 'Sort Descending',
10095
10096     constructor : function(tree, config) {
10097         if(!Ext.isObject(config)) {
10098             config = {
10099                 property: tree.columns[0].dataIndex || 'text',
10100                 folderSort: true
10101             }
10102         }
10103
10104         Ext.ux.tree.TreeGridSorter.superclass.constructor.apply(this, arguments);
10105
10106         this.tree = tree;
10107         tree.on('headerclick', this.onHeaderClick, this);
10108         tree.ddAppendOnly = true;
10109
10110         var me = this;
10111         this.defaultSortFn = function(n1, n2){
10112
10113             var desc = me.dir && me.dir.toLowerCase() == 'desc',
10114                 prop = me.property || 'text',
10115                 sortType = me.sortType,
10116                 caseSensitive = me.caseSensitive === true,
10117                 leafAttr = me.leafAttr || 'leaf',
10118                 attr1 = n1.attributes,
10119                 attr2 = n2.attributes;
10120
10121             if(me.folderSort){
10122                 if(attr1[leafAttr] && !attr2[leafAttr]){
10123                     return 1;
10124                 }
10125                 if(!attr1[leafAttr] && attr2[leafAttr]){
10126                     return -1;
10127                 }
10128             }
10129             var prop1 = attr1[prop],
10130                 prop2 = attr2[prop],
10131                 v1 = sortType ? sortType(prop1) : (caseSensitive ? prop1 : prop1.toUpperCase());
10132                 v2 = sortType ? sortType(prop2) : (caseSensitive ? prop2 : prop2.toUpperCase());
10133                 
10134             if(v1 < v2){
10135                 return desc ? +1 : -1;
10136             }else if(v1 > v2){
10137                 return desc ? -1 : +1;
10138             }else{
10139                 return 0;
10140             }
10141         };
10142
10143         tree.on('afterrender', this.onAfterTreeRender, this, {single: true});
10144         tree.on('headermenuclick', this.onHeaderMenuClick, this);
10145     },
10146
10147     onAfterTreeRender : function() {
10148         if(this.tree.hmenu){
10149             this.tree.hmenu.insert(0,
10150                 {itemId:'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'},
10151                 {itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
10152             );
10153         }
10154         this.updateSortIcon(0, 'asc');
10155     },
10156
10157     onHeaderMenuClick : function(c, id, index) {
10158         if(id === 'asc' || id === 'desc') {
10159             this.onHeaderClick(c, null, index);
10160             return false;
10161         }
10162     },
10163
10164     onHeaderClick : function(c, el, i) {
10165         if(c && !this.tree.headersDisabled){
10166             var me = this;
10167
10168             me.property = c.dataIndex;
10169             me.dir = c.dir = (c.dir === 'desc' ? 'asc' : 'desc');
10170             me.sortType = c.sortType;
10171             me.caseSensitive === Ext.isBoolean(c.caseSensitive) ? c.caseSensitive : this.caseSensitive;
10172             me.sortFn = c.sortFn || this.defaultSortFn;
10173
10174             this.tree.root.cascade(function(n) {
10175                 if(!n.isLeaf()) {
10176                     me.updateSort(me.tree, n);
10177                 }
10178             });
10179
10180             this.updateSortIcon(i, c.dir);
10181         }
10182     },
10183
10184     // private
10185     updateSortIcon : function(col, dir){
10186         var sc = this.sortClasses,
10187             hds = this.tree.innerHd.select('td').removeClass(sc);
10188         hds.item(col).addClass(sc[dir == 'desc' ? 1 : 0]);
10189     }
10190 });/**
10191  * @class Ext.ux.tree.TreeGridLoader
10192  * @extends Ext.tree.TreeLoader
10193  */
10194 Ext.ux.tree.TreeGridLoader = Ext.extend(Ext.tree.TreeLoader, {
10195     createNode : function(attr) {
10196         if (!attr.uiProvider) {
10197             attr.uiProvider = Ext.ux.tree.TreeGridNodeUI;
10198         }
10199         return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
10200     }
10201 });/**
10202  * @class Ext.ux.tree.TreeGrid
10203  * @extends Ext.tree.TreePanel
10204  * 
10205  * @xtype treegrid
10206  */
10207 Ext.ux.tree.TreeGrid = Ext.extend(Ext.tree.TreePanel, {
10208     rootVisible : false,
10209     useArrows : true,
10210     lines : false,
10211     borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
10212     cls : 'x-treegrid',
10213
10214     columnResize : true,
10215     enableSort : true,
10216     reserveScrollOffset : true,
10217     enableHdMenu : true,
10218     
10219     columnsText : 'Columns',
10220
10221     initComponent : function() {
10222         if(!this.root) {
10223             this.root = new Ext.tree.AsyncTreeNode({text: 'Root'});
10224         }
10225         
10226         // initialize the loader
10227         var l = this.loader;
10228         if(!l){
10229             l = new Ext.ux.tree.TreeGridLoader({
10230                 dataUrl: this.dataUrl,
10231                 requestMethod: this.requestMethod,
10232                 store: this.store
10233             });
10234         }else if(Ext.isObject(l) && !l.load){
10235             l = new Ext.ux.tree.TreeGridLoader(l);
10236         }
10237         this.loader = l;
10238                             
10239         Ext.ux.tree.TreeGrid.superclass.initComponent.call(this);                    
10240         
10241         this.initColumns();
10242         
10243         if(this.enableSort) {
10244             this.treeGridSorter = new Ext.ux.tree.TreeGridSorter(this, this.enableSort);
10245         }
10246         
10247         if(this.columnResize){
10248             this.colResizer = new Ext.tree.ColumnResizer(this.columnResize);
10249             this.colResizer.init(this);
10250         }
10251         
10252         var c = this.columns;
10253         if(!this.internalTpl){                                
10254             this.internalTpl = new Ext.XTemplate(
10255                 '<div class="x-grid3-header">',
10256                     '<div class="x-treegrid-header-inner">',
10257                         '<div class="x-grid3-header-offset">',
10258                             '<table style="table-layout: fixed;" cellspacing="0" cellpadding="0" border="0"><colgroup><tpl for="columns"><col /></tpl></colgroup>',
10259                             '<thead><tr class="x-grid3-hd-row">',
10260                             '<tpl for="columns">',
10261                             '<td class="x-grid3-hd x-grid3-cell x-treegrid-hd" style="text-align: {align};" id="', this.id, '-xlhd-{#}">',
10262                                 '<div class="x-grid3-hd-inner x-treegrid-hd-inner" unselectable="on">',
10263                                      this.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '',
10264                                      '{header}<img class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
10265                                  '</div>',
10266                             '</td></tpl>',
10267                             '</tr></thead>',
10268                         '</table>',
10269                     '</div></div>',
10270                 '</div>',
10271                 '<div class="x-treegrid-root-node">',
10272                     '<table class="x-treegrid-root-table" cellpadding="0" cellspacing="0" style="table-layout: fixed;"></table>',
10273                 '</div>'
10274             );
10275         }
10276         
10277         if(!this.colgroupTpl) {
10278             this.colgroupTpl = new Ext.XTemplate(
10279                 '<colgroup><tpl for="columns"><col style="width: {width}px"/></tpl></colgroup>'
10280             );
10281         }
10282     },
10283
10284     initColumns : function() {
10285         var cs = this.columns,
10286             len = cs.length, 
10287             columns = [],
10288             i, c;
10289
10290         for(i = 0; i < len; i++){
10291             c = cs[i];
10292             if(!c.isColumn) {
10293                 c.xtype = c.xtype ? (/^tg/.test(c.xtype) ? c.xtype : 'tg' + c.xtype) : 'tgcolumn';
10294                 c = Ext.create(c);
10295             }
10296             c.init(this);
10297             columns.push(c);
10298             
10299             if(this.enableSort !== false && c.sortable !== false) {
10300                 c.sortable = true;
10301                 this.enableSort = true;
10302             }
10303         }
10304
10305         this.columns = columns;
10306     },
10307
10308     onRender : function(){
10309         Ext.tree.TreePanel.superclass.onRender.apply(this, arguments);
10310
10311         this.el.addClass('x-treegrid');
10312         
10313         this.outerCt = this.body.createChild({
10314             cls:'x-tree-root-ct x-treegrid-ct ' + (this.useArrows ? 'x-tree-arrows' : this.lines ? 'x-tree-lines' : 'x-tree-no-lines')
10315         });
10316         
10317         this.internalTpl.overwrite(this.outerCt, {columns: this.columns});
10318         
10319         this.mainHd = Ext.get(this.outerCt.dom.firstChild);
10320         this.innerHd = Ext.get(this.mainHd.dom.firstChild);
10321         this.innerBody = Ext.get(this.outerCt.dom.lastChild);
10322         this.innerCt = Ext.get(this.innerBody.dom.firstChild);
10323         
10324         this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns});
10325         
10326         if(this.hideHeaders){
10327             this.el.child('.x-grid3-header').setDisplayed('none');
10328         }
10329         else if(this.enableHdMenu !== false){
10330             this.hmenu = new Ext.menu.Menu({id: this.id + '-hctx'});
10331             if(this.enableColumnHide !== false){
10332                 this.colMenu = new Ext.menu.Menu({id: this.id + '-hcols-menu'});
10333                 this.colMenu.on({
10334                     scope: this,
10335                     beforeshow: this.beforeColMenuShow,
10336                     itemclick: this.handleHdMenuClick
10337                 });
10338                 this.hmenu.add({
10339                     itemId:'columns',
10340                     hideOnClick: false,
10341                     text: this.columnsText,
10342                     menu: this.colMenu,
10343                     iconCls: 'x-cols-icon'
10344                 });
10345             }
10346             this.hmenu.on('itemclick', this.handleHdMenuClick, this);
10347         }
10348     },
10349
10350     setRootNode : function(node){
10351         node.attributes.uiProvider = Ext.ux.tree.TreeGridRootNodeUI;        
10352         node = Ext.ux.tree.TreeGrid.superclass.setRootNode.call(this, node);
10353         if(this.innerCt) {
10354             this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns});
10355         }
10356         return node;
10357     },
10358     
10359     clearInnerCt : function(){
10360         if(Ext.isIE){
10361             var dom = this.innerCt.dom;
10362             while(dom.firstChild){
10363                 dom.removeChild(dom.firstChild);
10364             }
10365         }else{
10366             Ext.ux.tree.TreeGrid.superclass.clearInnerCt.call(this);
10367         }
10368     },
10369     
10370     initEvents : function() {
10371         Ext.ux.tree.TreeGrid.superclass.initEvents.apply(this, arguments);
10372
10373         this.mon(this.innerBody, 'scroll', this.syncScroll, this);
10374         this.mon(this.innerHd, 'click', this.handleHdDown, this);
10375         this.mon(this.mainHd, {
10376             scope: this,
10377             mouseover: this.handleHdOver,
10378             mouseout: this.handleHdOut
10379         });
10380     },
10381     
10382     onResize : function(w, h) {
10383         Ext.ux.tree.TreeGrid.superclass.onResize.apply(this, arguments);
10384         
10385         var bd = this.innerBody.dom;
10386         var hd = this.innerHd.dom;
10387
10388         if(!bd){
10389             return;
10390         }
10391
10392         if(Ext.isNumber(h)){
10393             bd.style.height = this.body.getHeight(true) - hd.offsetHeight + 'px';
10394         }
10395
10396         if(Ext.isNumber(w)){                        
10397             var sw = Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
10398             if(this.reserveScrollOffset || ((bd.offsetWidth - bd.clientWidth) > 10)){
10399                 this.setScrollOffset(sw);
10400             }else{
10401                 var me = this;
10402                 setTimeout(function(){
10403                     me.setScrollOffset(bd.offsetWidth - bd.clientWidth > 10 ? sw : 0);
10404                 }, 10);
10405             }
10406         }
10407     },
10408
10409     updateColumnWidths : function() {
10410         var cols = this.columns,
10411             colCount = cols.length,
10412             groups = this.outerCt.query('colgroup'),
10413             groupCount = groups.length,
10414             c, g, i, j;
10415
10416         for(i = 0; i<colCount; i++) {
10417             c = cols[i];
10418             for(j = 0; j<groupCount; j++) {
10419                 g = groups[j];
10420                 g.childNodes[i].style.width = (c.hidden ? 0 : c.width) + 'px';
10421             }
10422         }
10423         
10424         for(i = 0, groups = this.innerHd.query('td'), len = groups.length; i<len; i++) {
10425             c = Ext.fly(groups[i]);
10426             if(cols[i] && cols[i].hidden) {
10427                 c.addClass('x-treegrid-hd-hidden');
10428             }
10429             else {
10430                 c.removeClass('x-treegrid-hd-hidden');
10431             }
10432         }
10433
10434         var tcw = this.getTotalColumnWidth();                        
10435         Ext.fly(this.innerHd.dom.firstChild).setWidth(tcw + (this.scrollOffset || 0));
10436         this.outerCt.select('table').setWidth(tcw);
10437         this.syncHeaderScroll();    
10438     },
10439                     
10440     getVisibleColumns : function() {
10441         var columns = [],
10442             cs = this.columns,
10443             len = cs.length,
10444             i;
10445             
10446         for(i = 0; i<len; i++) {
10447             if(!cs[i].hidden) {
10448                 columns.push(cs[i]);
10449             }
10450         }        
10451         return columns;
10452     },
10453
10454     getTotalColumnWidth : function() {
10455         var total = 0;
10456         for(var i = 0, cs = this.getVisibleColumns(), len = cs.length; i<len; i++) {
10457             total += cs[i].width;
10458         }
10459         return total;
10460     },
10461
10462     setScrollOffset : function(scrollOffset) {
10463         this.scrollOffset = scrollOffset;                        
10464         this.updateColumnWidths();
10465     },
10466
10467     // private
10468     handleHdDown : function(e, t){
10469         var hd = e.getTarget('.x-treegrid-hd');
10470
10471         if(hd && Ext.fly(t).hasClass('x-grid3-hd-btn')){
10472             var ms = this.hmenu.items,
10473                 cs = this.columns,
10474                 index = this.findHeaderIndex(hd),
10475                 c = cs[index],
10476                 sort = c.sortable;
10477                 
10478             e.stopEvent();
10479             Ext.fly(hd).addClass('x-grid3-hd-menu-open');
10480             this.hdCtxIndex = index;
10481             
10482             this.fireEvent('headerbuttonclick', ms, c, hd, index);
10483             
10484             this.hmenu.on('hide', function(){
10485                 Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
10486             }, this, {single:true});
10487             
10488             this.hmenu.show(t, 'tl-bl?');
10489         }
10490         else if(hd) {
10491             var index = this.findHeaderIndex(hd);
10492             this.fireEvent('headerclick', this.columns[index], hd, index);
10493         }
10494     },
10495
10496     // private
10497     handleHdOver : function(e, t){                    
10498         var hd = e.getTarget('.x-treegrid-hd');                        
10499         if(hd && !this.headersDisabled){
10500             index = this.findHeaderIndex(hd);
10501             this.activeHdRef = t;
10502             this.activeHdIndex = index;
10503             var el = Ext.get(hd);
10504             this.activeHdRegion = el.getRegion();
10505             el.addClass('x-grid3-hd-over');
10506             this.activeHdBtn = el.child('.x-grid3-hd-btn');
10507             if(this.activeHdBtn){
10508                 this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight-1)+'px';
10509             }
10510         }
10511     },
10512     
10513     // private
10514     handleHdOut : function(e, t){
10515         var hd = e.getTarget('.x-treegrid-hd');
10516         if(hd && (!Ext.isIE || !e.within(hd, true))){
10517             this.activeHdRef = null;
10518             Ext.fly(hd).removeClass('x-grid3-hd-over');
10519             hd.style.cursor = '';
10520         }
10521     },
10522                     
10523     findHeaderIndex : function(hd){
10524         hd = hd.dom || hd;
10525         var cs = hd.parentNode.childNodes;
10526         for(var i = 0, c; c = cs[i]; i++){
10527             if(c == hd){
10528                 return i;
10529             }
10530         }
10531         return -1;
10532     },
10533     
10534     // private
10535     beforeColMenuShow : function(){
10536         var cols = this.columns,  
10537             colCount = cols.length,
10538             i, c;                        
10539         this.colMenu.removeAll();                    
10540         for(i = 1; i < colCount; i++){
10541             c = cols[i];
10542             if(c.hideable !== false){
10543                 this.colMenu.add(new Ext.menu.CheckItem({
10544                     itemId: 'col-' + i,
10545                     text: c.header,
10546                     checked: !c.hidden,
10547                     hideOnClick:false,
10548                     disabled: c.hideable === false
10549                 }));
10550             }
10551         }
10552     },
10553                     
10554     // private
10555     handleHdMenuClick : function(item){
10556         var index = this.hdCtxIndex,
10557             id = item.getItemId();
10558         
10559         if(this.fireEvent('headermenuclick', this.columns[index], id, index) !== false) {
10560             index = id.substr(4);
10561             if(index > 0 && this.columns[index]) {
10562                 this.setColumnVisible(index, !item.checked);
10563             }     
10564         }
10565         
10566         return true;
10567     },
10568     
10569     setColumnVisible : function(index, visible) {
10570         this.columns[index].hidden = !visible;        
10571         this.updateColumnWidths();
10572     },
10573
10574     /**
10575      * Scrolls the grid to the top
10576      */
10577     scrollToTop : function(){
10578         this.innerBody.dom.scrollTop = 0;
10579         this.innerBody.dom.scrollLeft = 0;
10580     },
10581
10582     // private
10583     syncScroll : function(){
10584         this.syncHeaderScroll();
10585         var mb = this.innerBody.dom;
10586         this.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
10587     },
10588
10589     // private
10590     syncHeaderScroll : function(){
10591         var mb = this.innerBody.dom;
10592         this.innerHd.dom.scrollLeft = mb.scrollLeft;
10593         this.innerHd.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
10594     },
10595     
10596     registerNode : function(n) {
10597         Ext.ux.tree.TreeGrid.superclass.registerNode.call(this, n);
10598         if(!n.uiProvider && !n.isRoot && !n.ui.isTreeGridNodeUI) {
10599             n.ui = new Ext.ux.tree.TreeGridNodeUI(n);
10600         }
10601     }
10602 });
10603
10604 Ext.reg('treegrid', Ext.ux.tree.TreeGrid);