Upgrade to ExtJS 3.1.1 - Released 02/08/2010
[extjs.git] / examples / ux / ux-all-debug.js
1 /*!
2  * Ext JS Library 3.1.1
3  * Copyright(c) 2006-2010 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.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                 var 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                 var sc = this.scroller.dom.scrollTop;
86                 var 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, ct = ts.cell, rt = ts.row, rb = ts.rowBody, last = colCount-1;
95                 var rh = this.getStyleRowHeight();
96                 var vr = this.getVisibleRows();
97                 var tstyle = 'width:'+this.getTotalWidth()+';height:'+rh+'px;';
98                 // buffers
99                 var buf = [], cb, c, p = {}, rp = {tstyle: tstyle}, r;
100                 for (var j = 0, len = rs.length; j < len; j++) {
101                         r = rs[j]; cb = [];
102                         var rowIndex = (j+startRow);
103                         var visible = rowIndex >= vr.first && rowIndex <= vr.last;
104                         if (visible) {
105                                 for (var i = 0; i < colCount; i++) {
106                                         c = cs[i];
107                                         p.id = c.id;
108                                         p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
109                                         p.attr = p.cellAttr = "";
110                                         p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
111                                         p.style = c.style;
112                                         if (p.value == undefined || p.value === "") {
113                                                 p.value = "&#160;";
114                                         }
115                                         if (r.dirty && typeof r.modified[c.name] !== 'undefined') {
116                                                 p.css += ' x-grid3-dirty-cell';
117                                         }
118                                         cb[cb.length] = ct.apply(p);
119                                 }
120                         }
121                         var alt = [];
122                         if(stripe && ((rowIndex+1) % 2 == 0)){
123                             alt[0] = "x-grid3-row-alt";
124                         }
125                         if(r.dirty){
126                             alt[1] = " x-grid3-dirty-row";
127                         }
128                         rp.cols = colCount;
129                         if(this.getRowClass){
130                             alt[2] = this.getRowClass(r, rowIndex, rp, ds);
131                         }
132                         rp.alt = alt.join(" ");
133                         rp.cells = cb.join("");
134                         buf[buf.length] =  !visible ? ts.rowHolder.apply(rp) : (onlyBody ? rb.apply(rp) : rt.apply(rp));
135                 }
136                 return buf.join("");
137         },
138
139         isRowRendered: function(index){
140                 var row = this.getRow(index);
141                 return row && row.childNodes.length > 0;
142         },
143
144         syncScroll: function(){
145                 Ext.ux.grid.BufferView.superclass.syncScroll.apply(this, arguments);
146                 this.update();
147         },
148
149         // a (optionally) buffered method to update contents of gridview
150         update: function(){
151                 if (this.scrollDelay) {
152                         if (!this.renderTask) {
153                                 this.renderTask = new Ext.util.DelayedTask(this.doUpdate, this);
154                         }
155                         this.renderTask.delay(this.scrollDelay);
156                 }else{
157                         this.doUpdate();
158                 }
159         },
160     
161     onRemove : function(ds, record, index, isUpdate){
162         Ext.ux.grid.BufferView.superclass.onRemove.apply(this, arguments);
163         if(isUpdate !== true){
164             this.update();
165         }
166     },
167
168         doUpdate: function(){
169                 if (this.getVisibleRowCount() > 0) {
170                         var g = this.grid, cm = g.colModel, ds = g.store;
171                         var cs = this.getColumnData();
172
173                         var vr = this.getVisibleRows();
174                         for (var i = vr.first; i <= vr.last; i++) {
175                                 // if row is NOT rendered and is visible, render it
176                                 if(!this.isRowRendered(i)){
177                                         var html = this.doRender(cs, [ds.getAt(i)], ds, i, cm.getColumnCount(), g.stripeRows, true);
178                                         this.getRow(i).innerHTML = html;
179                                 }
180                         }
181                         this.clean();
182                 }
183         },
184
185         // a buffered method to clean rows
186         clean : function(){
187                 if(!this.cleanTask){
188                         this.cleanTask = new Ext.util.DelayedTask(this.doClean, this);
189                 }
190                 this.cleanTask.delay(this.cleanDelay);
191         },
192
193         doClean: function(){
194                 if (this.getVisibleRowCount() > 0) {
195                         var vr = this.getVisibleRows();
196                         vr.first -= this.cacheSize;
197                         vr.last += this.cacheSize;
198
199                         var i = 0, rows = this.getRows();
200                         // if first is less than 0, all rows have been rendered
201                         // so lets clean the end...
202                         if(vr.first <= 0){
203                                 i = vr.last + 1;
204                         }
205                         for(var len = this.ds.getCount(); i < len; i++){
206                                 // if current row is outside of first and last and
207                                 // has content, update the innerHTML to nothing
208                                 if ((i < vr.first || i > vr.last) && rows[i].innerHTML) {
209                                         rows[i].innerHTML = '';
210                                 }
211                         }
212                 }
213         },
214
215         layout: function(){
216                 Ext.ux.grid.BufferView.superclass.layout.call(this);
217                 this.update();
218         }
219 });
220 // We are adding these custom layouts to a namespace that does not
221 // exist by default in Ext, so we have to add the namespace first:
222 Ext.ns('Ext.ux.layout');
223
224 /**
225  * @class Ext.ux.layout.CenterLayout
226  * @extends Ext.layout.FitLayout
227  * <p>This is a very simple layout style used to center contents within a container.  This layout works within
228  * nested containers and can also be used as expected as a Viewport layout to center the page layout.</p>
229  * <p>As a subclass of FitLayout, CenterLayout expects to have a single child panel of the container that uses
230  * the layout.  The layout does not require any config options, although the child panel contained within the
231  * layout must provide a fixed or percentage width.  The child panel's height will fit to the container by
232  * default, but you can specify <tt>autoHeight:true</tt> to allow it to autosize based on its content height.
233  * Example usage:</p>
234  * <pre><code>
235 // The content panel is centered in the container
236 var p = new Ext.Panel({
237     title: 'Center Layout',
238     layout: 'ux.center',
239     items: [{
240         title: 'Centered Content',
241         width: '75%',
242         html: 'Some content'
243     }]
244 });
245
246 // If you leave the title blank and specify no border
247 // you'll create a non-visual, structural panel just
248 // for centering the contents in the main container.
249 var p = new Ext.Panel({
250     layout: 'ux.center',
251     border: false,
252     items: [{
253         title: 'Centered Content',
254         width: 300,
255         autoHeight: true,
256         html: 'Some content'
257     }]
258 });
259 </code></pre>
260  */
261 Ext.ux.layout.CenterLayout = Ext.extend(Ext.layout.FitLayout, {
262         // private
263     setItemSize : function(item, size){
264         this.container.addClass('ux-layout-center');
265         item.addClass('ux-layout-center-item');
266         if(item && size.height > 0){
267             if(item.width){
268                 size.width = item.width;
269             }
270             item.setSize(size);
271         }
272     }
273 });
274
275 Ext.Container.LAYOUTS['ux.center'] = Ext.ux.layout.CenterLayout;
276 Ext.ns('Ext.ux.grid');\r
277 \r
278 /**\r
279  * @class Ext.ux.grid.CheckColumn\r
280  * @extends Object\r
281  * GridPanel plugin to add a column with check boxes to a grid.\r
282  * <p>Example usage:</p>\r
283  * <pre><code>\r
284 // create the column\r
285 var checkColumn = new Ext.grid.CheckColumn({\r
286    header: 'Indoor?',\r
287    dataIndex: 'indoor',\r
288    id: 'check',\r
289    width: 55\r
290 });\r
291 \r
292 // add the column to the column model\r
293 var cm = new Ext.grid.ColumnModel([{\r
294        header: 'Foo',\r
295        ...\r
296     },\r
297     checkColumn\r
298 ]);\r
299 \r
300 // create the grid\r
301 var grid = new Ext.grid.EditorGridPanel({\r
302     ...\r
303     cm: cm,\r
304     plugins: [checkColumn], // include plugin\r
305     ...\r
306 });\r
307  * </code></pre>\r
308  * In addition to storing a Boolean value within the record data, this\r
309  * class toggles a css class between <tt>'x-grid3-check-col'</tt> and\r
310  * <tt>'x-grid3-check-col-on'</tt> to alter the background image used for\r
311  * a column.\r
312  */\r
313 Ext.ux.grid.CheckColumn = function(config){\r
314     Ext.apply(this, config);\r
315     if(!this.id){\r
316         this.id = Ext.id();\r
317     }\r
318     this.renderer = this.renderer.createDelegate(this);\r
319 };\r
320 \r
321 Ext.ux.grid.CheckColumn.prototype ={\r
322     init : function(grid){\r
323         this.grid = grid;\r
324         this.grid.on('render', function(){\r
325             var view = this.grid.getView();\r
326             view.mainBody.on('mousedown', this.onMouseDown, this);\r
327         }, this);\r
328     },\r
329 \r
330     onMouseDown : function(e, t){\r
331         if(Ext.fly(t).hasClass(this.createId())){\r
332             e.stopEvent();\r
333             var index = this.grid.getView().findRowIndex(t);\r
334             var record = this.grid.store.getAt(index);\r
335             record.set(this.dataIndex, !record.data[this.dataIndex]);\r
336         }\r
337     },\r
338 \r
339     renderer : function(v, p, record){\r
340         p.css += ' x-grid3-check-col-td'; \r
341         return String.format('<div class="x-grid3-check-col{0} {1}">&#160;</div>', v ? '-on' : '', this.createId());\r
342     },\r
343     \r
344     createId : function(){\r
345         return 'x-grid3-cc-' + this.id;\r
346     }\r
347 };\r
348 \r
349 // register ptype\r
350 Ext.preg('checkcolumn', Ext.ux.grid.CheckColumn);\r
351 \r
352 // backwards compat\r
353 Ext.grid.CheckColumn = Ext.ux.grid.CheckColumn;Ext.ns('Ext.ux.grid');\r
354 \r
355 Ext.ux.grid.ColumnHeaderGroup = Ext.extend(Ext.util.Observable, {\r
356 \r
357     constructor: function(config){\r
358         this.config = config;\r
359     },\r
360 \r
361     init: function(grid){\r
362         Ext.applyIf(grid.colModel, this.config);\r
363         Ext.apply(grid.getView(), this.viewConfig);\r
364     },\r
365 \r
366     viewConfig: {\r
367         initTemplates: function(){\r
368             this.constructor.prototype.initTemplates.apply(this, arguments);\r
369             var ts = this.templates || {};\r
370             if(!ts.gcell){\r
371                 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>');\r
372             }\r
373             this.templates = ts;\r
374             this.hrowRe = new RegExp("ux-grid-hd-group-row-(\\d+)", "");\r
375         },\r
376 \r
377         renderHeaders: function(){\r
378             var ts = this.templates, headers = [], cm = this.cm, rows = cm.rows, tstyle = 'width:' + this.getTotalWidth() + ';';\r
379 \r
380             for(var row = 0, rlen = rows.length; row < rlen; row++){\r
381                 var r = rows[row], cells = [];\r
382                 for(var i = 0, gcol = 0, len = r.length; i < len; i++){\r
383                     var group = r[i];\r
384                     group.colspan = group.colspan || 1;\r
385                     var id = this.getColumnId(group.dataIndex ? cm.findColumnIndex(group.dataIndex) : gcol), gs = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupStyle.call(this, group, gcol);\r
386                     cells[i] = ts.gcell.apply({\r
387                         cls: 'ux-grid-hd-group-cell',\r
388                         id: id,\r
389                         row: row,\r
390                         style: 'width:' + gs.width + ';' + (gs.hidden ? 'display:none;' : '') + (group.align ? 'text-align:' + group.align + ';' : ''),\r
391                         tooltip: group.tooltip ? (Ext.QuickTips.isEnabled() ? 'ext:qtip' : 'title') + '="' + group.tooltip + '"' : '',\r
392                         istyle: group.align == 'right' ? 'padding-right:16px' : '',\r
393                         btn: this.grid.enableHdMenu && group.header,\r
394                         value: group.header || '&nbsp;'\r
395                     });\r
396                     gcol += group.colspan;\r
397                 }\r
398                 headers[row] = ts.header.apply({\r
399                     tstyle: tstyle,\r
400                     cells: cells.join('')\r
401                 });\r
402             }\r
403             headers.push(this.constructor.prototype.renderHeaders.apply(this, arguments));\r
404             return headers.join('');\r
405         },\r
406 \r
407         onColumnWidthUpdated: function(){\r
408             this.constructor.prototype.onColumnWidthUpdated.apply(this, arguments);\r
409             Ext.ux.grid.ColumnHeaderGroup.prototype.updateGroupStyles.call(this);\r
410         },\r
411 \r
412         onAllColumnWidthsUpdated: function(){\r
413             this.constructor.prototype.onAllColumnWidthsUpdated.apply(this, arguments);\r
414             Ext.ux.grid.ColumnHeaderGroup.prototype.updateGroupStyles.call(this);\r
415         },\r
416 \r
417         onColumnHiddenUpdated: function(){\r
418             this.constructor.prototype.onColumnHiddenUpdated.apply(this, arguments);\r
419             Ext.ux.grid.ColumnHeaderGroup.prototype.updateGroupStyles.call(this);\r
420         },\r
421 \r
422         getHeaderCell: function(index){\r
423             return this.mainHd.query(this.cellSelector)[index];\r
424         },\r
425 \r
426         findHeaderCell: function(el){\r
427             return el ? this.fly(el).findParent('td.x-grid3-hd', this.cellSelectorDepth) : false;\r
428         },\r
429 \r
430         findHeaderIndex: function(el){\r
431             var cell = this.findHeaderCell(el);\r
432             return cell ? this.getCellIndex(cell) : false;\r
433         },\r
434 \r
435         updateSortIcon: function(col, dir){\r
436             var sc = this.sortClasses, hds = this.mainHd.select(this.cellSelector).removeClass(sc);\r
437             hds.item(col).addClass(sc[dir == "DESC" ? 1 : 0]);\r
438         },\r
439 \r
440         handleHdDown: function(e, t){\r
441             var el = Ext.get(t);\r
442             if(el.hasClass('x-grid3-hd-btn')){\r
443                 e.stopEvent();\r
444                 var hd = this.findHeaderCell(t);\r
445                 Ext.fly(hd).addClass('x-grid3-hd-menu-open');\r
446                 var index = this.getCellIndex(hd);\r
447                 this.hdCtxIndex = index;\r
448                 var ms = this.hmenu.items, cm = this.cm;\r
449                 ms.get('asc').setDisabled(!cm.isSortable(index));\r
450                 ms.get('desc').setDisabled(!cm.isSortable(index));\r
451                 this.hmenu.on('hide', function(){\r
452                     Ext.fly(hd).removeClass('x-grid3-hd-menu-open');\r
453                 }, this, {\r
454                     single: true\r
455                 });\r
456                 this.hmenu.show(t, 'tl-bl?');\r
457             }else if(el.hasClass('ux-grid-hd-group-cell') || Ext.fly(t).up('.ux-grid-hd-group-cell')){\r
458                 e.stopEvent();\r
459             }\r
460         },\r
461 \r
462         handleHdMove: function(e, t){\r
463             var hd = this.findHeaderCell(this.activeHdRef);\r
464             if(hd && !this.headersDisabled && !Ext.fly(hd).hasClass('ux-grid-hd-group-cell')){\r
465                 var hw = this.splitHandleWidth || 5, r = this.activeHdRegion, x = e.getPageX(), ss = hd.style, cur = '';\r
466                 if(this.grid.enableColumnResize !== false){\r
467                     if(x - r.left <= hw && this.cm.isResizable(this.activeHdIndex - 1)){\r
468                         cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize'; // col-resize\r
469                                                                                                 // not\r
470                                                                                                 // always\r
471                                                                                                 // supported\r
472                     }else if(r.right - x <= (!this.activeHdBtn ? hw : 2) && this.cm.isResizable(this.activeHdIndex)){\r
473                         cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize';\r
474                     }\r
475                 }\r
476                 ss.cursor = cur;\r
477             }\r
478         },\r
479 \r
480         handleHdOver: function(e, t){\r
481             var hd = this.findHeaderCell(t);\r
482             if(hd && !this.headersDisabled){\r
483                 this.activeHdRef = t;\r
484                 this.activeHdIndex = this.getCellIndex(hd);\r
485                 var fly = this.fly(hd);\r
486                 this.activeHdRegion = fly.getRegion();\r
487                 if(!(this.cm.isMenuDisabled(this.activeHdIndex) || fly.hasClass('ux-grid-hd-group-cell'))){\r
488                     fly.addClass('x-grid3-hd-over');\r
489                     this.activeHdBtn = fly.child('.x-grid3-hd-btn');\r
490                     if(this.activeHdBtn){\r
491                         this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight - 1) + 'px';\r
492                     }\r
493                 }\r
494             }\r
495         },\r
496 \r
497         handleHdOut: function(e, t){\r
498             var hd = this.findHeaderCell(t);\r
499             if(hd && (!Ext.isIE || !e.within(hd, true))){\r
500                 this.activeHdRef = null;\r
501                 this.fly(hd).removeClass('x-grid3-hd-over');\r
502                 hd.style.cursor = '';\r
503             }\r
504         },\r
505 \r
506         handleHdMenuClick: function(item){\r
507             var index = this.hdCtxIndex, cm = this.cm, ds = this.ds, id = item.getItemId();\r
508             switch(id){\r
509                 case 'asc':\r
510                     ds.sort(cm.getDataIndex(index), 'ASC');\r
511                     break;\r
512                 case 'desc':\r
513                     ds.sort(cm.getDataIndex(index), 'DESC');\r
514                     break;\r
515                 default:\r
516                     if(id.substr(0, 5) == 'group'){\r
517                         var i = id.split('-'), row = parseInt(i[1], 10), col = parseInt(i[2], 10), r = this.cm.rows[row], group, gcol = 0;\r
518                         for(var i = 0, len = r.length; i < len; i++){\r
519                             group = r[i];\r
520                             if(col >= gcol && col < gcol + group.colspan){\r
521                                 break;\r
522                             }\r
523                             gcol += group.colspan;\r
524                         }\r
525                         if(item.checked){\r
526                             var max = cm.getColumnsBy(this.isHideableColumn, this).length;\r
527                             for(var i = gcol, len = gcol + group.colspan; i < len; i++){\r
528                                 if(!cm.isHidden(i)){\r
529                                     max--;\r
530                                 }\r
531                             }\r
532                             if(max < 1){\r
533                                 this.onDenyColumnHide();\r
534                                 return false;\r
535                             }\r
536                         }\r
537                         for(var i = gcol, len = gcol + group.colspan; i < len; i++){\r
538                             if(cm.config[i].fixed !== true && cm.config[i].hideable !== false){\r
539                                 cm.setHidden(i, item.checked);\r
540                             }\r
541                         }\r
542                     }else{\r
543                         index = cm.getIndexById(id.substr(4));\r
544                         if(index != -1){\r
545                             if(item.checked && cm.getColumnsBy(this.isHideableColumn, this).length <= 1){\r
546                                 this.onDenyColumnHide();\r
547                                 return false;\r
548                             }\r
549                             cm.setHidden(index, item.checked);\r
550                         }\r
551                     }\r
552                     item.checked = !item.checked;\r
553                     if(item.menu){\r
554                         var updateChildren = function(menu){\r
555                             menu.items.each(function(childItem){\r
556                                 if(!childItem.disabled){\r
557                                     childItem.setChecked(item.checked, false);\r
558                                     if(childItem.menu){\r
559                                         updateChildren(childItem.menu);\r
560                                     }\r
561                                 }\r
562                             });\r
563                         }\r
564                         updateChildren(item.menu);\r
565                     }\r
566                     var parentMenu = item, parentItem;\r
567                     while(parentMenu = parentMenu.parentMenu){\r
568                         if(!parentMenu.parentMenu || !(parentItem = parentMenu.parentMenu.items.get(parentMenu.getItemId())) || !parentItem.setChecked){\r
569                             break;\r
570                         }\r
571                         var checked = parentMenu.items.findIndexBy(function(m){\r
572                             return m.checked;\r
573                         }) >= 0;\r
574                         parentItem.setChecked(checked, true);\r
575                     }\r
576                     item.checked = !item.checked;\r
577             }\r
578             return true;\r
579         },\r
580 \r
581         beforeColMenuShow: function(){\r
582             var cm = this.cm, rows = this.cm.rows;\r
583             this.colMenu.removeAll();\r
584             for(var col = 0, clen = cm.getColumnCount(); col < clen; col++){\r
585                 var menu = this.colMenu, title = cm.getColumnHeader(col), text = [];\r
586                 if(cm.config[col].fixed !== true && cm.config[col].hideable !== false){\r
587                     for(var row = 0, rlen = rows.length; row < rlen; row++){\r
588                         var r = rows[row], group, gcol = 0;\r
589                         for(var i = 0, len = r.length; i < len; i++){\r
590                             group = r[i];\r
591                             if(col >= gcol && col < gcol + group.colspan){\r
592                                 break;\r
593                             }\r
594                             gcol += group.colspan;\r
595                         }\r
596                         if(group && group.header){\r
597                             if(cm.hierarchicalColMenu){\r
598                                 var gid = 'group-' + row + '-' + gcol;\r
599                                 var item = menu.items.item(gid);\r
600                                 var submenu = item ? item.menu : null;\r
601                                 if(!submenu){\r
602                                     submenu = new Ext.menu.Menu({\r
603                                         itemId: gid\r
604                                     });\r
605                                     submenu.on("itemclick", this.handleHdMenuClick, this);\r
606                                     var checked = false, disabled = true;\r
607                                     for(var c = gcol, lc = gcol + group.colspan; c < lc; c++){\r
608                                         if(!cm.isHidden(c)){\r
609                                             checked = true;\r
610                                         }\r
611                                         if(cm.config[c].hideable !== false){\r
612                                             disabled = false;\r
613                                         }\r
614                                     }\r
615                                     menu.add({\r
616                                         itemId: gid,\r
617                                         text: group.header,\r
618                                         menu: submenu,\r
619                                         hideOnClick: false,\r
620                                         checked: checked,\r
621                                         disabled: disabled\r
622                                     });\r
623                                 }\r
624                                 menu = submenu;\r
625                             }else{\r
626                                 text.push(group.header);\r
627                             }\r
628                         }\r
629                     }\r
630                     text.push(title);\r
631                     menu.add(new Ext.menu.CheckItem({\r
632                         itemId: "col-" + cm.getColumnId(col),\r
633                         text: text.join(' '),\r
634                         checked: !cm.isHidden(col),\r
635                         hideOnClick: false,\r
636                         disabled: cm.config[col].hideable === false\r
637                     }));\r
638                 }\r
639             }\r
640         },\r
641 \r
642         renderUI: function(){\r
643             this.constructor.prototype.renderUI.apply(this, arguments);\r
644             Ext.apply(this.columnDrop, Ext.ux.grid.ColumnHeaderGroup.prototype.columnDropConfig);\r
645             Ext.apply(this.splitZone, Ext.ux.grid.ColumnHeaderGroup.prototype.splitZoneConfig);\r
646         }\r
647     },\r
648 \r
649     splitZoneConfig: {\r
650         allowHeaderDrag: function(e){\r
651             return !e.getTarget(null, null, true).hasClass('ux-grid-hd-group-cell');\r
652         }\r
653     },\r
654 \r
655     columnDropConfig: {\r
656         getTargetFromEvent: function(e){\r
657             var t = Ext.lib.Event.getTarget(e);\r
658             return this.view.findHeaderCell(t);\r
659         },\r
660 \r
661         positionIndicator: function(h, n, e){\r
662             var data = Ext.ux.grid.ColumnHeaderGroup.prototype.getDragDropData.call(this, h, n, e);\r
663             if(data === false){\r
664                 return false;\r
665             }\r
666             var px = data.px + this.proxyOffsets[0];\r
667             this.proxyTop.setLeftTop(px, data.r.top + this.proxyOffsets[1]);\r
668             this.proxyTop.show();\r
669             this.proxyBottom.setLeftTop(px, data.r.bottom);\r
670             this.proxyBottom.show();\r
671             return data.pt;\r
672         },\r
673 \r
674         onNodeDrop: function(n, dd, e, data){\r
675             var h = data.header;\r
676             if(h != n){\r
677                 var d = Ext.ux.grid.ColumnHeaderGroup.prototype.getDragDropData.call(this, h, n, e);\r
678                 if(d === false){\r
679                     return false;\r
680                 }\r
681                 var cm = this.grid.colModel, right = d.oldIndex < d.newIndex, rows = cm.rows;\r
682                 for(var row = d.row, rlen = rows.length; row < rlen; row++){\r
683                     var r = rows[row], len = r.length, fromIx = 0, span = 1, toIx = len;\r
684                     for(var i = 0, gcol = 0; i < len; i++){\r
685                         var group = r[i];\r
686                         if(d.oldIndex >= gcol && d.oldIndex < gcol + group.colspan){\r
687                             fromIx = i;\r
688                         }\r
689                         if(d.oldIndex + d.colspan - 1 >= gcol && d.oldIndex + d.colspan - 1 < gcol + group.colspan){\r
690                             span = i - fromIx + 1;\r
691                         }\r
692                         if(d.newIndex >= gcol && d.newIndex < gcol + group.colspan){\r
693                             toIx = i;\r
694                         }\r
695                         gcol += group.colspan;\r
696                     }\r
697                     var groups = r.splice(fromIx, span);\r
698                     rows[row] = r.splice(0, toIx - (right ? span : 0)).concat(groups).concat(r);\r
699                 }\r
700                 for(var c = 0; c < d.colspan; c++){\r
701                     var oldIx = d.oldIndex + (right ? 0 : c), newIx = d.newIndex + (right ? -1 : c);\r
702                     cm.moveColumn(oldIx, newIx);\r
703                     this.grid.fireEvent("columnmove", oldIx, newIx);\r
704                 }\r
705                 return true;\r
706             }\r
707             return false;\r
708         }\r
709     },\r
710 \r
711     getGroupStyle: function(group, gcol){\r
712         var width = 0, hidden = true;\r
713         for(var i = gcol, len = gcol + group.colspan; i < len; i++){\r
714             if(!this.cm.isHidden(i)){\r
715                 var cw = this.cm.getColumnWidth(i);\r
716                 if(typeof cw == 'number'){\r
717                     width += cw;\r
718                 }\r
719                 hidden = false;\r
720             }\r
721         }\r
722         return {\r
723             width: (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2) ? width : Math.max(width - this.borderWidth, 0)) + 'px',\r
724             hidden: hidden\r
725         };\r
726     },\r
727 \r
728     updateGroupStyles: function(col){\r
729         var tables = this.mainHd.query('.x-grid3-header-offset > table'), tw = this.getTotalWidth(), rows = this.cm.rows;\r
730         for(var row = 0; row < tables.length; row++){\r
731             tables[row].style.width = tw;\r
732             if(row < rows.length){\r
733                 var cells = tables[row].firstChild.firstChild.childNodes;\r
734                 for(var i = 0, gcol = 0; i < cells.length; i++){\r
735                     var group = rows[row][i];\r
736                     if((typeof col != 'number') || (col >= gcol && col < gcol + group.colspan)){\r
737                         var gs = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupStyle.call(this, group, gcol);\r
738                         cells[i].style.width = gs.width;\r
739                         cells[i].style.display = gs.hidden ? 'none' : '';\r
740                     }\r
741                     gcol += group.colspan;\r
742                 }\r
743             }\r
744         }\r
745     },\r
746 \r
747     getGroupRowIndex: function(el){\r
748         if(el){\r
749             var m = el.className.match(this.hrowRe);\r
750             if(m && m[1]){\r
751                 return parseInt(m[1], 10);\r
752             }\r
753         }\r
754         return this.cm.rows.length;\r
755     },\r
756 \r
757     getGroupSpan: function(row, col){\r
758         if(row < 0){\r
759             return {\r
760                 col: 0,\r
761                 colspan: this.cm.getColumnCount()\r
762             };\r
763         }\r
764         var r = this.cm.rows[row];\r
765         if(r){\r
766             for(var i = 0, gcol = 0, len = r.length; i < len; i++){\r
767                 var group = r[i];\r
768                 if(col >= gcol && col < gcol + group.colspan){\r
769                     return {\r
770                         col: gcol,\r
771                         colspan: group.colspan\r
772                     };\r
773                 }\r
774                 gcol += group.colspan;\r
775             }\r
776             return {\r
777                 col: gcol,\r
778                 colspan: 0\r
779             };\r
780         }\r
781         return {\r
782             col: col,\r
783             colspan: 1\r
784         };\r
785     },\r
786 \r
787     getDragDropData: function(h, n, e){\r
788         if(h.parentNode != n.parentNode){\r
789             return false;\r
790         }\r
791         var cm = this.grid.colModel, x = Ext.lib.Event.getPageX(e), r = Ext.lib.Dom.getRegion(n.firstChild), px, pt;\r
792         if((r.right - x) <= (r.right - r.left) / 2){\r
793             px = r.right + this.view.borderWidth;\r
794             pt = "after";\r
795         }else{\r
796             px = r.left;\r
797             pt = "before";\r
798         }\r
799         var oldIndex = this.view.getCellIndex(h), newIndex = this.view.getCellIndex(n);\r
800         if(cm.isFixed(newIndex)){\r
801             return false;\r
802         }\r
803         var row = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupRowIndex.call(this.view, h),\r
804             oldGroup = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupSpan.call(this.view, row, oldIndex),\r
805             newGroup = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupSpan.call(this.view, row, newIndex),\r
806             oldIndex = oldGroup.col;\r
807             newIndex = newGroup.col + (pt == "after" ? newGroup.colspan : 0);\r
808         if(newIndex >= oldGroup.col && newIndex <= oldGroup.col + oldGroup.colspan){\r
809             return false;\r
810         }\r
811         var parentGroup = Ext.ux.grid.ColumnHeaderGroup.prototype.getGroupSpan.call(this.view, row - 1, oldIndex);\r
812         if(newIndex < parentGroup.col || newIndex > parentGroup.col + parentGroup.colspan){\r
813             return false;\r
814         }\r
815         return {\r
816             r: r,\r
817             px: px,\r
818             pt: pt,\r
819             row: row,\r
820             oldIndex: oldIndex,\r
821             newIndex: newIndex,\r
822             colspan: oldGroup.colspan\r
823         };\r
824     }\r
825 });Ext.ns('Ext.ux.tree');\r
826 \r
827 /**\r
828  * @class Ext.ux.tree.ColumnTree\r
829  * @extends Ext.tree.TreePanel\r
830  * \r
831  * @xtype columntree\r
832  */\r
833 Ext.ux.tree.ColumnTree = Ext.extend(Ext.tree.TreePanel, {\r
834     lines : false,\r
835     borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell\r
836     cls : 'x-column-tree',\r
837 \r
838     onRender : function(){\r
839         Ext.tree.ColumnTree.superclass.onRender.apply(this, arguments);\r
840         this.headers = this.header.createChild({cls:'x-tree-headers'});\r
841 \r
842         var cols = this.columns, c;\r
843         var totalWidth = 0;\r
844         var scrollOffset = 19; // similar to Ext.grid.GridView default\r
845 \r
846         for(var i = 0, len = cols.length; i < len; i++){\r
847              c = cols[i];\r
848              totalWidth += c.width;\r
849              this.headers.createChild({\r
850                  cls:'x-tree-hd ' + (c.cls?c.cls+'-hd':''),\r
851                  cn: {\r
852                      cls:'x-tree-hd-text',\r
853                      html: c.header\r
854                  },\r
855                  style:'width:'+(c.width-this.borderWidth)+'px;'\r
856              });\r
857         }\r
858         this.headers.createChild({cls:'x-clear'});\r
859         // prevent floats from wrapping when clipped\r
860         this.headers.setWidth(totalWidth+scrollOffset);\r
861         this.innerCt.setWidth(totalWidth);\r
862     }\r
863 });\r
864 \r
865 Ext.reg('columntree', Ext.ux.tree.ColumnTree);\r
866 \r
867 //backwards compat\r
868 Ext.tree.ColumnTree = Ext.ux.tree.ColumnTree;\r
869 \r
870 \r
871 /**\r
872  * @class Ext.ux.tree.ColumnNodeUI\r
873  * @extends Ext.tree.TreeNodeUI\r
874  */\r
875 Ext.ux.tree.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {\r
876     focus: Ext.emptyFn, // prevent odd scrolling behavior\r
877 \r
878     renderElements : function(n, a, targetNode, bulkRender){\r
879         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';\r
880 \r
881         var t = n.getOwnerTree();\r
882         var cols = t.columns;\r
883         var bw = t.borderWidth;\r
884         var c = cols[0];\r
885 \r
886         var buf = [\r
887              '<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf ', a.cls,'">',\r
888                 '<div class="x-tree-col" style="width:',c.width-bw,'px;">',\r
889                     '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",\r
890                     '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow">',\r
891                     '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on">',\r
892                     '<a hidefocus="on" class="x-tree-node-anchor" href="',a.href ? a.href : "#",'" tabIndex="1" ',\r
893                     a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '>',\r
894                     '<span unselectable="on">', n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</span></a>",\r
895                 "</div>"];\r
896          for(var i = 1, len = cols.length; i < len; i++){\r
897              c = cols[i];\r
898 \r
899              buf.push('<div class="x-tree-col ',(c.cls?c.cls:''),'" style="width:',c.width-bw,'px;">',\r
900                         '<div class="x-tree-col-text">',(c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),"</div>",\r
901                       "</div>");\r
902          }\r
903          buf.push(\r
904             '<div class="x-clear"></div></div>',\r
905             '<ul class="x-tree-node-ct" style="display:none;"></ul>',\r
906             "</li>");\r
907 \r
908         if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){\r
909             this.wrap = Ext.DomHelper.insertHtml("beforeBegin",\r
910                                 n.nextSibling.ui.getEl(), buf.join(""));\r
911         }else{\r
912             this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));\r
913         }\r
914 \r
915         this.elNode = this.wrap.childNodes[0];\r
916         this.ctNode = this.wrap.childNodes[1];\r
917         var cs = this.elNode.firstChild.childNodes;\r
918         this.indentNode = cs[0];\r
919         this.ecNode = cs[1];\r
920         this.iconNode = cs[2];\r
921         this.anchor = cs[3];\r
922         this.textNode = cs[3].firstChild;\r
923     }\r
924 });\r
925 \r
926 //backwards compat\r
927 Ext.tree.ColumnNodeUI = Ext.ux.tree.ColumnNodeUI;\r
928 /**\r
929  * @class Ext.DataView.LabelEditor\r
930  * @extends Ext.Editor\r
931  * \r
932  */\r
933 Ext.DataView.LabelEditor = Ext.extend(Ext.Editor, {\r
934     alignment: "tl-tl",\r
935     hideEl : false,\r
936     cls: "x-small-editor",\r
937     shim: false,\r
938     completeOnEnter: true,\r
939     cancelOnEsc: true,\r
940     labelSelector: 'span.x-editable',\r
941     \r
942     constructor: function(cfg, field){\r
943         Ext.DataView.LabelEditor.superclass.constructor.call(this,\r
944             field || new Ext.form.TextField({\r
945                 allowBlank: false,\r
946                 growMin:90,\r
947                 growMax:240,\r
948                 grow:true,\r
949                 selectOnFocus:true\r
950             }), cfg\r
951         );\r
952     },\r
953     \r
954     init : function(view){\r
955         this.view = view;\r
956         view.on('render', this.initEditor, this);\r
957         this.on('complete', this.onSave, this);\r
958     },\r
959 \r
960     initEditor : function(){\r
961         this.view.on({\r
962             scope: this,\r
963             containerclick: this.doBlur,\r
964             click: this.doBlur\r
965         });\r
966         this.view.getEl().on('mousedown', this.onMouseDown, this, {delegate: this.labelSelector});\r
967     },\r
968     \r
969     doBlur: function(){\r
970         if(this.editing){\r
971             this.field.blur();\r
972         }\r
973     },\r
974 \r
975     onMouseDown : function(e, target){\r
976         if(!e.ctrlKey && !e.shiftKey){\r
977             var item = this.view.findItemFromChild(target);\r
978             e.stopEvent();\r
979             var record = this.view.store.getAt(this.view.indexOf(item));\r
980             this.startEdit(target, record.data[this.dataIndex]);\r
981             this.activeRecord = record;\r
982         }else{\r
983             e.preventDefault();\r
984         }\r
985     },\r
986 \r
987     onSave : function(ed, value){\r
988         this.activeRecord.set(this.dataIndex, value);\r
989     }\r
990 });\r
991 \r
992 \r
993 Ext.DataView.DragSelector = function(cfg){\r
994     cfg = cfg || {};\r
995     var view, proxy, tracker;\r
996     var rs, bodyRegion, dragRegion = new Ext.lib.Region(0,0,0,0);\r
997     var dragSafe = cfg.dragSafe === true;\r
998 \r
999     this.init = function(dataView){\r
1000         view = dataView;\r
1001         view.on('render', onRender);\r
1002     };\r
1003 \r
1004     function fillRegions(){\r
1005         rs = [];\r
1006         view.all.each(function(el){\r
1007             rs[rs.length] = el.getRegion();\r
1008         });\r
1009         bodyRegion = view.el.getRegion();\r
1010     }\r
1011 \r
1012     function cancelClick(){\r
1013         return false;\r
1014     }\r
1015 \r
1016     function onBeforeStart(e){\r
1017         return !dragSafe || e.target == view.el.dom;\r
1018     }\r
1019 \r
1020     function onStart(e){\r
1021         view.on('containerclick', cancelClick, view, {single:true});\r
1022         if(!proxy){\r
1023             proxy = view.el.createChild({cls:'x-view-selector'});\r
1024         }else{\r
1025             if(proxy.dom.parentNode !== view.el.dom){\r
1026                 view.el.dom.appendChild(proxy.dom);\r
1027             }\r
1028             proxy.setDisplayed('block');\r
1029         }\r
1030         fillRegions();\r
1031         view.clearSelections();\r
1032     }\r
1033 \r
1034     function onDrag(e){\r
1035         var startXY = tracker.startXY;\r
1036         var xy = tracker.getXY();\r
1037 \r
1038         var x = Math.min(startXY[0], xy[0]);\r
1039         var y = Math.min(startXY[1], xy[1]);\r
1040         var w = Math.abs(startXY[0] - xy[0]);\r
1041         var h = Math.abs(startXY[1] - xy[1]);\r
1042 \r
1043         dragRegion.left = x;\r
1044         dragRegion.top = y;\r
1045         dragRegion.right = x+w;\r
1046         dragRegion.bottom = y+h;\r
1047 \r
1048         dragRegion.constrainTo(bodyRegion);\r
1049         proxy.setRegion(dragRegion);\r
1050 \r
1051         for(var i = 0, len = rs.length; i < len; i++){\r
1052             var r = rs[i], sel = dragRegion.intersect(r);\r
1053             if(sel && !r.selected){\r
1054                 r.selected = true;\r
1055                 view.select(i, true);\r
1056             }else if(!sel && r.selected){\r
1057                 r.selected = false;\r
1058                 view.deselect(i);\r
1059             }\r
1060         }\r
1061     }\r
1062 \r
1063     function onEnd(e){\r
1064         if (!Ext.isIE) {\r
1065             view.un('containerclick', cancelClick, view);    \r
1066         }        \r
1067         if(proxy){\r
1068             proxy.setDisplayed(false);\r
1069         }\r
1070     }\r
1071 \r
1072     function onRender(view){\r
1073         tracker = new Ext.dd.DragTracker({\r
1074             onBeforeStart: onBeforeStart,\r
1075             onStart: onStart,\r
1076             onDrag: onDrag,\r
1077             onEnd: onEnd\r
1078         });\r
1079         tracker.initEl(view.el);\r
1080     }\r
1081 };Ext.ns('Ext.ux.form');
1082
1083 /**
1084  * @class Ext.ux.form.FileUploadField
1085  * @extends Ext.form.TextField
1086  * Creates a file upload field.
1087  * @xtype fileuploadfield
1088  */
1089 Ext.ux.form.FileUploadField = Ext.extend(Ext.form.TextField,  {
1090     /**
1091      * @cfg {String} buttonText The button text to display on the upload button (defaults to
1092      * 'Browse...').  Note that if you supply a value for {@link #buttonCfg}, the buttonCfg.text
1093      * value will be used instead if available.
1094      */
1095     buttonText: 'Browse...',
1096     /**
1097      * @cfg {Boolean} buttonOnly True to display the file upload field as a button with no visible
1098      * text field (defaults to false).  If true, all inherited TextField members will still be available.
1099      */
1100     buttonOnly: false,
1101     /**
1102      * @cfg {Number} buttonOffset The number of pixels of space reserved between the button and the text field
1103      * (defaults to 3).  Note that this only applies if {@link #buttonOnly} = false.
1104      */
1105     buttonOffset: 3,
1106     /**
1107      * @cfg {Object} buttonCfg A standard {@link Ext.Button} config object.
1108      */
1109
1110     // private
1111     readOnly: true,
1112
1113     /**
1114      * @hide
1115      * @method autoSize
1116      */
1117     autoSize: Ext.emptyFn,
1118
1119     // private
1120     initComponent: function(){
1121         Ext.ux.form.FileUploadField.superclass.initComponent.call(this);
1122
1123         this.addEvents(
1124             /**
1125              * @event fileselected
1126              * Fires when the underlying file input field's value has changed from the user
1127              * selecting a new file from the system file selection dialog.
1128              * @param {Ext.ux.form.FileUploadField} this
1129              * @param {String} value The file value returned by the underlying file input field
1130              */
1131             'fileselected'
1132         );
1133     },
1134
1135     // private
1136     onRender : function(ct, position){
1137         Ext.ux.form.FileUploadField.superclass.onRender.call(this, ct, position);
1138
1139         this.wrap = this.el.wrap({cls:'x-form-field-wrap x-form-file-wrap'});
1140         this.el.addClass('x-form-file-text');
1141         this.el.dom.removeAttribute('name');
1142         this.createFileInput();
1143
1144         var btnCfg = Ext.applyIf(this.buttonCfg || {}, {
1145             text: this.buttonText
1146         });
1147         this.button = new Ext.Button(Ext.apply(btnCfg, {
1148             renderTo: this.wrap,
1149             cls: 'x-form-file-btn' + (btnCfg.iconCls ? ' x-btn-icon' : '')
1150         }));
1151
1152         if(this.buttonOnly){
1153             this.el.hide();
1154             this.wrap.setWidth(this.button.getEl().getWidth());
1155         }
1156
1157         this.bindListeners();
1158         this.resizeEl = this.positionEl = this.wrap;
1159     },
1160     
1161     bindListeners: function(){
1162         this.fileInput.on({
1163             scope: this,
1164             mouseenter: function() {
1165                 this.button.addClass(['x-btn-over','x-btn-focus'])
1166             },
1167             mouseleave: function(){
1168                 this.button.removeClass(['x-btn-over','x-btn-focus','x-btn-click'])
1169             },
1170             mousedown: function(){
1171                 this.button.addClass('x-btn-click')
1172             },
1173             mouseup: function(){
1174                 this.button.removeClass(['x-btn-over','x-btn-focus','x-btn-click'])
1175             },
1176             change: function(){
1177                 var v = this.fileInput.dom.value;
1178                 this.setValue(v);
1179                 this.fireEvent('fileselected', this, v);    
1180             }
1181         }); 
1182     },
1183     
1184     createFileInput : function() {
1185         this.fileInput = this.wrap.createChild({
1186             id: this.getFileInputId(),
1187             name: this.name||this.getId(),
1188             cls: 'x-form-file',
1189             tag: 'input',
1190             type: 'file',
1191             size: 1
1192         });
1193     },
1194     
1195     reset : function(){
1196         this.fileInput.remove();
1197         this.createFileInput();
1198         this.bindListeners();
1199         Ext.ux.form.FileUploadField.superclass.reset.call(this);
1200     },
1201
1202     // private
1203     getFileInputId: function(){
1204         return this.id + '-file';
1205     },
1206
1207     // private
1208     onResize : function(w, h){
1209         Ext.ux.form.FileUploadField.superclass.onResize.call(this, w, h);
1210
1211         this.wrap.setWidth(w);
1212
1213         if(!this.buttonOnly){
1214             var w = this.wrap.getWidth() - this.button.getEl().getWidth() - this.buttonOffset;
1215             this.el.setWidth(w);
1216         }
1217     },
1218
1219     // private
1220     onDestroy: function(){
1221         Ext.ux.form.FileUploadField.superclass.onDestroy.call(this);
1222         Ext.destroy(this.fileInput, this.button, this.wrap);
1223     },
1224     
1225     onDisable: function(){
1226         Ext.ux.form.FileUploadField.superclass.onDisable.call(this);
1227         this.doDisable(true);
1228     },
1229     
1230     onEnable: function(){
1231         Ext.ux.form.FileUploadField.superclass.onEnable.call(this);
1232         this.doDisable(false);
1233
1234     },
1235     
1236     // private
1237     doDisable: function(disabled){
1238         this.fileInput.dom.disabled = disabled;
1239         this.button.setDisabled(disabled);
1240     },
1241
1242
1243     // private
1244     preFocus : Ext.emptyFn,
1245
1246     // private
1247     alignErrorIcon : function(){
1248         this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
1249     }
1250
1251 });
1252
1253 Ext.reg('fileuploadfield', Ext.ux.form.FileUploadField);
1254
1255 // backwards compat
1256 Ext.form.FileUploadField = Ext.ux.form.FileUploadField;
1257 /**\r
1258  * @class Ext.ux.GMapPanel\r
1259  * @extends Ext.Panel\r
1260  * @author Shea Frederick\r
1261  */\r
1262 Ext.ux.GMapPanel = Ext.extend(Ext.Panel, {\r
1263     initComponent : function(){\r
1264         \r
1265         var defConfig = {\r
1266             plain: true,\r
1267             zoomLevel: 3,\r
1268             yaw: 180,\r
1269             pitch: 0,\r
1270             zoom: 0,\r
1271             gmapType: 'map',\r
1272             border: false\r
1273         };\r
1274         \r
1275         Ext.applyIf(this,defConfig);\r
1276         \r
1277         Ext.ux.GMapPanel.superclass.initComponent.call(this);        \r
1278 \r
1279     },\r
1280     afterRender : function(){\r
1281         \r
1282         var wh = this.ownerCt.getSize();\r
1283         Ext.applyIf(this, wh);\r
1284         \r
1285         Ext.ux.GMapPanel.superclass.afterRender.call(this);    \r
1286         \r
1287         if (this.gmapType === 'map'){\r
1288             this.gmap = new GMap2(this.body.dom);\r
1289         }\r
1290         \r
1291         if (this.gmapType === 'panorama'){\r
1292             this.gmap = new GStreetviewPanorama(this.body.dom);\r
1293         }\r
1294         \r
1295         if (typeof this.addControl == 'object' && this.gmapType === 'map') {\r
1296             this.gmap.addControl(this.addControl);\r
1297         }\r
1298         \r
1299         if (typeof this.setCenter === 'object') {\r
1300             if (typeof this.setCenter.geoCodeAddr === 'string'){\r
1301                 this.geoCodeLookup(this.setCenter.geoCodeAddr);\r
1302             }else{\r
1303                 if (this.gmapType === 'map'){\r
1304                     var point = new GLatLng(this.setCenter.lat,this.setCenter.lng);\r
1305                     this.gmap.setCenter(point, this.zoomLevel);    \r
1306                 }\r
1307                 if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){\r
1308                     this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear);\r
1309                 }\r
1310             }\r
1311             if (this.gmapType === 'panorama'){\r
1312                 this.gmap.setLocationAndPOV(new GLatLng(this.setCenter.lat,this.setCenter.lng), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoom});\r
1313             }\r
1314         }\r
1315 \r
1316         GEvent.bind(this.gmap, 'load', this, function(){\r
1317             this.onMapReady();\r
1318         });\r
1319 \r
1320     },\r
1321     onMapReady : function(){\r
1322         this.addMarkers(this.markers);\r
1323         this.addMapControls();\r
1324         this.addOptions();  \r
1325     },\r
1326     onResize : function(w, h){\r
1327 \r
1328         if (typeof this.getMap() == 'object') {\r
1329             this.gmap.checkResize();\r
1330         }\r
1331         \r
1332         Ext.ux.GMapPanel.superclass.onResize.call(this, w, h);\r
1333 \r
1334     },\r
1335     setSize : function(width, height, animate){\r
1336         \r
1337         if (typeof this.getMap() == 'object') {\r
1338             this.gmap.checkResize();\r
1339         }\r
1340         \r
1341         Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate);\r
1342         \r
1343     },\r
1344     getMap : function(){\r
1345         \r
1346         return this.gmap;\r
1347         \r
1348     },\r
1349     getCenter : function(){\r
1350         \r
1351         return this.getMap().getCenter();\r
1352         \r
1353     },\r
1354     getCenterLatLng : function(){\r
1355         \r
1356         var ll = this.getCenter();\r
1357         return {lat: ll.lat(), lng: ll.lng()};\r
1358         \r
1359     },\r
1360     addMarkers : function(markers) {\r
1361         \r
1362         if (Ext.isArray(markers)){\r
1363             for (var i = 0; i < markers.length; i++) {\r
1364                 var mkr_point = new GLatLng(markers[i].lat,markers[i].lng);\r
1365                 this.addMarker(mkr_point,markers[i].marker,false,markers[i].setCenter, markers[i].listeners);\r
1366             }\r
1367         }\r
1368         \r
1369     },\r
1370     addMarker : function(point, marker, clear, center, listeners){\r
1371         \r
1372         Ext.applyIf(marker,G_DEFAULT_ICON);\r
1373 \r
1374         if (clear === true){\r
1375             this.getMap().clearOverlays();\r
1376         }\r
1377         if (center === true) {\r
1378             this.getMap().setCenter(point, this.zoomLevel);\r
1379         }\r
1380 \r
1381         var mark = new GMarker(point,marker);\r
1382         if (typeof listeners === 'object'){\r
1383             for (evt in listeners) {\r
1384                 GEvent.bind(mark, evt, this, listeners[evt]);\r
1385             }\r
1386         }\r
1387         this.getMap().addOverlay(mark);\r
1388 \r
1389     },\r
1390     addMapControls : function(){\r
1391         \r
1392         if (this.gmapType === 'map') {\r
1393             if (Ext.isArray(this.mapControls)) {\r
1394                 for(i=0;i<this.mapControls.length;i++){\r
1395                     this.addMapControl(this.mapControls[i]);\r
1396                 }\r
1397             }else if(typeof this.mapControls === 'string'){\r
1398                 this.addMapControl(this.mapControls);\r
1399             }else if(typeof this.mapControls === 'object'){\r
1400                 this.getMap().addControl(this.mapControls);\r
1401             }\r
1402         }\r
1403         \r
1404     },\r
1405     addMapControl : function(mc){\r
1406         \r
1407         var mcf = window[mc];\r
1408         if (typeof mcf === 'function') {\r
1409             this.getMap().addControl(new mcf());\r
1410         }    \r
1411         \r
1412     },\r
1413     addOptions : function(){\r
1414         \r
1415         if (Ext.isArray(this.mapConfOpts)) {\r
1416             var mc;\r
1417             for(i=0;i<this.mapConfOpts.length;i++){\r
1418                 this.addOption(this.mapConfOpts[i]);\r
1419             }\r
1420         }else if(typeof this.mapConfOpts === 'string'){\r
1421             this.addOption(this.mapConfOpts);\r
1422         }        \r
1423         \r
1424     },\r
1425     addOption : function(mc){\r
1426         \r
1427         var mcf = this.getMap()[mc];\r
1428         if (typeof mcf === 'function') {\r
1429             this.getMap()[mc]();\r
1430         }    \r
1431         \r
1432     },\r
1433     geoCodeLookup : function(addr) {\r
1434         \r
1435         this.geocoder = new GClientGeocoder();\r
1436         this.geocoder.getLocations(addr, this.addAddressToMap.createDelegate(this));\r
1437         \r
1438     },\r
1439     addAddressToMap : function(response) {\r
1440         \r
1441         if (!response || response.Status.code != 200) {\r
1442             Ext.MessageBox.alert('Error', 'Code '+response.Status.code+' Error Returned');\r
1443         }else{\r
1444             place = response.Placemark[0];\r
1445             addressinfo = place.AddressDetails;\r
1446             accuracy = addressinfo.Accuracy;\r
1447             if (accuracy === 0) {\r
1448                 Ext.MessageBox.alert('Unable to Locate Address', 'Unable to Locate the Address you provided');\r
1449             }else{\r
1450                 if (accuracy < 7) {\r
1451                     Ext.MessageBox.alert('Address Accuracy', 'The address provided has a low accuracy.<br><br>Level '+accuracy+' Accuracy (8 = Exact Match, 1 = Vague Match)');\r
1452                 }else{\r
1453                     point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);\r
1454                     if (typeof this.setCenter.marker === 'object' && typeof point === 'object'){\r
1455                         this.addMarker(point,this.setCenter.marker,this.setCenter.marker.clear,true, this.setCenter.listeners);\r
1456                     }\r
1457                 }\r
1458             }\r
1459         }\r
1460         \r
1461     }\r
1462  \r
1463 });\r
1464 \r
1465 Ext.reg('gmappanel', Ext.ux.GMapPanel); Ext.namespace('Ext.ux.grid');\r
1466 \r
1467 /**\r
1468  * @class Ext.ux.grid.GridFilters\r
1469  * @extends Ext.util.Observable\r
1470  * <p>GridFilter is a plugin (<code>ptype='gridfilters'</code>) for grids that\r
1471  * allow for a slightly more robust representation of filtering than what is\r
1472  * provided by the default store.</p>\r
1473  * <p>Filtering is adjusted by the user using the grid's column header menu\r
1474  * (this menu can be disabled through configuration). Through this menu users\r
1475  * can configure, enable, and disable filters for each column.</p>\r
1476  * <p><b><u>Features:</u></b></p>\r
1477  * <div class="mdetail-params"><ul>\r
1478  * <li><b>Filtering implementations</b> :\r
1479  * <div class="sub-desc">\r
1480  * Default filtering for Strings, Numeric Ranges, Date Ranges, Lists (which can\r
1481  * be backed by a Ext.data.Store), and Boolean. Additional custom filter types\r
1482  * and menus are easily created by extending Ext.ux.grid.filter.Filter.\r
1483  * </div></li>\r
1484  * <li><b>Graphical indicators</b> :\r
1485  * <div class="sub-desc">\r
1486  * Columns that are filtered have {@link #filterCls a configurable css class}\r
1487  * applied to the column headers.\r
1488  * </div></li>\r
1489  * <li><b>Paging</b> :\r
1490  * <div class="sub-desc">\r
1491  * If specified as a plugin to the grid's configured PagingToolbar, the current page\r
1492  * will be reset to page 1 whenever you update the filters.\r
1493  * </div></li>\r
1494  * <li><b>Automatic Reconfiguration</b> :\r
1495  * <div class="sub-desc">\r
1496  * Filters automatically reconfigure when the grid 'reconfigure' event fires.\r
1497  * </div></li>\r
1498  * <li><b>Stateful</b> :\r
1499  * Filter information will be persisted across page loads by specifying a\r
1500  * <code>stateId</code> in the Grid configuration.\r
1501  * <div class="sub-desc">\r
1502  * The filter collection binds to the\r
1503  * <code>{@link Ext.grid.GridPanel#beforestaterestore beforestaterestore}</code>\r
1504  * and <code>{@link Ext.grid.GridPanel#beforestatesave beforestatesave}</code>\r
1505  * events in order to be stateful.\r
1506  * </div></li>\r
1507  * <li><b>Grid Changes</b> :\r
1508  * <div class="sub-desc"><ul>\r
1509  * <li>A <code>filters</code> <i>property</i> is added to the grid pointing to\r
1510  * this plugin.</li>\r
1511  * <li>A <code>filterupdate</code> <i>event</i> is added to the grid and is\r
1512  * fired upon onStateChange completion.</li>\r
1513  * </ul></div></li>\r
1514  * <li><b>Server side code examples</b> :\r
1515  * <div class="sub-desc"><ul>\r
1516  * <li><a href="http://www.vinylfox.com/extjs/grid-filter-php-backend-code.php">PHP</a> - (Thanks VinylFox)</li>\r
1517  * <li><a href="http://extjs.com/forum/showthread.php?p=77326#post77326">Ruby on Rails</a> - (Thanks Zyclops)</li>\r
1518  * <li><a href="http://extjs.com/forum/showthread.php?p=176596#post176596">Ruby on Rails</a> - (Thanks Rotomaul)</li>\r
1519  * <li><a href="http://www.debatablybeta.com/posts/using-extjss-grid-filtering-with-django/">Python</a> - (Thanks Matt)</li>\r
1520  * <li><a href="http://mcantrell.wordpress.com/2008/08/22/extjs-grids-and-grails/">Grails</a> - (Thanks Mike)</li>\r
1521  * </ul></div></li>\r
1522  * </ul></div>\r
1523  * <p><b><u>Example usage:</u></b></p>\r
1524  * <pre><code>\r
1525 var store = new Ext.data.GroupingStore({\r
1526     ...\r
1527 });\r
1528 \r
1529 var filters = new Ext.ux.grid.GridFilters({\r
1530     autoReload: false, //don&#39;t reload automatically\r
1531     local: true, //only filter locally\r
1532     // filters may be configured through the plugin,\r
1533     // or in the column definition within the column model configuration\r
1534     filters: [{\r
1535         type: 'numeric',\r
1536         dataIndex: 'id'\r
1537     }, {\r
1538         type: 'string',\r
1539         dataIndex: 'name'\r
1540     }, {\r
1541         type: 'numeric',\r
1542         dataIndex: 'price'\r
1543     }, {\r
1544         type: 'date',\r
1545         dataIndex: 'dateAdded'\r
1546     }, {\r
1547         type: 'list',\r
1548         dataIndex: 'size',\r
1549         options: ['extra small', 'small', 'medium', 'large', 'extra large'],\r
1550         phpMode: true\r
1551     }, {\r
1552         type: 'boolean',\r
1553         dataIndex: 'visible'\r
1554     }]\r
1555 });\r
1556 var cm = new Ext.grid.ColumnModel([{\r
1557     ...\r
1558 }]);\r
1559 \r
1560 var grid = new Ext.grid.GridPanel({\r
1561      ds: store,\r
1562      cm: cm,\r
1563      view: new Ext.grid.GroupingView(),\r
1564      plugins: [filters],\r
1565      height: 400,\r
1566      width: 700,\r
1567      bbar: new Ext.PagingToolbar({\r
1568          store: store,\r
1569          pageSize: 15,\r
1570          plugins: [filters] //reset page to page 1 if filters change\r
1571      })\r
1572  });\r
1573 \r
1574 store.load({params: {start: 0, limit: 15}});\r
1575 \r
1576 // a filters property is added to the grid\r
1577 grid.filters\r
1578  * </code></pre>\r
1579  */\r
1580 Ext.ux.grid.GridFilters = Ext.extend(Ext.util.Observable, {\r
1581     /**\r
1582      * @cfg {Boolean} autoReload\r
1583      * Defaults to true, reloading the datasource when a filter change happens.\r
1584      * Set this to false to prevent the datastore from being reloaded if there\r
1585      * are changes to the filters.  See <code>{@link updateBuffer}</code>.\r
1586      */\r
1587     autoReload : true,\r
1588     /**\r
1589      * @cfg {Boolean} encode\r
1590      * Specify true for {@link #buildQuery} to use Ext.util.JSON.encode to\r
1591      * encode the filter query parameter sent with a remote request.\r
1592      * Defaults to false.\r
1593      */\r
1594     /**\r
1595      * @cfg {Array} filters\r
1596      * An Array of filters config objects. Refer to each filter type class for\r
1597      * configuration details specific to each filter type. Filters for Strings,\r
1598      * Numeric Ranges, Date Ranges, Lists, and Boolean are the standard filters\r
1599      * available.\r
1600      */\r
1601     /**\r
1602      * @cfg {String} filterCls\r
1603      * The css class to be applied to column headers with active filters.\r
1604      * Defaults to <tt>'ux-filterd-column'</tt>.\r
1605      */\r
1606     filterCls : 'ux-filtered-column',\r
1607     /**\r
1608      * @cfg {Boolean} local\r
1609      * <tt>true</tt> to use Ext.data.Store filter functions (local filtering)\r
1610      * instead of the default (<tt>false</tt>) server side filtering.\r
1611      */\r
1612     local : false,\r
1613     /**\r
1614      * @cfg {String} menuFilterText\r
1615      * defaults to <tt>'Filters'</tt>.\r
1616      */\r
1617     menuFilterText : 'Filters',\r
1618     /**\r
1619      * @cfg {String} paramPrefix\r
1620      * The url parameter prefix for the filters.\r
1621      * Defaults to <tt>'filter'</tt>.\r
1622      */\r
1623     paramPrefix : 'filter',\r
1624     /**\r
1625      * @cfg {Boolean} showMenu\r
1626      * Defaults to true, including a filter submenu in the default header menu.\r
1627      */\r
1628     showMenu : true,\r
1629     /**\r
1630      * @cfg {String} stateId\r
1631      * Name of the value to be used to store state information.\r
1632      */\r
1633     stateId : undefined,\r
1634     /**\r
1635      * @cfg {Integer} updateBuffer\r
1636      * Number of milliseconds to defer store updates since the last filter change.\r
1637      */\r
1638     updateBuffer : 500,\r
1639 \r
1640     /** @private */\r
1641     constructor : function (config) {\r
1642         config = config || {};\r
1643         this.deferredUpdate = new Ext.util.DelayedTask(this.reload, this);\r
1644         this.filters = new Ext.util.MixedCollection();\r
1645         this.filters.getKey = function (o) {\r
1646             return o ? o.dataIndex : null;\r
1647         };\r
1648         this.addFilters(config.filters);\r
1649         delete config.filters;\r
1650         Ext.apply(this, config);\r
1651     },\r
1652 \r
1653     /** @private */\r
1654     init : function (grid) {\r
1655         if (grid instanceof Ext.grid.GridPanel) {\r
1656             this.grid = grid;\r
1657 \r
1658             this.bindStore(this.grid.getStore(), true);\r
1659             // assumes no filters were passed in the constructor, so try and use ones from the colModel\r
1660             if(this.filters.getCount() == 0){\r
1661                 this.addFilters(this.grid.getColumnModel());\r
1662             }\r
1663 \r
1664             this.grid.filters = this;\r
1665 \r
1666             this.grid.addEvents({'filterupdate': true});\r
1667 \r
1668             grid.on({\r
1669                 scope: this,\r
1670                 beforestaterestore: this.applyState,\r
1671                 beforestatesave: this.saveState,\r
1672                 beforedestroy: this.destroy,\r
1673                 reconfigure: this.onReconfigure\r
1674             });\r
1675 \r
1676             if (grid.rendered){\r
1677                 this.onRender();\r
1678             } else {\r
1679                 grid.on({\r
1680                     scope: this,\r
1681                     single: true,\r
1682                     render: this.onRender\r
1683                 });\r
1684             }\r
1685 \r
1686         } else if (grid instanceof Ext.PagingToolbar) {\r
1687             this.toolbar = grid;\r
1688         }\r
1689     },\r
1690 \r
1691     /**\r
1692      * @private\r
1693      * Handler for the grid's beforestaterestore event (fires before the state of the\r
1694      * grid is restored).\r
1695      * @param {Object} grid The grid object\r
1696      * @param {Object} state The hash of state values returned from the StateProvider.\r
1697      */\r
1698     applyState : function (grid, state) {\r
1699         var key, filter;\r
1700         this.applyingState = true;\r
1701         this.clearFilters();\r
1702         if (state.filters) {\r
1703             for (key in state.filters) {\r
1704                 filter = this.filters.get(key);\r
1705                 if (filter) {\r
1706                     filter.setValue(state.filters[key]);\r
1707                     filter.setActive(true);\r
1708                 }\r
1709             }\r
1710         }\r
1711         this.deferredUpdate.cancel();\r
1712         if (this.local) {\r
1713             this.reload();\r
1714         }\r
1715         delete this.applyingState;\r
1716     },\r
1717 \r
1718     /**\r
1719      * Saves the state of all active filters\r
1720      * @param {Object} grid\r
1721      * @param {Object} state\r
1722      * @return {Boolean}\r
1723      */\r
1724     saveState : function (grid, state) {\r
1725         var filters = {};\r
1726         this.filters.each(function (filter) {\r
1727             if (filter.active) {\r
1728                 filters[filter.dataIndex] = filter.getValue();\r
1729             }\r
1730         });\r
1731         return (state.filters = filters);\r
1732     },\r
1733 \r
1734     /**\r
1735      * @private\r
1736      * Handler called when the grid is rendered\r
1737      */\r
1738     onRender : function () {\r
1739         this.grid.getView().on('refresh', this.onRefresh, this);\r
1740         this.createMenu();\r
1741     },\r
1742 \r
1743     /**\r
1744      * @private\r
1745      * Handler called by the grid 'beforedestroy' event\r
1746      */\r
1747     destroy : function () {\r
1748         this.removeAll();\r
1749         this.purgeListeners();\r
1750 \r
1751         if(this.filterMenu){\r
1752             Ext.menu.MenuMgr.unregister(this.filterMenu);\r
1753             this.filterMenu.destroy();\r
1754              this.filterMenu = this.menu.menu = null;\r
1755         }\r
1756     },\r
1757 \r
1758     /**\r
1759      * Remove all filters, permanently destroying them.\r
1760      */\r
1761     removeAll : function () {\r
1762         if(this.filters){\r
1763             Ext.destroy.apply(Ext, this.filters.items);\r
1764             // remove all items from the collection\r
1765             this.filters.clear();\r
1766         }\r
1767     },\r
1768 \r
1769 \r
1770     /**\r
1771      * Changes the data store bound to this view and refreshes it.\r
1772      * @param {Store} store The store to bind to this view\r
1773      */\r
1774     bindStore : function(store, initial){\r
1775         if(!initial && this.store){\r
1776             if (this.local) {\r
1777                 store.un('load', this.onLoad, this);\r
1778             } else {\r
1779                 store.un('beforeload', this.onBeforeLoad, this);\r
1780             }\r
1781         }\r
1782         if(store){\r
1783             if (this.local) {\r
1784                 store.on('load', this.onLoad, this);\r
1785             } else {\r
1786                 store.on('beforeload', this.onBeforeLoad, this);\r
1787             }\r
1788         }\r
1789         this.store = store;\r
1790     },\r
1791 \r
1792     /**\r
1793      * @private\r
1794      * Handler called when the grid reconfigure event fires\r
1795      */\r
1796     onReconfigure : function () {\r
1797         this.bindStore(this.grid.getStore());\r
1798         this.store.clearFilter();\r
1799         this.removeAll();\r
1800         this.addFilters(this.grid.getColumnModel());\r
1801         this.updateColumnHeadings();\r
1802     },\r
1803 \r
1804     createMenu : function () {\r
1805         var view = this.grid.getView(),\r
1806             hmenu = view.hmenu;\r
1807 \r
1808         if (this.showMenu && hmenu) {\r
1809 \r
1810             this.sep  = hmenu.addSeparator();\r
1811             this.filterMenu = new Ext.menu.Menu({\r
1812                 id: this.grid.id + '-filters-menu'\r
1813             });\r
1814             this.menu = hmenu.add({\r
1815                 checked: false,\r
1816                 itemId: 'filters',\r
1817                 text: this.menuFilterText,\r
1818                 menu: this.filterMenu\r
1819             });\r
1820 \r
1821             this.menu.on({\r
1822                 scope: this,\r
1823                 checkchange: this.onCheckChange,\r
1824                 beforecheckchange: this.onBeforeCheck\r
1825             });\r
1826             hmenu.on('beforeshow', this.onMenu, this);\r
1827         }\r
1828         this.updateColumnHeadings();\r
1829     },\r
1830 \r
1831     /**\r
1832      * @private\r
1833      * Get the filter menu from the filters MixedCollection based on the clicked header\r
1834      */\r
1835     getMenuFilter : function () {\r
1836         var view = this.grid.getView();\r
1837         if (!view || view.hdCtxIndex === undefined) {\r
1838             return null;\r
1839         }\r
1840         return this.filters.get(\r
1841             view.cm.config[view.hdCtxIndex].dataIndex\r
1842         );\r
1843     },\r
1844 \r
1845     /**\r
1846      * @private\r
1847      * Handler called by the grid's hmenu beforeshow event\r
1848      */\r
1849     onMenu : function (filterMenu) {\r
1850         var filter = this.getMenuFilter();\r
1851 \r
1852         if (filter) {\r
1853 /*\r
1854 TODO: lazy rendering\r
1855             if (!filter.menu) {\r
1856                 filter.menu = filter.createMenu();\r
1857             }\r
1858 */\r
1859             this.menu.menu = filter.menu;\r
1860             this.menu.setChecked(filter.active, false);\r
1861             // disable the menu if filter.disabled explicitly set to true\r
1862             this.menu.setDisabled(filter.disabled === true);\r
1863         }\r
1864 \r
1865         this.menu.setVisible(filter !== undefined);\r
1866         this.sep.setVisible(filter !== undefined);\r
1867     },\r
1868 \r
1869     /** @private */\r
1870     onCheckChange : function (item, value) {\r
1871         this.getMenuFilter().setActive(value);\r
1872     },\r
1873 \r
1874     /** @private */\r
1875     onBeforeCheck : function (check, value) {\r
1876         return !value || this.getMenuFilter().isActivatable();\r
1877     },\r
1878 \r
1879     /**\r
1880      * @private\r
1881      * Handler for all events on filters.\r
1882      * @param {String} event Event name\r
1883      * @param {Object} filter Standard signature of the event before the event is fired\r
1884      */\r
1885     onStateChange : function (event, filter) {\r
1886         if (event === 'serialize') {\r
1887             return;\r
1888         }\r
1889 \r
1890         if (filter == this.getMenuFilter()) {\r
1891             this.menu.setChecked(filter.active, false);\r
1892         }\r
1893 \r
1894         if ((this.autoReload || this.local) && !this.applyingState) {\r
1895             this.deferredUpdate.delay(this.updateBuffer);\r
1896         }\r
1897         this.updateColumnHeadings();\r
1898 \r
1899         if (!this.applyingState) {\r
1900             this.grid.saveState();\r
1901         }\r
1902         this.grid.fireEvent('filterupdate', this, filter);\r
1903     },\r
1904 \r
1905     /**\r
1906      * @private\r
1907      * Handler for store's beforeload event when configured for remote filtering\r
1908      * @param {Object} store\r
1909      * @param {Object} options\r
1910      */\r
1911     onBeforeLoad : function (store, options) {\r
1912         options.params = options.params || {};\r
1913         this.cleanParams(options.params);\r
1914         var params = this.buildQuery(this.getFilterData());\r
1915         Ext.apply(options.params, params);\r
1916     },\r
1917 \r
1918     /**\r
1919      * @private\r
1920      * Handler for store's load event when configured for local filtering\r
1921      * @param {Object} store\r
1922      * @param {Object} options\r
1923      */\r
1924     onLoad : function (store, options) {\r
1925         store.filterBy(this.getRecordFilter());\r
1926     },\r
1927 \r
1928     /**\r
1929      * @private\r
1930      * Handler called when the grid's view is refreshed\r
1931      */\r
1932     onRefresh : function () {\r
1933         this.updateColumnHeadings();\r
1934     },\r
1935 \r
1936     /**\r
1937      * Update the styles for the header row based on the active filters\r
1938      */\r
1939     updateColumnHeadings : function () {\r
1940         var view = this.grid.getView(),\r
1941             i, len, filter;\r
1942         if (view.mainHd) {\r
1943             for (i = 0, len = view.cm.config.length; i < len; i++) {\r
1944                 filter = this.getFilter(view.cm.config[i].dataIndex);\r
1945                 Ext.fly(view.getHeaderCell(i))[filter && filter.active ? 'addClass' : 'removeClass'](this.filterCls);\r
1946             }\r
1947         }\r
1948     },\r
1949 \r
1950     /** @private */\r
1951     reload : function () {\r
1952         if (this.local) {\r
1953             this.grid.store.clearFilter(true);\r
1954             this.grid.store.filterBy(this.getRecordFilter());\r
1955         } else {\r
1956             var start,\r
1957                 store = this.grid.store;\r
1958             this.deferredUpdate.cancel();\r
1959             if (this.toolbar) {\r
1960                 start = store.paramNames.start;\r
1961                 if (store.lastOptions && store.lastOptions.params && store.lastOptions.params[start]) {\r
1962                     store.lastOptions.params[start] = 0;\r
1963                 }\r
1964             }\r
1965             store.reload();\r
1966         }\r
1967     },\r
1968 \r
1969     /**\r
1970      * Method factory that generates a record validator for the filters active at the time\r
1971      * of invokation.\r
1972      * @private\r
1973      */\r
1974     getRecordFilter : function () {\r
1975         var f = [], len, i;\r
1976         this.filters.each(function (filter) {\r
1977             if (filter.active) {\r
1978                 f.push(filter);\r
1979             }\r
1980         });\r
1981 \r
1982         len = f.length;\r
1983         return function (record) {\r
1984             for (i = 0; i < len; i++) {\r
1985                 if (!f[i].validateRecord(record)) {\r
1986                     return false;\r
1987                 }\r
1988             }\r
1989             return true;\r
1990         };\r
1991     },\r
1992 \r
1993     /**\r
1994      * Adds a filter to the collection and observes it for state change.\r
1995      * @param {Object/Ext.ux.grid.filter.Filter} config A filter configuration or a filter object.\r
1996      * @return {Ext.ux.grid.filter.Filter} The existing or newly created filter object.\r
1997      */\r
1998     addFilter : function (config) {\r
1999         var Cls = this.getFilterClass(config.type),\r
2000             filter = config.menu ? config : (new Cls(config));\r
2001         this.filters.add(filter);\r
2002 \r
2003         Ext.util.Observable.capture(filter, this.onStateChange, this);\r
2004         return filter;\r
2005     },\r
2006 \r
2007     /**\r
2008      * Adds filters to the collection.\r
2009      * @param {Array/Ext.grid.ColumnModel} filters Either an Array of\r
2010      * filter configuration objects or an Ext.grid.ColumnModel.  The columns\r
2011      * of a passed Ext.grid.ColumnModel will be examined for a <code>filter</code>\r
2012      * property and, if present, will be used as the filter configuration object.\r
2013      */\r
2014     addFilters : function (filters) {\r
2015         if (filters) {\r
2016             var i, len, filter, cm = false, dI;\r
2017             if (filters instanceof Ext.grid.ColumnModel) {\r
2018                 filters = filters.config;\r
2019                 cm = true;\r
2020             }\r
2021             for (i = 0, len = filters.length; i < len; i++) {\r
2022                 filter = false;\r
2023                 if (cm) {\r
2024                     dI = filters[i].dataIndex;\r
2025                     filter = filters[i].filter || filters[i].filterable;\r
2026                     if (filter){\r
2027                         filter = (filter === true) ? {} : filter;\r
2028                         Ext.apply(filter, {dataIndex:dI});\r
2029                         // filter type is specified in order of preference:\r
2030                         //     filter type specified in config\r
2031                         //     type specified in store's field's type config\r
2032                         filter.type = filter.type || this.store.fields.get(dI).type;\r
2033                     }\r
2034                 } else {\r
2035                     filter = filters[i];\r
2036                 }\r
2037                 // if filter config found add filter for the column\r
2038                 if (filter) {\r
2039                     this.addFilter(filter);\r
2040                 }\r
2041             }\r
2042         }\r
2043     },\r
2044 \r
2045     /**\r
2046      * Returns a filter for the given dataIndex, if one exists.\r
2047      * @param {String} dataIndex The dataIndex of the desired filter object.\r
2048      * @return {Ext.ux.grid.filter.Filter}\r
2049      */\r
2050     getFilter : function (dataIndex) {\r
2051         return this.filters.get(dataIndex);\r
2052     },\r
2053 \r
2054     /**\r
2055      * Turns all filters off. This does not clear the configuration information\r
2056      * (see {@link #removeAll}).\r
2057      */\r
2058     clearFilters : function () {\r
2059         this.filters.each(function (filter) {\r
2060             filter.setActive(false);\r
2061         });\r
2062     },\r
2063 \r
2064     /**\r
2065      * Returns an Array of the currently active filters.\r
2066      * @return {Array} filters Array of the currently active filters.\r
2067      */\r
2068     getFilterData : function () {\r
2069         var filters = [], i, len;\r
2070 \r
2071         this.filters.each(function (f) {\r
2072             if (f.active) {\r
2073                 var d = [].concat(f.serialize());\r
2074                 for (i = 0, len = d.length; i < len; i++) {\r
2075                     filters.push({\r
2076                         field: f.dataIndex,\r
2077                         data: d[i]\r
2078                     });\r
2079                 }\r
2080             }\r
2081         });\r
2082         return filters;\r
2083     },\r
2084 \r
2085     /**\r
2086      * Function to take the active filters data and build it into a query.\r
2087      * The format of the query depends on the <code>{@link #encode}</code>\r
2088      * configuration:\r
2089      * <div class="mdetail-params"><ul>\r
2090      *\r
2091      * <li><b><tt>false</tt></b> : <i>Default</i>\r
2092      * <div class="sub-desc">\r
2093      * Flatten into query string of the form (assuming <code>{@link #paramPrefix}='filters'</code>:\r
2094      * <pre><code>\r
2095 filters[0][field]="someDataIndex"&\r
2096 filters[0][data][comparison]="someValue1"&\r
2097 filters[0][data][type]="someValue2"&\r
2098 filters[0][data][value]="someValue3"&\r
2099      * </code></pre>\r
2100      * </div></li>\r
2101      * <li><b><tt>true</tt></b> :\r
2102      * <div class="sub-desc">\r
2103      * JSON encode the filter data\r
2104      * <pre><code>\r
2105 filters[0][field]="someDataIndex"&\r
2106 filters[0][data][comparison]="someValue1"&\r
2107 filters[0][data][type]="someValue2"&\r
2108 filters[0][data][value]="someValue3"&\r
2109      * </code></pre>\r
2110      * </div></li>\r
2111      * </ul></div>\r
2112      * Override this method to customize the format of the filter query for remote requests.\r
2113      * @param {Array} filters A collection of objects representing active filters and their configuration.\r
2114      *    Each element will take the form of {field: dataIndex, data: filterConf}. dataIndex is not assured\r
2115      *    to be unique as any one filter may be a composite of more basic filters for the same dataIndex.\r
2116      * @return {Object} Query keys and values\r
2117      */\r
2118     buildQuery : function (filters) {\r
2119         var p = {}, i, f, root, dataPrefix, key, tmp,\r
2120             len = filters.length;\r
2121 \r
2122         if (!this.encode){\r
2123             for (i = 0; i < len; i++) {\r
2124                 f = filters[i];\r
2125                 root = [this.paramPrefix, '[', i, ']'].join('');\r
2126                 p[root + '[field]'] = f.field;\r
2127 \r
2128                 dataPrefix = root + '[data]';\r
2129                 for (key in f.data) {\r
2130                     p[[dataPrefix, '[', key, ']'].join('')] = f.data[key];\r
2131                 }\r
2132             }\r
2133         } else {\r
2134             tmp = [];\r
2135             for (i = 0; i < len; i++) {\r
2136                 f = filters[i];\r
2137                 tmp.push(Ext.apply(\r
2138                     {},\r
2139                     {field: f.field},\r
2140                     f.data\r
2141                 ));\r
2142             }\r
2143             // only build if there is active filter\r
2144             if (tmp.length > 0){\r
2145                 p[this.paramPrefix] = Ext.util.JSON.encode(tmp);\r
2146             }\r
2147         }\r
2148         return p;\r
2149     },\r
2150 \r
2151     /**\r
2152      * Removes filter related query parameters from the provided object.\r
2153      * @param {Object} p Query parameters that may contain filter related fields.\r
2154      */\r
2155     cleanParams : function (p) {\r
2156         // if encoding just delete the property\r
2157         if (this.encode) {\r
2158             delete p[this.paramPrefix];\r
2159         // otherwise scrub the object of filter data\r
2160         } else {\r
2161             var regex, key;\r
2162             regex = new RegExp('^' + this.paramPrefix + '\[[0-9]+\]');\r
2163             for (key in p) {\r
2164                 if (regex.test(key)) {\r
2165                     delete p[key];\r
2166                 }\r
2167             }\r
2168         }\r
2169     },\r
2170 \r
2171     /**\r
2172      * Function for locating filter classes, overwrite this with your favorite\r
2173      * loader to provide dynamic filter loading.\r
2174      * @param {String} type The type of filter to load ('Filter' is automatically\r
2175      * appended to the passed type; eg, 'string' becomes 'StringFilter').\r
2176      * @return {Class} The Ext.ux.grid.filter.Class\r
2177      */\r
2178     getFilterClass : function (type) {\r
2179         // map the supported Ext.data.Field type values into a supported filter\r
2180         switch(type) {\r
2181             case 'auto':\r
2182               type = 'string';\r
2183               break;\r
2184             case 'int':\r
2185             case 'float':\r
2186               type = 'numeric';\r
2187               break;\r
2188         }\r
2189         return Ext.ux.grid.filter[type.substr(0, 1).toUpperCase() + type.substr(1) + 'Filter'];\r
2190     }\r
2191 });\r
2192 \r
2193 // register ptype\r
2194 Ext.preg('gridfilters', Ext.ux.grid.GridFilters);\r
2195 Ext.namespace('Ext.ux.grid.filter');\r
2196 \r
2197 /** \r
2198  * @class Ext.ux.grid.filter.Filter\r
2199  * @extends Ext.util.Observable\r
2200  * Abstract base class for filter implementations.\r
2201  */\r
2202 Ext.ux.grid.filter.Filter = Ext.extend(Ext.util.Observable, {\r
2203     /**\r
2204      * @cfg {Boolean} active\r
2205      * Indicates the initial status of the filter (defaults to false).\r
2206      */\r
2207     active : false,\r
2208     /**\r
2209      * True if this filter is active.  Use setActive() to alter after configuration.\r
2210      * @type Boolean\r
2211      * @property active\r
2212      */\r
2213     /**\r
2214      * @cfg {String} dataIndex \r
2215      * The {@link Ext.data.Store} dataIndex of the field this filter represents.\r
2216      * The dataIndex does not actually have to exist in the store.\r
2217      */\r
2218     dataIndex : null,\r
2219     /**\r
2220      * The filter configuration menu that will be installed into the filter submenu of a column menu.\r
2221      * @type Ext.menu.Menu\r
2222      * @property\r
2223      */\r
2224     menu : null,\r
2225     /**\r
2226      * @cfg {Number} updateBuffer\r
2227      * Number of milliseconds to wait after user interaction to fire an update. Only supported \r
2228      * by filters: 'list', 'numeric', and 'string'. Defaults to 500.\r
2229      */\r
2230     updateBuffer : 500,\r
2231 \r
2232     constructor : function (config) {\r
2233         Ext.apply(this, config);\r
2234             \r
2235         this.addEvents(\r
2236             /**\r
2237              * @event activate\r
2238              * Fires when an inactive filter becomes active\r
2239              * @param {Ext.ux.grid.filter.Filter} this\r
2240              */\r
2241             'activate',\r
2242             /**\r
2243              * @event deactivate\r
2244              * Fires when an active filter becomes inactive\r
2245              * @param {Ext.ux.grid.filter.Filter} this\r
2246              */\r
2247             'deactivate',\r
2248             /**\r
2249              * @event serialize\r
2250              * Fires after the serialization process. Use this to attach additional parameters to serialization\r
2251              * data before it is encoded and sent to the server.\r
2252              * @param {Array/Object} data A map or collection of maps representing the current filter configuration.\r
2253              * @param {Ext.ux.grid.filter.Filter} filter The filter being serialized.\r
2254              */\r
2255             'serialize',\r
2256             /**\r
2257              * @event update\r
2258              * Fires when a filter configuration has changed\r
2259              * @param {Ext.ux.grid.filter.Filter} this The filter object.\r
2260              */\r
2261             'update'\r
2262         );\r
2263         Ext.ux.grid.filter.Filter.superclass.constructor.call(this);\r
2264 \r
2265         this.menu = new Ext.menu.Menu();\r
2266         this.init(config);\r
2267         if(config && config.value){\r
2268             this.setValue(config.value);\r
2269             this.setActive(config.active !== false, true);\r
2270             delete config.value;\r
2271         }\r
2272     },\r
2273 \r
2274     /**\r
2275      * Destroys this filter by purging any event listeners, and removing any menus.\r
2276      */\r
2277     destroy : function(){\r
2278         if (this.menu){\r
2279             this.menu.destroy();\r
2280         }\r
2281         this.purgeListeners();\r
2282     },\r
2283 \r
2284     /**\r
2285      * Template method to be implemented by all subclasses that is to\r
2286      * initialize the filter and install required menu items.\r
2287      * Defaults to Ext.emptyFn.\r
2288      */\r
2289     init : Ext.emptyFn,\r
2290     \r
2291     /**\r
2292      * Template method to be implemented by all subclasses that is to\r
2293      * get and return the value of the filter.\r
2294      * Defaults to Ext.emptyFn.\r
2295      * @return {Object} The 'serialized' form of this filter\r
2296      * @methodOf Ext.ux.grid.filter.Filter\r
2297      */\r
2298     getValue : Ext.emptyFn,\r
2299     \r
2300     /**\r
2301      * Template method to be implemented by all subclasses that is to\r
2302      * set the value of the filter and fire the 'update' event.\r
2303      * Defaults to Ext.emptyFn.\r
2304      * @param {Object} data The value to set the filter\r
2305      * @methodOf Ext.ux.grid.filter.Filter\r
2306      */ \r
2307     setValue : Ext.emptyFn,\r
2308     \r
2309     /**\r
2310      * Template method to be implemented by all subclasses that is to\r
2311      * return <tt>true</tt> if the filter has enough configuration information to be activated.\r
2312      * Defaults to <tt>return true</tt>.\r
2313      * @return {Boolean}\r
2314      */\r
2315     isActivatable : function(){\r
2316         return true;\r
2317     },\r
2318     \r
2319     /**\r
2320      * Template method to be implemented by all subclasses that is to\r
2321      * get and return serialized filter data for transmission to the server.\r
2322      * Defaults to Ext.emptyFn.\r
2323      */\r
2324     getSerialArgs : Ext.emptyFn,\r
2325 \r
2326     /**\r
2327      * Template method to be implemented by all subclasses that is to\r
2328      * validates the provided Ext.data.Record against the filters configuration.\r
2329      * Defaults to <tt>return true</tt>.\r
2330      * @param {Ext.data.Record} record The record to validate\r
2331      * @return {Boolean} true if the record is valid within the bounds\r
2332      * of the filter, false otherwise.\r
2333      */\r
2334     validateRecord : function(){\r
2335         return true;\r
2336     },\r
2337 \r
2338     /**\r
2339      * Returns the serialized filter data for transmission to the server\r
2340      * and fires the 'serialize' event.\r
2341      * @return {Object/Array} An object or collection of objects containing\r
2342      * key value pairs representing the current configuration of the filter.\r
2343      * @methodOf Ext.ux.grid.filter.Filter\r
2344      */\r
2345     serialize : function(){\r
2346         var args = this.getSerialArgs();\r
2347         this.fireEvent('serialize', args, this);\r
2348         return args;\r
2349     },\r
2350 \r
2351     /** @private */\r
2352     fireUpdate : function(){\r
2353         if (this.active) {\r
2354             this.fireEvent('update', this);\r
2355         }\r
2356         this.setActive(this.isActivatable());\r
2357     },\r
2358     \r
2359     /**\r
2360      * Sets the status of the filter and fires the appropriate events.\r
2361      * @param {Boolean} active        The new filter state.\r
2362      * @param {Boolean} suppressEvent True to prevent events from being fired.\r
2363      * @methodOf Ext.ux.grid.filter.Filter\r
2364      */\r
2365     setActive : function(active, suppressEvent){\r
2366         if(this.active != active){\r
2367             this.active = active;\r
2368             if (suppressEvent !== true) {\r
2369                 this.fireEvent(active ? 'activate' : 'deactivate', this);\r
2370             }\r
2371         }\r
2372     }    \r
2373 });/** \r
2374  * @class Ext.ux.grid.filter.BooleanFilter\r
2375  * @extends Ext.ux.grid.filter.Filter\r
2376  * Boolean filters use unique radio group IDs (so you can have more than one!)\r
2377  * <p><b><u>Example Usage:</u></b></p>\r
2378  * <pre><code>    \r
2379 var filters = new Ext.ux.grid.GridFilters({\r
2380     ...\r
2381     filters: [{\r
2382         // required configs\r
2383         type: 'boolean',\r
2384         dataIndex: 'visible'\r
2385 \r
2386         // optional configs\r
2387         defaultValue: null, // leave unselected (false selected by default)\r
2388         yesText: 'Yes',     // default\r
2389         noText: 'No'        // default\r
2390     }]\r
2391 });\r
2392  * </code></pre>\r
2393  */\r
2394 Ext.ux.grid.filter.BooleanFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
2395         /**\r
2396          * @cfg {Boolean} defaultValue\r
2397          * Set this to null if you do not want either option to be checked by default. Defaults to false.\r
2398          */\r
2399         defaultValue : false,\r
2400         /**\r
2401          * @cfg {String} yesText\r
2402          * Defaults to 'Yes'.\r
2403          */\r
2404         yesText : 'Yes',\r
2405         /**\r
2406          * @cfg {String} noText\r
2407          * Defaults to 'No'.\r
2408          */\r
2409         noText : 'No',\r
2410 \r
2411     /**  \r
2412      * @private\r
2413      * Template method that is to initialize the filter and install required menu items.\r
2414      */\r
2415     init : function (config) {\r
2416         var gId = Ext.id();\r
2417                 this.options = [\r
2418                         new Ext.menu.CheckItem({text: this.yesText, group: gId, checked: this.defaultValue === true}),\r
2419                         new Ext.menu.CheckItem({text: this.noText, group: gId, checked: this.defaultValue === false})];\r
2420                 \r
2421                 this.menu.add(this.options[0], this.options[1]);\r
2422                 \r
2423                 for(var i=0; i<this.options.length; i++){\r
2424                         this.options[i].on('click', this.fireUpdate, this);\r
2425                         this.options[i].on('checkchange', this.fireUpdate, this);\r
2426                 }\r
2427         },\r
2428         \r
2429     /**\r
2430      * @private\r
2431      * Template method that is to get and return the value of the filter.\r
2432      * @return {String} The value of this filter\r
2433      */\r
2434     getValue : function () {\r
2435                 return this.options[0].checked;\r
2436         },\r
2437 \r
2438     /**\r
2439      * @private\r
2440      * Template method that is to set the value of the filter.\r
2441      * @param {Object} value The value to set the filter\r
2442      */ \r
2443         setValue : function (value) {\r
2444                 this.options[value ? 0 : 1].setChecked(true);\r
2445         },\r
2446 \r
2447     /**\r
2448      * @private\r
2449      * Template method that is to get and return serialized filter data for\r
2450      * transmission to the server.\r
2451      * @return {Object/Array} An object or collection of objects containing\r
2452      * key value pairs representing the current configuration of the filter.\r
2453      */\r
2454     getSerialArgs : function () {\r
2455                 var args = {type: 'boolean', value: this.getValue()};\r
2456                 return args;\r
2457         },\r
2458         \r
2459     /**\r
2460      * Template method that is to validate the provided Ext.data.Record\r
2461      * against the filters configuration.\r
2462      * @param {Ext.data.Record} record The record to validate\r
2463      * @return {Boolean} true if the record is valid within the bounds\r
2464      * of the filter, false otherwise.\r
2465      */\r
2466     validateRecord : function (record) {\r
2467                 return record.get(this.dataIndex) == this.getValue();\r
2468         }\r
2469 });/** \r
2470  * @class Ext.ux.grid.filter.DateFilter\r
2471  * @extends Ext.ux.grid.filter.Filter\r
2472  * Filter by a configurable Ext.menu.DateMenu\r
2473  * <p><b><u>Example Usage:</u></b></p>\r
2474  * <pre><code>    \r
2475 var filters = new Ext.ux.grid.GridFilters({\r
2476     ...\r
2477     filters: [{\r
2478         // required configs\r
2479         type: 'date',\r
2480         dataIndex: 'dateAdded',\r
2481         \r
2482         // optional configs\r
2483         dateFormat: 'm/d/Y',  // default\r
2484         beforeText: 'Before', // default\r
2485         afterText: 'After',   // default\r
2486         onText: 'On',         // default\r
2487         pickerOpts: {\r
2488             // any DateMenu configs\r
2489         },\r
2490 \r
2491         active: true // default is false\r
2492     }]\r
2493 });\r
2494  * </code></pre>\r
2495  */\r
2496 Ext.ux.grid.filter.DateFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
2497     /**\r
2498      * @cfg {String} afterText\r
2499      * Defaults to 'After'.\r
2500      */\r
2501     afterText : 'After',\r
2502     /**\r
2503      * @cfg {String} beforeText\r
2504      * Defaults to 'Before'.\r
2505      */\r
2506     beforeText : 'Before',\r
2507     /**\r
2508      * @cfg {Object} compareMap\r
2509      * Map for assigning the comparison values used in serialization.\r
2510      */\r
2511     compareMap : {\r
2512         before: 'lt',\r
2513         after:  'gt',\r
2514         on:     'eq'\r
2515     },\r
2516     /**\r
2517      * @cfg {String} dateFormat\r
2518      * The date format to return when using getValue.\r
2519      * Defaults to 'm/d/Y'.\r
2520      */\r
2521     dateFormat : 'm/d/Y',\r
2522 \r
2523     /**\r
2524      * @cfg {Date} maxDate\r
2525      * Allowable date as passed to the Ext.DatePicker\r
2526      * Defaults to undefined.\r
2527      */\r
2528     /**\r
2529      * @cfg {Date} minDate\r
2530      * Allowable date as passed to the Ext.DatePicker\r
2531      * Defaults to undefined.\r
2532      */\r
2533     /**\r
2534      * @cfg {Array} menuItems\r
2535      * The items to be shown in this menu\r
2536      * Defaults to:<pre>\r
2537      * menuItems : ['before', 'after', '-', 'on'],\r
2538      * </pre>\r
2539      */\r
2540     menuItems : ['before', 'after', '-', 'on'],\r
2541 \r
2542     /**\r
2543      * @cfg {Object} menuItemCfgs\r
2544      * Default configuration options for each menu item\r
2545      */\r
2546     menuItemCfgs : {\r
2547         selectOnFocus: true,\r
2548         width: 125\r
2549     },\r
2550 \r
2551     /**\r
2552      * @cfg {String} onText\r
2553      * Defaults to 'On'.\r
2554      */\r
2555     onText : 'On',\r
2556     \r
2557     /**\r
2558      * @cfg {Object} pickerOpts\r
2559      * Configuration options for the date picker associated with each field.\r
2560      */\r
2561     pickerOpts : {},\r
2562 \r
2563     /**  \r
2564      * @private\r
2565      * Template method that is to initialize the filter and install required menu items.\r
2566      */\r
2567     init : function (config) {\r
2568         var menuCfg, i, len, item, cfg, Cls;\r
2569 \r
2570         menuCfg = Ext.apply(this.pickerOpts, {\r
2571             minDate: this.minDate, \r
2572             maxDate: this.maxDate, \r
2573             format:  this.dateFormat,\r
2574             listeners: {\r
2575                 scope: this,\r
2576                 select: this.onMenuSelect\r
2577             }\r
2578         });\r
2579 \r
2580         this.fields = {};\r
2581         for (i = 0, len = this.menuItems.length; i < len; i++) {\r
2582             item = this.menuItems[i];\r
2583             if (item !== '-') {\r
2584                 cfg = {\r
2585                     itemId: 'range-' + item,\r
2586                     text: this[item + 'Text'],\r
2587                     menu: new Ext.menu.DateMenu(\r
2588                         Ext.apply(menuCfg, {\r
2589                             itemId: item\r
2590                         })\r
2591                     ),\r
2592                     listeners: {\r
2593                         scope: this,\r
2594                         checkchange: this.onCheckChange\r
2595                     }\r
2596                 };\r
2597                 Cls = Ext.menu.CheckItem;\r
2598                 item = this.fields[item] = new Cls(cfg);\r
2599             }\r
2600             //this.add(item);\r
2601             this.menu.add(item);\r
2602         }\r
2603     },\r
2604 \r
2605     onCheckChange : function () {\r
2606         this.setActive(this.isActivatable());\r
2607         this.fireEvent('update', this);\r
2608     },\r
2609 \r
2610     /**  \r
2611      * @private\r
2612      * Handler method called when there is a keyup event on an input\r
2613      * item of this menu.\r
2614      */\r
2615     onInputKeyUp : function (field, e) {\r
2616         var k = e.getKey();\r
2617         if (k == e.RETURN && field.isValid()) {\r
2618             e.stopEvent();\r
2619             this.menu.hide(true);\r
2620             return;\r
2621         }\r
2622     },\r
2623 \r
2624     /**\r
2625      * Handler for when the menu for a field fires the 'select' event\r
2626      * @param {Object} date\r
2627      * @param {Object} menuItem\r
2628      * @param {Object} value\r
2629      * @param {Object} picker\r
2630      */\r
2631     onMenuSelect : function (menuItem, value, picker) {\r
2632         var fields = this.fields,\r
2633             field = this.fields[menuItem.itemId];\r
2634         \r
2635         field.setChecked(true);\r
2636         \r
2637         if (field == fields.on) {\r
2638             fields.before.setChecked(false, true);\r
2639             fields.after.setChecked(false, true);\r
2640         } else {\r
2641             fields.on.setChecked(false, true);\r
2642             if (field == fields.after && fields.before.menu.picker.value < value) {\r
2643                 fields.before.setChecked(false, true);\r
2644             } else if (field == fields.before && fields.after.menu.picker.value > value) {\r
2645                 fields.after.setChecked(false, true);\r
2646             }\r
2647         }\r
2648         this.fireEvent('update', this);\r
2649     },\r
2650 \r
2651     /**\r
2652      * @private\r
2653      * Template method that is to get and return the value of the filter.\r
2654      * @return {String} The value of this filter\r
2655      */\r
2656     getValue : function () {\r
2657         var key, result = {};\r
2658         for (key in this.fields) {\r
2659             if (this.fields[key].checked) {\r
2660                 result[key] = this.fields[key].menu.picker.getValue();\r
2661             }\r
2662         }\r
2663         return result;\r
2664     },\r
2665 \r
2666     /**\r
2667      * @private\r
2668      * Template method that is to set the value of the filter.\r
2669      * @param {Object} value The value to set the filter\r
2670      * @param {Boolean} preserve true to preserve the checked status\r
2671      * of the other fields.  Defaults to false, unchecking the\r
2672      * other fields\r
2673      */ \r
2674     setValue : function (value, preserve) {\r
2675         var key;\r
2676         for (key in this.fields) {\r
2677             if(value[key]){\r
2678                 this.fields[key].menu.picker.setValue(value[key]);\r
2679                 this.fields[key].setChecked(true);\r
2680             } else if (!preserve) {\r
2681                 this.fields[key].setChecked(false);\r
2682             }\r
2683         }\r
2684         this.fireEvent('update', this);\r
2685     },\r
2686 \r
2687     /**\r
2688      * @private\r
2689      * Template method that is to return <tt>true</tt> if the filter\r
2690      * has enough configuration information to be activated.\r
2691      * @return {Boolean}\r
2692      */\r
2693     isActivatable : function () {\r
2694         var key;\r
2695         for (key in this.fields) {\r
2696             if (this.fields[key].checked) {\r
2697                 return true;\r
2698             }\r
2699         }\r
2700         return false;\r
2701     },\r
2702 \r
2703     /**\r
2704      * @private\r
2705      * Template method that is to get and return serialized filter data for\r
2706      * transmission to the server.\r
2707      * @return {Object/Array} An object or collection of objects containing\r
2708      * key value pairs representing the current configuration of the filter.\r
2709      */\r
2710     getSerialArgs : function () {\r
2711         var args = [];\r
2712         for (var key in this.fields) {\r
2713             if(this.fields[key].checked){\r
2714                 args.push({\r
2715                     type: 'date',\r
2716                     comparison: this.compareMap[key],\r
2717                     value: this.getFieldValue(key).format(this.dateFormat)\r
2718                 });\r
2719             }\r
2720         }\r
2721         return args;\r
2722     },\r
2723 \r
2724     /**\r
2725      * Get and return the date menu picker value\r
2726      * @param {String} item The field identifier ('before', 'after', 'on')\r
2727      * @return {Date} Gets the current selected value of the date field\r
2728      */\r
2729     getFieldValue : function(item){\r
2730         return this.fields[item].menu.picker.getValue();\r
2731     },\r
2732     \r
2733     /**\r
2734      * Gets the menu picker associated with the passed field\r
2735      * @param {String} item The field identifier ('before', 'after', 'on')\r
2736      * @return {Object} The menu picker\r
2737      */\r
2738     getPicker : function(item){\r
2739         return this.fields[item].menu.picker;\r
2740     },\r
2741 \r
2742     /**\r
2743      * Template method that is to validate the provided Ext.data.Record\r
2744      * against the filters configuration.\r
2745      * @param {Ext.data.Record} record The record to validate\r
2746      * @return {Boolean} true if the record is valid within the bounds\r
2747      * of the filter, false otherwise.\r
2748      */\r
2749     validateRecord : function (record) {\r
2750         var key,\r
2751             pickerValue,\r
2752             val = record.get(this.dataIndex);\r
2753             \r
2754         if(!Ext.isDate(val)){\r
2755             return false;\r
2756         }\r
2757         val = val.clearTime(true).getTime();\r
2758         \r
2759         for (key in this.fields) {\r
2760             if (this.fields[key].checked) {\r
2761                 pickerValue = this.getFieldValue(key).clearTime(true).getTime();\r
2762                 if (key == 'before' && pickerValue <= val) {\r
2763                     return false;\r
2764                 }\r
2765                 if (key == 'after' && pickerValue >= val) {\r
2766                     return false;\r
2767                 }\r
2768                 if (key == 'on' && pickerValue != val) {\r
2769                     return false;\r
2770                 }\r
2771             }\r
2772         }\r
2773         return true;\r
2774     }\r
2775 });/** \r
2776  * @class Ext.ux.grid.filter.ListFilter\r
2777  * @extends Ext.ux.grid.filter.Filter\r
2778  * <p>List filters are able to be preloaded/backed by an Ext.data.Store to load\r
2779  * their options the first time they are shown. ListFilter utilizes the\r
2780  * {@link Ext.ux.menu.ListMenu} component.</p>\r
2781  * <p>Although not shown here, this class accepts all configuration options\r
2782  * for {@link Ext.ux.menu.ListMenu}.</p>\r
2783  * \r
2784  * <p><b><u>Example Usage:</u></b></p>\r
2785  * <pre><code>    \r
2786 var filters = new Ext.ux.grid.GridFilters({\r
2787     ...\r
2788     filters: [{\r
2789         type: 'list',\r
2790         dataIndex: 'size',\r
2791         phpMode: true,\r
2792         // options will be used as data to implicitly creates an ArrayStore\r
2793         options: ['extra small', 'small', 'medium', 'large', 'extra large']\r
2794     }]\r
2795 });\r
2796  * </code></pre>\r
2797  * \r
2798  */\r
2799 Ext.ux.grid.filter.ListFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
2800 \r
2801     /**\r
2802      * @cfg {Array} options\r
2803      * <p><code>data</code> to be used to implicitly create a data store\r
2804      * to back this list when the data source is <b>local</b>. If the\r
2805      * data for the list is remote, use the <code>{@link #store}</code>\r
2806      * config instead.</p>\r
2807      * <br><p>Each item within the provided array may be in one of the\r
2808      * following formats:</p>\r
2809      * <div class="mdetail-params"><ul>\r
2810      * <li><b>Array</b> :\r
2811      * <pre><code>\r
2812 options: [\r
2813     [11, 'extra small'], \r
2814     [18, 'small'],\r
2815     [22, 'medium'],\r
2816     [35, 'large'],\r
2817     [44, 'extra large']\r
2818 ]\r
2819      * </code></pre>\r
2820      * </li>\r
2821      * <li><b>Object</b> :\r
2822      * <pre><code>\r
2823 labelField: 'name', // override default of 'text'\r
2824 options: [\r
2825     {id: 11, name:'extra small'}, \r
2826     {id: 18, name:'small'}, \r
2827     {id: 22, name:'medium'}, \r
2828     {id: 35, name:'large'}, \r
2829     {id: 44, name:'extra large'} \r
2830 ]\r
2831      * </code></pre>\r
2832      * </li>\r
2833      * <li><b>String</b> :\r
2834      * <pre><code>\r
2835      * options: ['extra small', 'small', 'medium', 'large', 'extra large']\r
2836      * </code></pre>\r
2837      * </li>\r
2838      */\r
2839     /**\r
2840      * @cfg {Boolean} phpMode\r
2841      * <p>Adjust the format of this filter. Defaults to false.</p>\r
2842      * <br><p>When GridFilters <code>@cfg encode = false</code> (default):</p>\r
2843      * <pre><code>\r
2844 // phpMode == false (default):\r
2845 filter[0][data][type] list\r
2846 filter[0][data][value] value1\r
2847 filter[0][data][value] value2\r
2848 filter[0][field] prod \r
2849 \r
2850 // phpMode == true:\r
2851 filter[0][data][type] list\r
2852 filter[0][data][value] value1, value2\r
2853 filter[0][field] prod \r
2854      * </code></pre>\r
2855      * When GridFilters <code>@cfg encode = true</code>:\r
2856      * <pre><code>\r
2857 // phpMode == false (default):\r
2858 filter : [{"type":"list","value":["small","medium"],"field":"size"}]\r
2859 \r
2860 // phpMode == true:\r
2861 filter : [{"type":"list","value":"small,medium","field":"size"}]\r
2862      * </code></pre>\r
2863      */\r
2864     phpMode : false,\r
2865     /**\r
2866      * @cfg {Ext.data.Store} store\r
2867      * The {@link Ext.data.Store} this list should use as its data source\r
2868      * when the data source is <b>remote</b>. If the data for the list\r
2869      * is local, use the <code>{@link #options}</code> config instead.\r
2870      */\r
2871 \r
2872     /**  \r
2873      * @private\r
2874      * Template method that is to initialize the filter and install required menu items.\r
2875      * @param {Object} config\r
2876      */\r
2877     init : function (config) {\r
2878         this.dt = new Ext.util.DelayedTask(this.fireUpdate, this);\r
2879 \r
2880         // if a menu already existed, do clean up first\r
2881         if (this.menu){\r
2882             this.menu.destroy();\r
2883         }\r
2884         this.menu = new Ext.ux.menu.ListMenu(config);\r
2885         this.menu.on('checkchange', this.onCheckChange, this);\r
2886     },\r
2887     \r
2888     /**\r
2889      * @private\r
2890      * Template method that is to get and return the value of the filter.\r
2891      * @return {String} The value of this filter\r
2892      */\r
2893     getValue : function () {\r
2894         return this.menu.getSelected();\r
2895     },\r
2896     /**\r
2897      * @private\r
2898      * Template method that is to set the value of the filter.\r
2899      * @param {Object} value The value to set the filter\r
2900      */ \r
2901     setValue : function (value) {\r
2902         this.menu.setSelected(value);\r
2903         this.fireEvent('update', this);\r
2904     },\r
2905 \r
2906     /**\r
2907      * @private\r
2908      * Template method that is to return <tt>true</tt> if the filter\r
2909      * has enough configuration information to be activated.\r
2910      * @return {Boolean}\r
2911      */\r
2912     isActivatable : function () {\r
2913         return this.getValue().length > 0;\r
2914     },\r
2915     \r
2916     /**\r
2917      * @private\r
2918      * Template method that is to get and return serialized filter data for\r
2919      * transmission to the server.\r
2920      * @return {Object/Array} An object or collection of objects containing\r
2921      * key value pairs representing the current configuration of the filter.\r
2922      */\r
2923     getSerialArgs : function () {\r
2924         var args = {type: 'list', value: this.phpMode ? this.getValue().join(',') : this.getValue()};\r
2925         return args;\r
2926     },\r
2927 \r
2928     /** @private */\r
2929     onCheckChange : function(){\r
2930         this.dt.delay(this.updateBuffer);\r
2931     },\r
2932     \r
2933     \r
2934     /**\r
2935      * Template method that is to validate the provided Ext.data.Record\r
2936      * against the filters configuration.\r
2937      * @param {Ext.data.Record} record The record to validate\r
2938      * @return {Boolean} true if the record is valid within the bounds\r
2939      * of the filter, false otherwise.\r
2940      */\r
2941     validateRecord : function (record) {\r
2942         return this.getValue().indexOf(record.get(this.dataIndex)) > -1;\r
2943     }\r
2944 });/** \r
2945  * @class Ext.ux.grid.filter.NumericFilter\r
2946  * @extends Ext.ux.grid.filter.Filter\r
2947  * Filters using an Ext.ux.menu.RangeMenu.\r
2948  * <p><b><u>Example Usage:</u></b></p>\r
2949  * <pre><code>    \r
2950 var filters = new Ext.ux.grid.GridFilters({\r
2951     ...\r
2952     filters: [{\r
2953         type: 'numeric',\r
2954         dataIndex: 'price'\r
2955     }]\r
2956 });\r
2957  * </code></pre> \r
2958  */\r
2959 Ext.ux.grid.filter.NumericFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
2960 \r
2961     /**\r
2962      * @cfg {Object} fieldCls\r
2963      * The Class to use to construct each field item within this menu\r
2964      * Defaults to:<pre>\r
2965      * fieldCls : Ext.form.NumberField\r
2966      * </pre>\r
2967      */\r
2968     fieldCls : Ext.form.NumberField,\r
2969     /**\r
2970      * @cfg {Object} fieldCfg\r
2971      * The default configuration options for any field item unless superseded\r
2972      * by the <code>{@link #fields}</code> configuration.\r
2973      * Defaults to:<pre>\r
2974      * fieldCfg : {}\r
2975      * </pre>\r
2976      * Example usage:\r
2977      * <pre><code>\r
2978 fieldCfg : {\r
2979     width: 150,\r
2980 },\r
2981      * </code></pre>\r
2982      */\r
2983     /**\r
2984      * @cfg {Object} fields\r
2985      * The field items may be configured individually\r
2986      * Defaults to <tt>undefined</tt>.\r
2987      * Example usage:\r
2988      * <pre><code>\r
2989 fields : {\r
2990     gt: { // override fieldCfg options\r
2991         width: 200,\r
2992         fieldCls: Ext.ux.form.CustomNumberField // to override default {@link #fieldCls}\r
2993     }\r
2994 },\r
2995      * </code></pre>\r
2996      */\r
2997     /**\r
2998      * @cfg {Object} iconCls\r
2999      * The iconCls to be applied to each comparator field item.\r
3000      * Defaults to:<pre>\r
3001 iconCls : {\r
3002     gt : 'ux-rangemenu-gt',\r
3003     lt : 'ux-rangemenu-lt',\r
3004     eq : 'ux-rangemenu-eq'\r
3005 }\r
3006      * </pre>\r
3007      */\r
3008     iconCls : {\r
3009         gt : 'ux-rangemenu-gt',\r
3010         lt : 'ux-rangemenu-lt',\r
3011         eq : 'ux-rangemenu-eq'\r
3012     },\r
3013 \r
3014     /**\r
3015      * @cfg {Object} menuItemCfgs\r
3016      * Default configuration options for each menu item\r
3017      * Defaults to:<pre>\r
3018 menuItemCfgs : {\r
3019     emptyText: 'Enter Filter Text...',\r
3020     selectOnFocus: true,\r
3021     width: 125\r
3022 }\r
3023      * </pre>\r
3024      */\r
3025     menuItemCfgs : {\r
3026         emptyText: 'Enter Filter Text...',\r
3027         selectOnFocus: true,\r
3028         width: 125\r
3029     },\r
3030 \r
3031     /**\r
3032      * @cfg {Array} menuItems\r
3033      * The items to be shown in this menu.  Items are added to the menu\r
3034      * according to their position within this array. Defaults to:<pre>\r
3035      * menuItems : ['lt','gt','-','eq']\r
3036      * </pre>\r
3037      */\r
3038     menuItems : ['lt', 'gt', '-', 'eq'],\r
3039 \r
3040     /**  \r
3041      * @private\r
3042      * Template method that is to initialize the filter and install required menu items.\r
3043      */\r
3044     init : function (config) {\r
3045         // if a menu already existed, do clean up first\r
3046         if (this.menu){\r
3047             this.menu.destroy();\r
3048         }        \r
3049         this.menu = new Ext.ux.menu.RangeMenu(Ext.apply(config, {\r
3050             // pass along filter configs to the menu\r
3051             fieldCfg : this.fieldCfg || {},\r
3052             fieldCls : this.fieldCls,\r
3053             fields : this.fields || {},\r
3054             iconCls: this.iconCls,\r
3055             menuItemCfgs: this.menuItemCfgs,\r
3056             menuItems: this.menuItems,\r
3057             updateBuffer: this.updateBuffer\r
3058         }));\r
3059         // relay the event fired by the menu\r
3060         this.menu.on('update', this.fireUpdate, this);\r
3061     },\r
3062     \r
3063     /**\r
3064      * @private\r
3065      * Template method that is to get and return the value of the filter.\r
3066      * @return {String} The value of this filter\r
3067      */\r
3068     getValue : function () {\r
3069         return this.menu.getValue();\r
3070     },\r
3071 \r
3072     /**\r
3073      * @private\r
3074      * Template method that is to set the value of the filter.\r
3075      * @param {Object} value The value to set the filter\r
3076      */ \r
3077     setValue : function (value) {\r
3078         this.menu.setValue(value);\r
3079     },\r
3080 \r
3081     /**\r
3082      * @private\r
3083      * Template method that is to return <tt>true</tt> if the filter\r
3084      * has enough configuration information to be activated.\r
3085      * @return {Boolean}\r
3086      */\r
3087     isActivatable : function () {\r
3088         var values = this.getValue();\r
3089         for (key in values) {\r
3090             if (values[key] !== undefined) {\r
3091                 return true;\r
3092             }\r
3093         }\r
3094         return false;\r
3095     },\r
3096     \r
3097     /**\r
3098      * @private\r
3099      * Template method that is to get and return serialized filter data for\r
3100      * transmission to the server.\r
3101      * @return {Object/Array} An object or collection of objects containing\r
3102      * key value pairs representing the current configuration of the filter.\r
3103      */\r
3104     getSerialArgs : function () {\r
3105         var key,\r
3106             args = [],\r
3107             values = this.menu.getValue();\r
3108         for (key in values) {\r
3109             args.push({\r
3110                 type: 'numeric',\r
3111                 comparison: key,\r
3112                 value: values[key]\r
3113             });\r
3114         }\r
3115         return args;\r
3116     },\r
3117 \r
3118     /**\r
3119      * Template method that is to validate the provided Ext.data.Record\r
3120      * against the filters configuration.\r
3121      * @param {Ext.data.Record} record The record to validate\r
3122      * @return {Boolean} true if the record is valid within the bounds\r
3123      * of the filter, false otherwise.\r
3124      */\r
3125     validateRecord : function (record) {\r
3126         var val = record.get(this.dataIndex),\r
3127             values = this.getValue();\r
3128         if (values.eq !== undefined && val != values.eq) {\r
3129             return false;\r
3130         }\r
3131         if (values.lt !== undefined && val >= values.lt) {\r
3132             return false;\r
3133         }\r
3134         if (values.gt !== undefined && val <= values.gt) {\r
3135             return false;\r
3136         }\r
3137         return true;\r
3138     }\r
3139 });/** \r
3140  * @class Ext.ux.grid.filter.StringFilter\r
3141  * @extends Ext.ux.grid.filter.Filter\r
3142  * Filter by a configurable Ext.form.TextField\r
3143  * <p><b><u>Example Usage:</u></b></p>\r
3144  * <pre><code>    \r
3145 var filters = new Ext.ux.grid.GridFilters({\r
3146     ...\r
3147     filters: [{\r
3148         // required configs\r
3149         type: 'string',\r
3150         dataIndex: 'name',\r
3151         \r
3152         // optional configs\r
3153         value: 'foo',\r
3154         active: true, // default is false\r
3155         iconCls: 'ux-gridfilter-text-icon' // default\r
3156         // any Ext.form.TextField configs accepted\r
3157     }]\r
3158 });\r
3159  * </code></pre>\r
3160  */\r
3161 Ext.ux.grid.filter.StringFilter = Ext.extend(Ext.ux.grid.filter.Filter, {\r
3162 \r
3163     /**\r
3164      * @cfg {String} iconCls\r
3165      * The iconCls to be applied to the menu item.\r
3166      * Defaults to <tt>'ux-gridfilter-text-icon'</tt>.\r
3167      */\r
3168     iconCls : 'ux-gridfilter-text-icon',\r
3169 \r
3170     emptyText: 'Enter Filter Text...',\r
3171     selectOnFocus: true,\r
3172     width: 125,\r
3173     \r
3174     /**  \r
3175      * @private\r
3176      * Template method that is to initialize the filter and install required menu items.\r
3177      */\r
3178     init : function (config) {\r
3179         Ext.applyIf(config, {\r
3180             enableKeyEvents: true,\r
3181             iconCls: this.iconCls,\r
3182             listeners: {\r
3183                 scope: this,\r
3184                 keyup: this.onInputKeyUp\r
3185             }\r
3186         });\r
3187 \r
3188         this.inputItem = new Ext.form.TextField(config); \r
3189         this.menu.add(this.inputItem);\r
3190         this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this);\r
3191     },\r
3192     \r
3193     /**\r
3194      * @private\r
3195      * Template method that is to get and return the value of the filter.\r
3196      * @return {String} The value of this filter\r
3197      */\r
3198     getValue : function () {\r
3199         return this.inputItem.getValue();\r
3200     },\r
3201     \r
3202     /**\r
3203      * @private\r
3204      * Template method that is to set the value of the filter.\r
3205      * @param {Object} value The value to set the filter\r
3206      */ \r
3207     setValue : function (value) {\r
3208         this.inputItem.setValue(value);\r
3209         this.fireEvent('update', this);\r
3210     },\r
3211 \r
3212     /**\r
3213      * @private\r
3214      * Template method that is to return <tt>true</tt> if the filter\r
3215      * has enough configuration information to be activated.\r
3216      * @return {Boolean}\r
3217      */\r
3218     isActivatable : function () {\r
3219         return this.inputItem.getValue().length > 0;\r
3220     },\r
3221 \r
3222     /**\r
3223      * @private\r
3224      * Template method that is to get and return serialized filter data for\r
3225      * transmission to the server.\r
3226      * @return {Object/Array} An object or collection of objects containing\r
3227      * key value pairs representing the current configuration of the filter.\r
3228      */\r
3229     getSerialArgs : function () {\r
3230         return {type: 'string', value: this.getValue()};\r
3231     },\r
3232 \r
3233     /**\r
3234      * Template method that is to validate the provided Ext.data.Record\r
3235      * against the filters configuration.\r
3236      * @param {Ext.data.Record} record The record to validate\r
3237      * @return {Boolean} true if the record is valid within the bounds\r
3238      * of the filter, false otherwise.\r
3239      */\r
3240     validateRecord : function (record) {\r
3241         var val = record.get(this.dataIndex);\r
3242 \r
3243         if(typeof val != 'string') {\r
3244             return (this.getValue().length === 0);\r
3245         }\r
3246 \r
3247         return val.toLowerCase().indexOf(this.getValue().toLowerCase()) > -1;\r
3248     },\r
3249     \r
3250     /**  \r
3251      * @private\r
3252      * Handler method called when there is a keyup event on this.inputItem\r
3253      */\r
3254     onInputKeyUp : function (field, e) {\r
3255         var k = e.getKey();\r
3256         if (k == e.RETURN && field.isValid()) {\r
3257             e.stopEvent();\r
3258             this.menu.hide(true);\r
3259             return;\r
3260         }\r
3261         // restart the timer\r
3262         this.updateTask.delay(this.updateBuffer);\r
3263     }\r
3264 });\r
3265 Ext.namespace('Ext.ux.menu');\r
3266 \r
3267 /** \r
3268  * @class Ext.ux.menu.ListMenu\r
3269  * @extends Ext.menu.Menu\r
3270  * This is a supporting class for {@link Ext.ux.grid.filter.ListFilter}.\r
3271  * Although not listed as configuration options for this class, this class\r
3272  * also accepts all configuration options from {@link Ext.ux.grid.filter.ListFilter}.\r
3273  */\r
3274 Ext.ux.menu.ListMenu = Ext.extend(Ext.menu.Menu, {\r
3275     /**\r
3276      * @cfg {String} labelField\r
3277      * Defaults to 'text'.\r
3278      */\r
3279     labelField :  'text',\r
3280     /**\r
3281      * @cfg {String} paramPrefix\r
3282      * Defaults to 'Loading...'.\r
3283      */\r
3284     loadingText : 'Loading...',\r
3285     /**\r
3286      * @cfg {Boolean} loadOnShow\r
3287      * Defaults to true.\r
3288      */\r
3289     loadOnShow : true,\r
3290     /**\r
3291      * @cfg {Boolean} single\r
3292      * Specify true to group all items in this list into a single-select\r
3293      * radio button group. Defaults to false.\r
3294      */\r
3295     single : false,\r
3296 \r
3297     constructor : function (cfg) {\r
3298         this.selected = [];\r
3299         this.addEvents(\r
3300             /**\r
3301              * @event checkchange\r
3302              * Fires when there is a change in checked items from this list\r
3303              * @param {Object} item Ext.menu.CheckItem\r
3304              * @param {Object} checked The checked value that was set\r
3305              */\r
3306             'checkchange'\r
3307         );\r
3308       \r
3309         Ext.ux.menu.ListMenu.superclass.constructor.call(this, cfg = cfg || {});\r
3310     \r
3311         if(!cfg.store && cfg.options){\r
3312             var options = [];\r
3313             for(var i=0, len=cfg.options.length; i<len; i++){\r
3314                 var value = cfg.options[i];\r
3315                 switch(Ext.type(value)){\r
3316                     case 'array':  options.push(value); break;\r
3317                     case 'object': options.push([value.id, value[this.labelField]]); break;\r
3318                     case 'string': options.push([value, value]); break;\r
3319                 }\r
3320             }\r
3321             \r
3322             this.store = new Ext.data.Store({\r
3323                 reader: new Ext.data.ArrayReader({id: 0}, ['id', this.labelField]),\r
3324                 data:   options,\r
3325                 listeners: {\r
3326                     'load': this.onLoad,\r
3327                     scope:  this\r
3328                 }\r
3329             });\r
3330             this.loaded = true;\r
3331         } else {\r
3332             this.add({text: this.loadingText, iconCls: 'loading-indicator'});\r
3333             this.store.on('load', this.onLoad, this);\r
3334         }\r
3335     },\r
3336 \r
3337     destroy : function () {\r
3338         if (this.store) {\r
3339             this.store.destroy();    \r
3340         }\r
3341         Ext.ux.menu.ListMenu.superclass.destroy.call(this);\r
3342     },\r
3343 \r
3344     /**\r
3345      * Lists will initially show a 'loading' item while the data is retrieved from the store.\r
3346      * In some cases the loaded data will result in a list that goes off the screen to the\r
3347      * right (as placement calculations were done with the loading item). This adapter will\r
3348      * allow show to be called with no arguments to show with the previous arguments and\r
3349      * thus recalculate the width and potentially hang the menu from the left.\r
3350      */\r
3351     show : function () {\r
3352         var lastArgs = null;\r
3353         return function(){\r
3354             if(arguments.length === 0){\r
3355                 Ext.ux.menu.ListMenu.superclass.show.apply(this, lastArgs);\r
3356             } else {\r
3357                 lastArgs = arguments;\r
3358                 if (this.loadOnShow && !this.loaded) {\r
3359                     this.store.load();\r
3360                 }\r
3361                 Ext.ux.menu.ListMenu.superclass.show.apply(this, arguments);\r
3362             }\r
3363         };\r
3364     }(),\r
3365     \r
3366     /** @private */\r
3367     onLoad : function (store, records) {\r
3368         var visible = this.isVisible();\r
3369         this.hide(false);\r
3370         \r
3371         this.removeAll(true);\r
3372         \r
3373         var gid = this.single ? Ext.id() : null;\r
3374         for(var i=0, len=records.length; i<len; i++){\r
3375             var item = new Ext.menu.CheckItem({\r
3376                 text:    records[i].get(this.labelField), \r
3377                 group:   gid,\r
3378                 checked: this.selected.indexOf(records[i].id) > -1,\r
3379                 hideOnClick: false});\r
3380             \r
3381             item.itemId = records[i].id;\r
3382             item.on('checkchange', this.checkChange, this);\r
3383                         \r
3384             this.add(item);\r
3385         }\r
3386         \r
3387         this.loaded = true;\r
3388         \r
3389         if (visible) {\r
3390             this.show();\r
3391         }       \r
3392         this.fireEvent('load', this, records);\r
3393     },\r
3394 \r
3395     /**\r
3396      * Get the selected items.\r
3397      * @return {Array} selected\r
3398      */\r
3399     getSelected : function () {\r
3400         return this.selected;\r
3401     },\r
3402     \r
3403     /** @private */\r
3404     setSelected : function (value) {\r
3405         value = this.selected = [].concat(value);\r
3406 \r
3407         if (this.loaded) {\r
3408             this.items.each(function(item){\r
3409                 item.setChecked(false, true);\r
3410                 for (var i = 0, len = value.length; i < len; i++) {\r
3411                     if (item.itemId == value[i]) {\r
3412                         item.setChecked(true, true);\r
3413                     }\r
3414                 }\r
3415             }, this);\r
3416         }\r
3417     },\r
3418     \r
3419     /**\r
3420      * Handler for the 'checkchange' event from an check item in this menu\r
3421      * @param {Object} item Ext.menu.CheckItem\r
3422      * @param {Object} checked The checked value that was set\r
3423      */\r
3424     checkChange : function (item, checked) {\r
3425         var value = [];\r
3426         this.items.each(function(item){\r
3427             if (item.checked) {\r
3428                 value.push(item.itemId);\r
3429             }\r
3430         },this);\r
3431         this.selected = value;\r
3432         \r
3433         this.fireEvent('checkchange', item, checked);\r
3434     }    \r
3435 });Ext.ns('Ext.ux.menu');\r
3436 \r
3437 /** \r
3438  * @class Ext.ux.menu.RangeMenu\r
3439  * @extends Ext.menu.Menu\r
3440  * Custom implementation of Ext.menu.Menu that has preconfigured\r
3441  * items for gt, lt, eq.\r
3442  * <p><b><u>Example Usage:</u></b></p>\r
3443  * <pre><code>    \r
3444 \r
3445  * </code></pre> \r
3446  */\r
3447 Ext.ux.menu.RangeMenu = Ext.extend(Ext.menu.Menu, {\r
3448 \r
3449     constructor : function (config) {\r
3450 \r
3451         Ext.ux.menu.RangeMenu.superclass.constructor.call(this, config);\r
3452 \r
3453         this.addEvents(\r
3454             /**\r
3455              * @event update\r
3456              * Fires when a filter configuration has changed\r
3457              * @param {Ext.ux.grid.filter.Filter} this The filter object.\r
3458              */\r
3459             'update'\r
3460         );\r
3461       \r
3462         this.updateTask = new Ext.util.DelayedTask(this.fireUpdate, this);\r
3463     \r
3464         var i, len, item, cfg, Cls;\r
3465 \r
3466         for (i = 0, len = this.menuItems.length; i < len; i++) {\r
3467             item = this.menuItems[i];\r
3468             if (item !== '-') {\r
3469                 // defaults\r
3470                 cfg = {\r
3471                     itemId: 'range-' + item,\r
3472                     enableKeyEvents: true,\r
3473                     iconCls: this.iconCls[item] || 'no-icon',\r
3474                     listeners: {\r
3475                         scope: this,\r
3476                         keyup: this.onInputKeyUp\r
3477                     }\r
3478                 };\r
3479                 Ext.apply(\r
3480                     cfg,\r
3481                     // custom configs\r
3482                     Ext.applyIf(this.fields[item] || {}, this.fieldCfg[item]),\r
3483                     // configurable defaults\r
3484                     this.menuItemCfgs\r
3485                 );\r
3486                 Cls = cfg.fieldCls || this.fieldCls;\r
3487                 item = this.fields[item] = new Cls(cfg);\r
3488             }\r
3489             this.add(item);\r
3490         }\r
3491     },\r
3492 \r
3493     /**\r
3494      * @private\r
3495      * called by this.updateTask\r
3496      */\r
3497     fireUpdate : function () {\r
3498         this.fireEvent('update', this);\r
3499     },\r
3500     \r
3501     /**\r
3502      * Get and return the value of the filter.\r
3503      * @return {String} The value of this filter\r
3504      */\r
3505     getValue : function () {\r
3506         var result = {}, key, field;\r
3507         for (key in this.fields) {\r
3508             field = this.fields[key];\r
3509             if (field.isValid() && String(field.getValue()).length > 0) {\r
3510                 result[key] = field.getValue();\r
3511             }\r
3512         }\r
3513         return result;\r
3514     },\r
3515   \r
3516     /**\r
3517      * Set the value of this menu and fires the 'update' event.\r
3518      * @param {Object} data The data to assign to this menu\r
3519      */ \r
3520     setValue : function (data) {\r
3521         var key;\r
3522         for (key in this.fields) {\r
3523             this.fields[key].setValue(data[key] !== undefined ? data[key] : '');\r
3524         }\r
3525         this.fireEvent('update', this);\r
3526     },\r
3527 \r
3528     /**  \r
3529      * @private\r
3530      * Handler method called when there is a keyup event on an input\r
3531      * item of this menu.\r
3532      */\r
3533     onInputKeyUp : function (field, e) {\r
3534         var k = e.getKey();\r
3535         if (k == e.RETURN && field.isValid()) {\r
3536             e.stopEvent();\r
3537             this.hide(true);\r
3538             return;\r
3539         }\r
3540         \r
3541         if (field == this.fields.eq) {\r
3542             if (this.fields.gt) {\r
3543                 this.fields.gt.setValue(null);\r
3544             }\r
3545             if (this.fields.lt) {\r
3546                 this.fields.lt.setValue(null);\r
3547             }\r
3548         }\r
3549         else {\r
3550             this.fields.eq.setValue(null);\r
3551         }\r
3552         \r
3553         // restart the timer\r
3554         this.updateTask.delay(this.updateBuffer);\r
3555     }\r
3556 });\r
3557 Ext.ns('Ext.ux.grid');\r
3558 \r
3559 /**\r
3560  * @class Ext.ux.grid.GroupSummary\r
3561  * @extends Ext.util.Observable\r
3562  * A GridPanel plugin that enables dynamic column calculations and a dynamically\r
3563  * updated grouped summary row.\r
3564  */\r
3565 Ext.ux.grid.GroupSummary = Ext.extend(Ext.util.Observable, {\r
3566     /**\r
3567      * @cfg {Function} summaryRenderer Renderer example:<pre><code>\r
3568 summaryRenderer: function(v, params, data){\r
3569     return ((v === 0 || v > 1) ? '(' + v +' Tasks)' : '(1 Task)');\r
3570 },\r
3571      * </code></pre>\r
3572      */\r
3573     /**\r
3574      * @cfg {String} summaryType (Optional) The type of\r
3575      * calculation to be used for the column.  For options available see\r
3576      * {@link #Calculations}.\r
3577      */\r
3578 \r
3579     constructor : function(config){\r
3580         Ext.apply(this, config);\r
3581         Ext.ux.grid.GroupSummary.superclass.constructor.call(this);\r
3582     },\r
3583     init : function(grid){\r
3584         this.grid = grid;\r
3585         var v = this.view = grid.getView();\r
3586         v.doGroupEnd = this.doGroupEnd.createDelegate(this);\r
3587 \r
3588         v.afterMethod('onColumnWidthUpdated', this.doWidth, this);\r
3589         v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);\r
3590         v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);\r
3591         v.afterMethod('onUpdate', this.doUpdate, this);\r
3592         v.afterMethod('onRemove', this.doRemove, this);\r
3593 \r
3594         if(!this.rowTpl){\r
3595             this.rowTpl = new Ext.Template(\r
3596                 '<div class="x-grid3-summary-row" style="{tstyle}">',\r
3597                 '<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',\r
3598                     '<tbody><tr>{cells}</tr></tbody>',\r
3599                 '</table></div>'\r
3600             );\r
3601             this.rowTpl.disableFormats = true;\r
3602         }\r
3603         this.rowTpl.compile();\r
3604 \r
3605         if(!this.cellTpl){\r
3606             this.cellTpl = new Ext.Template(\r
3607                 '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',\r
3608                 '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on">{value}</div>',\r
3609                 "</td>"\r
3610             );\r
3611             this.cellTpl.disableFormats = true;\r
3612         }\r
3613         this.cellTpl.compile();\r
3614     },\r
3615 \r
3616     /**\r
3617      * Toggle the display of the summary row on/off\r
3618      * @param {Boolean} visible <tt>true</tt> to show the summary, <tt>false</tt> to hide the summary.\r
3619      */\r
3620     toggleSummaries : function(visible){\r
3621         var el = this.grid.getGridEl();\r
3622         if(el){\r
3623             if(visible === undefined){\r
3624                 visible = el.hasClass('x-grid-hide-summary');\r
3625             }\r
3626             el[visible ? 'removeClass' : 'addClass']('x-grid-hide-summary');\r
3627         }\r
3628     },\r
3629 \r
3630     renderSummary : function(o, cs){\r
3631         cs = cs || this.view.getColumnData();\r
3632         var cfg = this.grid.getColumnModel().config,\r
3633             buf = [], c, p = {}, cf, last = cs.length-1;\r
3634         for(var i = 0, len = cs.length; i < len; i++){\r
3635             c = cs[i];\r
3636             cf = cfg[i];\r
3637             p.id = c.id;\r
3638             p.style = c.style;\r
3639             p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');\r
3640             if(cf.summaryType || cf.summaryRenderer){\r
3641                 p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);\r
3642             }else{\r
3643                 p.value = '';\r
3644             }\r
3645             if(p.value == undefined || p.value === "") p.value = "&#160;";\r
3646             buf[buf.length] = this.cellTpl.apply(p);\r
3647         }\r
3648 \r
3649         return this.rowTpl.apply({\r
3650             tstyle: 'width:'+this.view.getTotalWidth()+';',\r
3651             cells: buf.join('')\r
3652         });\r
3653     },\r
3654 \r
3655     /**\r
3656      * @private\r
3657      * @param {Object} rs\r
3658      * @param {Object} cs\r
3659      */\r
3660     calculate : function(rs, cs){\r
3661         var data = {}, r, c, cfg = this.grid.getColumnModel().config, cf;\r
3662         for(var j = 0, jlen = rs.length; j < jlen; j++){\r
3663             r = rs[j];\r
3664             for(var i = 0, len = cs.length; i < len; i++){\r
3665                 c = cs[i];\r
3666                 cf = cfg[i];\r
3667                 if(cf.summaryType){\r
3668                     data[c.name] = Ext.ux.grid.GroupSummary.Calculations[cf.summaryType](data[c.name] || 0, r, c.name, data);\r
3669                 }\r
3670             }\r
3671         }\r
3672         return data;\r
3673     },\r
3674 \r
3675     doGroupEnd : function(buf, g, cs, ds, colCount){\r
3676         var data = this.calculate(g.rs, cs);\r
3677         buf.push('</div>', this.renderSummary({data: data}, cs), '</div>');\r
3678     },\r
3679 \r
3680     doWidth : function(col, w, tw){\r
3681         var gs = this.view.getGroups(), s;\r
3682         for(var i = 0, len = gs.length; i < len; i++){\r
3683             s = gs[i].childNodes[2];\r
3684             s.style.width = tw;\r
3685             s.firstChild.style.width = tw;\r
3686             s.firstChild.rows[0].childNodes[col].style.width = w;\r
3687         }\r
3688     },\r
3689 \r
3690     doAllWidths : function(ws, tw){\r
3691         var gs = this.view.getGroups(), s, cells, wlen = ws.length;\r
3692         for(var i = 0, len = gs.length; i < len; i++){\r
3693             s = gs[i].childNodes[2];\r
3694             s.style.width = tw;\r
3695             s.firstChild.style.width = tw;\r
3696             cells = s.firstChild.rows[0].childNodes;\r
3697             for(var j = 0; j < wlen; j++){\r
3698                 cells[j].style.width = ws[j];\r
3699             }\r
3700         }\r
3701     },\r
3702 \r
3703     doHidden : function(col, hidden, tw){\r
3704         var gs = this.view.getGroups(), s, display = hidden ? 'none' : '';\r
3705         for(var i = 0, len = gs.length; i < len; i++){\r
3706             s = gs[i].childNodes[2];\r
3707             s.style.width = tw;\r
3708             s.firstChild.style.width = tw;\r
3709             s.firstChild.rows[0].childNodes[col].style.display = display;\r
3710         }\r
3711     },\r
3712 \r
3713     // Note: requires that all (or the first) record in the\r
3714     // group share the same group value. Returns false if the group\r
3715     // could not be found.\r
3716     refreshSummary : function(groupValue){\r
3717         return this.refreshSummaryById(this.view.getGroupId(groupValue));\r
3718     },\r
3719 \r
3720     getSummaryNode : function(gid){\r
3721         var g = Ext.fly(gid, '_gsummary');\r
3722         if(g){\r
3723             return g.down('.x-grid3-summary-row', true);\r
3724         }\r
3725         return null;\r
3726     },\r
3727 \r
3728     refreshSummaryById : function(gid){\r
3729         var g = Ext.getDom(gid);\r
3730         if(!g){\r
3731             return false;\r
3732         }\r
3733         var rs = [];\r
3734         this.grid.getStore().each(function(r){\r
3735             if(r._groupId == gid){\r
3736                 rs[rs.length] = r;\r
3737             }\r
3738         });\r
3739         var cs = this.view.getColumnData(),\r
3740             data = this.calculate(rs, cs),\r
3741             markup = this.renderSummary({data: data}, cs),\r
3742             existing = this.getSummaryNode(gid);\r
3743             \r
3744         if(existing){\r
3745             g.removeChild(existing);\r
3746         }\r
3747         Ext.DomHelper.append(g, markup);\r
3748         return true;\r
3749     },\r
3750 \r
3751     doUpdate : function(ds, record){\r
3752         this.refreshSummaryById(record._groupId);\r
3753     },\r
3754 \r
3755     doRemove : function(ds, record, index, isUpdate){\r
3756         if(!isUpdate){\r
3757             this.refreshSummaryById(record._groupId);\r
3758         }\r
3759     },\r
3760 \r
3761     /**\r
3762      * Show a message in the summary row.\r
3763      * <pre><code>\r
3764 grid.on('afteredit', function(){\r
3765     var groupValue = 'Ext Forms: Field Anchoring';\r
3766     summary.showSummaryMsg(groupValue, 'Updating Summary...');\r
3767 });\r
3768      * </code></pre>\r
3769      * @param {String} groupValue\r
3770      * @param {String} msg Text to use as innerHTML for the summary row.\r
3771      */\r
3772     showSummaryMsg : function(groupValue, msg){\r
3773         var gid = this.view.getGroupId(groupValue),\r
3774              node = this.getSummaryNode(gid);\r
3775         if(node){\r
3776             node.innerHTML = '<div class="x-grid3-summary-msg">' + msg + '</div>';\r
3777         }\r
3778     }\r
3779 });\r
3780 \r
3781 //backwards compat\r
3782 Ext.grid.GroupSummary = Ext.ux.grid.GroupSummary;\r
3783 \r
3784 \r
3785 /**\r
3786  * Calculation types for summary row:</p><div class="mdetail-params"><ul>\r
3787  * <li><b><tt>sum</tt></b> : <div class="sub-desc"></div></li>\r
3788  * <li><b><tt>count</tt></b> : <div class="sub-desc"></div></li>\r
3789  * <li><b><tt>max</tt></b> : <div class="sub-desc"></div></li>\r
3790  * <li><b><tt>min</tt></b> : <div class="sub-desc"></div></li>\r
3791  * <li><b><tt>average</tt></b> : <div class="sub-desc"></div></li>\r
3792  * </ul></div>\r
3793  * <p>Custom calculations may be implemented.  An example of\r
3794  * custom <code>summaryType=totalCost</code>:</p><pre><code>\r
3795 // define a custom summary function\r
3796 Ext.ux.grid.GroupSummary.Calculations['totalCost'] = function(v, record, field){\r
3797     return v + (record.data.estimate * record.data.rate);\r
3798 };\r
3799  * </code></pre>\r
3800  * @property Calculations\r
3801  */\r
3802 \r
3803 Ext.ux.grid.GroupSummary.Calculations = {\r
3804     'sum' : function(v, record, field){\r
3805         return v + (record.data[field]||0);\r
3806     },\r
3807 \r
3808     'count' : function(v, record, field, data){\r
3809         return data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
3810     },\r
3811 \r
3812     'max' : function(v, record, field, data){\r
3813         var v = record.data[field];\r
3814         var max = data[field+'max'] === undefined ? (data[field+'max'] = v) : data[field+'max'];\r
3815         return v > max ? (data[field+'max'] = v) : max;\r
3816     },\r
3817 \r
3818     'min' : function(v, record, field, data){\r
3819         var v = record.data[field];\r
3820         var min = data[field+'min'] === undefined ? (data[field+'min'] = v) : data[field+'min'];\r
3821         return v < min ? (data[field+'min'] = v) : min;\r
3822     },\r
3823 \r
3824     'average' : function(v, record, field, data){\r
3825         var c = data[field+'count'] ? ++data[field+'count'] : (data[field+'count'] = 1);\r
3826         var t = (data[field+'total'] = ((data[field+'total']||0) + (record.data[field]||0)));\r
3827         return t === 0 ? 0 : t / c;\r
3828     }\r
3829 };\r
3830 Ext.grid.GroupSummary.Calculations = Ext.ux.grid.GroupSummary.Calculations;\r
3831 \r
3832 /**\r
3833  * @class Ext.ux.grid.HybridSummary\r
3834  * @extends Ext.ux.grid.GroupSummary\r
3835  * Adds capability to specify the summary data for the group via json as illustrated here:\r
3836  * <pre><code>\r
3837 {\r
3838     data: [\r
3839         {\r
3840             projectId: 100,     project: 'House',\r
3841             taskId:    112, description: 'Paint',\r
3842             estimate:    6,        rate:     150,\r
3843             due:'06/24/2007'\r
3844         },\r
3845         ...\r
3846     ],\r
3847 \r
3848     summaryData: {\r
3849         'House': {\r
3850             description: 14, estimate: 9,\r
3851                    rate: 99, due: new Date(2009, 6, 29),\r
3852                    cost: 999\r
3853         }\r
3854     }\r
3855 }\r
3856  * </code></pre>\r
3857  *\r
3858  */\r
3859 Ext.ux.grid.HybridSummary = Ext.extend(Ext.ux.grid.GroupSummary, {\r
3860     /**\r
3861      * @private\r
3862      * @param {Object} rs\r
3863      * @param {Object} cs\r
3864      */\r
3865     calculate : function(rs, cs){\r
3866         var gcol = this.view.getGroupField(),\r
3867             gvalue = rs[0].data[gcol],\r
3868             gdata = this.getSummaryData(gvalue);\r
3869         return gdata || Ext.ux.grid.HybridSummary.superclass.calculate.call(this, rs, cs);\r
3870     },\r
3871 \r
3872     /**\r
3873      * <pre><code>\r
3874 grid.on('afteredit', function(){\r
3875     var groupValue = 'Ext Forms: Field Anchoring';\r
3876     summary.showSummaryMsg(groupValue, 'Updating Summary...');\r
3877     setTimeout(function(){ // simulate server call\r
3878         // HybridSummary class implements updateSummaryData\r
3879         summary.updateSummaryData(groupValue,\r
3880             // create data object based on configured dataIndex\r
3881             {description: 22, estimate: 888, rate: 888, due: new Date(), cost: 8});\r
3882     }, 2000);\r
3883 });\r
3884      * </code></pre>\r
3885      * @param {String} groupValue\r
3886      * @param {Object} data data object\r
3887      * @param {Boolean} skipRefresh (Optional) Defaults to false\r
3888      */\r
3889     updateSummaryData : function(groupValue, data, skipRefresh){\r
3890         var json = this.grid.getStore().reader.jsonData;\r
3891         if(!json.summaryData){\r
3892             json.summaryData = {};\r
3893         }\r
3894         json.summaryData[groupValue] = data;\r
3895         if(!skipRefresh){\r
3896             this.refreshSummary(groupValue);\r
3897         }\r
3898     },\r
3899 \r
3900     /**\r
3901      * Returns the summaryData for the specified groupValue or null.\r
3902      * @param {String} groupValue\r
3903      * @return {Object} summaryData\r
3904      */\r
3905     getSummaryData : function(groupValue){\r
3906         var json = this.grid.getStore().reader.jsonData;\r
3907         if(json && json.summaryData){\r
3908             return json.summaryData[groupValue];\r
3909         }\r
3910         return null;\r
3911     }\r
3912 });\r
3913 \r
3914 //backwards compat\r
3915 Ext.grid.HybridSummary = Ext.ux.grid.HybridSummary;\r
3916 Ext.ux.GroupTab = Ext.extend(Ext.Container, {\r
3917     mainItem: 0,\r
3918     \r
3919     expanded: true,\r
3920     \r
3921     deferredRender: true,\r
3922     \r
3923     activeTab: null,\r
3924     \r
3925     idDelimiter: '__',\r
3926     \r
3927     headerAsText: false,\r
3928     \r
3929     frame: false,\r
3930     \r
3931     hideBorders: true,\r
3932     \r
3933     initComponent: function(config){\r
3934         Ext.apply(this, config);\r
3935         this.frame = false;\r
3936         \r
3937         Ext.ux.GroupTab.superclass.initComponent.call(this);\r
3938         \r
3939         this.addEvents('activate', 'deactivate', 'changemainitem', 'beforetabchange', 'tabchange');\r
3940         \r
3941         this.setLayout(new Ext.layout.CardLayout({\r
3942             deferredRender: this.deferredRender\r
3943         }));\r
3944         \r
3945         if (!this.stack) {\r
3946             this.stack = Ext.TabPanel.AccessStack();\r
3947         }\r
3948         \r
3949         this.initItems();\r
3950         \r
3951         this.on('beforerender', function(){\r
3952             this.groupEl = this.ownerCt.getGroupEl(this);\r
3953         }, this);\r
3954         \r
3955         this.on('add', this.onAdd, this, {\r
3956             target: this\r
3957         });\r
3958         this.on('remove', this.onRemove, this, {\r
3959             target: this\r
3960         });\r
3961         \r
3962         if (this.mainItem !== undefined) {\r
3963             var item = (typeof this.mainItem == 'object') ? this.mainItem : this.items.get(this.mainItem);\r
3964             delete this.mainItem;\r
3965             this.setMainItem(item);\r
3966         }\r
3967     },\r
3968     \r
3969     /**\r
3970      * Sets the specified tab as the active tab. This method fires the {@link #beforetabchange} event which\r
3971      * can return false to cancel the tab change.\r
3972      * @param {String/Panel} tab The id or tab Panel to activate\r
3973      */\r
3974     setActiveTab : function(item){\r
3975         item = this.getComponent(item);\r
3976         if(!item){\r
3977             return false;\r
3978         }\r
3979         if(!this.rendered){\r
3980             this.activeTab = item;\r
3981             return true;\r
3982         }\r
3983         if(this.activeTab != item && this.fireEvent('beforetabchange', this, item, this.activeTab) !== false){\r
3984             if(this.activeTab && this.activeTab != this.mainItem){\r
3985                 var oldEl = this.getTabEl(this.activeTab);\r
3986                 if(oldEl){\r
3987                     Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');\r
3988                 }\r
3989             }\r
3990             var el = this.getTabEl(item);\r
3991             Ext.fly(el).addClass('x-grouptabs-strip-active');\r
3992             this.activeTab = item;\r
3993             this.stack.add(item);\r
3994 \r
3995             this.layout.setActiveItem(item);\r
3996             if(this.layoutOnTabChange && item.doLayout){\r
3997                 item.doLayout();\r
3998             }\r
3999             if(this.scrolling){\r
4000                 this.scrollToTab(item, this.animScroll);\r
4001             }\r
4002 \r
4003             this.fireEvent('tabchange', this, item);\r
4004             return true;\r
4005         }\r
4006         return false;\r
4007     },\r
4008     \r
4009     getTabEl: function(item){\r
4010         if (item == this.mainItem) {\r
4011             return this.groupEl;\r
4012         }\r
4013         return Ext.TabPanel.prototype.getTabEl.call(this, item);\r
4014     },\r
4015     \r
4016     onRender: function(ct, position){\r
4017         Ext.ux.GroupTab.superclass.onRender.call(this, ct, position);\r
4018         \r
4019         this.strip = Ext.fly(this.groupEl).createChild({\r
4020             tag: 'ul',\r
4021             cls: 'x-grouptabs-sub'\r
4022         });\r
4023 \r
4024         this.tooltip = new Ext.ToolTip({\r
4025            target: this.groupEl,\r
4026            delegate: 'a.x-grouptabs-text',\r
4027            trackMouse: true,\r
4028            renderTo: document.body,\r
4029            listeners: {\r
4030                beforeshow: function(tip) {\r
4031                    var item = (tip.triggerElement.parentNode === this.mainItem.tabEl)\r
4032                        ? this.mainItem\r
4033                        : this.findById(tip.triggerElement.parentNode.id.split(this.idDelimiter)[1]);\r
4034 \r
4035                    if(!item.tabTip) {\r
4036                        return false;\r
4037                    }\r
4038                    tip.body.dom.innerHTML = item.tabTip;\r
4039                },\r
4040                scope: this\r
4041            }\r
4042         });\r
4043                 \r
4044         if (!this.itemTpl) {\r
4045             var tt = new Ext.Template('<li class="{cls}" id="{id}">', '<a onclick="return false;" class="x-grouptabs-text {iconCls}">{text}</a>', '</li>');\r
4046             tt.disableFormats = true;\r
4047             tt.compile();\r
4048             Ext.ux.GroupTab.prototype.itemTpl = tt;\r
4049         }\r
4050         \r
4051         this.items.each(this.initTab, this);\r
4052     },\r
4053     \r
4054     afterRender: function(){\r
4055         Ext.ux.GroupTab.superclass.afterRender.call(this);\r
4056         \r
4057         if (this.activeTab !== undefined) {\r
4058             var item = (typeof this.activeTab == 'object') ? this.activeTab : this.items.get(this.activeTab);\r
4059             delete this.activeTab;\r
4060             this.setActiveTab(item);\r
4061         }\r
4062     },\r
4063     \r
4064     // private\r
4065     initTab: function(item, index){\r
4066         var before = this.strip.dom.childNodes[index];\r
4067         var p = Ext.TabPanel.prototype.getTemplateArgs.call(this, item);\r
4068         \r
4069         if (item === this.mainItem) {\r
4070             item.tabEl = this.groupEl;\r
4071             p.cls += ' x-grouptabs-main-item';\r
4072         }\r
4073         \r
4074         var el = before ? this.itemTpl.insertBefore(before, p) : this.itemTpl.append(this.strip, p);\r
4075         \r
4076         item.tabEl = item.tabEl || el;\r
4077                 \r
4078         item.on('disable', this.onItemDisabled, this);\r
4079         item.on('enable', this.onItemEnabled, this);\r
4080         item.on('titlechange', this.onItemTitleChanged, this);\r
4081         item.on('iconchange', this.onItemIconChanged, this);\r
4082         item.on('beforeshow', this.onBeforeShowItem, this);\r
4083     },\r
4084     \r
4085     setMainItem: function(item){\r
4086         item = this.getComponent(item);\r
4087         if (!item || this.fireEvent('changemainitem', this, item, this.mainItem) === false) {\r
4088             return;\r
4089         }\r
4090         \r
4091         this.mainItem = item;\r
4092     },\r
4093     \r
4094     getMainItem: function(){\r
4095         return this.mainItem || null;\r
4096     },\r
4097     \r
4098     // private\r
4099     onBeforeShowItem: function(item){\r
4100         if (item != this.activeTab) {\r
4101             this.setActiveTab(item);\r
4102             return false;\r
4103         }\r
4104     },\r
4105     \r
4106     // private\r
4107     onAdd: function(gt, item, index){\r
4108         if (this.rendered) {\r
4109             this.initTab.call(this, item, index);\r
4110         }\r
4111     },\r
4112     \r
4113     // private\r
4114     onRemove: function(tp, item){\r
4115         Ext.destroy(Ext.get(this.getTabEl(item)));\r
4116         this.stack.remove(item);\r
4117         item.un('disable', this.onItemDisabled, this);\r
4118         item.un('enable', this.onItemEnabled, this);\r
4119         item.un('titlechange', this.onItemTitleChanged, this);\r
4120         item.un('iconchange', this.onItemIconChanged, this);\r
4121         item.un('beforeshow', this.onBeforeShowItem, this);\r
4122         if (item == this.activeTab) {\r
4123             var next = this.stack.next();\r
4124             if (next) {\r
4125                 this.setActiveTab(next);\r
4126             }\r
4127             else if (this.items.getCount() > 0) {\r
4128                 this.setActiveTab(0);\r
4129             }\r
4130             else {\r
4131                 this.activeTab = null;\r
4132             }\r
4133         }\r
4134     },\r
4135     \r
4136     // private\r
4137     onBeforeAdd: function(item){\r
4138         var existing = item.events ? (this.items.containsKey(item.getItemId()) ? item : null) : this.items.get(item);\r
4139         if (existing) {\r
4140             this.setActiveTab(item);\r
4141             return false;\r
4142         }\r
4143         Ext.TabPanel.superclass.onBeforeAdd.apply(this, arguments);\r
4144         var es = item.elements;\r
4145         item.elements = es ? es.replace(',header', '') : es;\r
4146         item.border = (item.border === true);\r
4147     },\r
4148     \r
4149     // private\r
4150     onItemDisabled: Ext.TabPanel.prototype.onItemDisabled,\r
4151     onItemEnabled: Ext.TabPanel.prototype.onItemEnabled,\r
4152     \r
4153     // private\r
4154     onItemTitleChanged: function(item){\r
4155         var el = this.getTabEl(item);\r
4156         if (el) {\r
4157             Ext.fly(el).child('a.x-grouptabs-text', true).innerHTML = item.title;\r
4158         }\r
4159     },\r
4160     \r
4161     //private\r
4162     onItemIconChanged: function(item, iconCls, oldCls){\r
4163         var el = this.getTabEl(item);\r
4164         if (el) {\r
4165             Ext.fly(el).child('a.x-grouptabs-text').replaceClass(oldCls, iconCls);\r
4166         }\r
4167     },\r
4168     \r
4169     beforeDestroy: function(){\r
4170         Ext.TabPanel.prototype.beforeDestroy.call(this);\r
4171         this.tooltip.destroy();\r
4172     }\r
4173 });\r
4174 \r
4175 Ext.reg('grouptab', Ext.ux.GroupTab);\r
4176 Ext.ns('Ext.ux');\r
4177 \r
4178 Ext.ux.GroupTabPanel = Ext.extend(Ext.TabPanel, {\r
4179     tabPosition: 'left',\r
4180     \r
4181     alternateColor: false,\r
4182     \r
4183     alternateCls: 'x-grouptabs-panel-alt',\r
4184     \r
4185     defaultType: 'grouptab',\r
4186     \r
4187     deferredRender: false,\r
4188     \r
4189     activeGroup : null,\r
4190     \r
4191     initComponent: function(){\r
4192         Ext.ux.GroupTabPanel.superclass.initComponent.call(this);\r
4193         \r
4194         this.addEvents(\r
4195             'beforegroupchange',\r
4196             'groupchange'\r
4197         );\r
4198         this.elements = 'body,header';\r
4199         this.stripTarget = 'header';\r
4200         \r
4201         this.tabPosition = this.tabPosition == 'right' ? 'right' : 'left';\r
4202         \r
4203         this.addClass('x-grouptabs-panel');\r
4204         \r
4205         if (this.tabStyle && this.tabStyle != '') {\r
4206             this.addClass('x-grouptabs-panel-' + this.tabStyle);\r
4207         }\r
4208         \r
4209         if (this.alternateColor) {\r
4210             this.addClass(this.alternateCls);\r
4211         }\r
4212         \r
4213         this.on('beforeadd', function(gtp, item, index){\r
4214             this.initGroup(item, index);\r
4215         });                  \r
4216     },\r
4217     \r
4218     initEvents : function() {\r
4219         this.mon(this.strip, 'mousedown', this.onStripMouseDown, this);\r
4220     },\r
4221         \r
4222     onRender: function(ct, position){\r
4223         Ext.TabPanel.superclass.onRender.call(this, ct, position);\r
4224         if(this.plain){\r
4225             var pos = this.tabPosition == 'top' ? 'header' : 'footer';\r
4226             this[pos].addClass('x-tab-panel-'+pos+'-plain');\r
4227         }\r
4228 \r
4229         var st = this[this.stripTarget];\r
4230 \r
4231         this.stripWrap = st.createChild({cls:'x-tab-strip-wrap ', cn:{\r
4232             tag:'ul', cls:'x-grouptabs-strip x-grouptabs-tab-strip-'+this.tabPosition}});\r
4233 \r
4234         var beforeEl = (this.tabPosition=='bottom' ? this.stripWrap : null);\r
4235         this.strip = new Ext.Element(this.stripWrap.dom.firstChild);\r
4236 \r
4237                 this.header.addClass('x-grouptabs-panel-header');\r
4238                 this.bwrap.addClass('x-grouptabs-bwrap');\r
4239         this.body.addClass('x-tab-panel-body-'+this.tabPosition + ' x-grouptabs-panel-body');\r
4240 \r
4241         if (!this.groupTpl) {\r
4242             var tt = new Ext.Template(\r
4243                 '<li class="{cls}" id="{id}">', \r
4244                 '<a class="x-grouptabs-expand" onclick="return false;"></a>', \r
4245                 '<a class="x-grouptabs-text {iconCls}" href="#" onclick="return false;">',\r
4246                 '<span>{text}</span></a>', \r
4247                 '</li>'\r
4248             );\r
4249             tt.disableFormats = true;\r
4250             tt.compile();\r
4251             Ext.ux.GroupTabPanel.prototype.groupTpl = tt;\r
4252         }\r
4253         this.items.each(this.initGroup, this);\r
4254     },\r
4255     \r
4256     afterRender: function(){\r
4257         Ext.ux.GroupTabPanel.superclass.afterRender.call(this);\r
4258         \r
4259         this.tabJoint = Ext.fly(this.body.dom.parentNode).createChild({\r
4260             cls: 'x-tab-joint'\r
4261         });\r
4262         \r
4263         this.addClass('x-tab-panel-' + this.tabPosition);\r
4264         this.header.setWidth(this.tabWidth);\r
4265         \r
4266         if (this.activeGroup !== undefined) {\r
4267             var group = (typeof this.activeGroup == 'object') ? this.activeGroup : this.items.get(this.activeGroup);\r
4268             delete this.activeGroup;\r
4269             this.setActiveGroup(group);\r
4270             group.setActiveTab(group.getMainItem());\r
4271         }\r
4272     },\r
4273 \r
4274     getGroupEl : Ext.TabPanel.prototype.getTabEl,\r
4275         \r
4276     // private\r
4277     findTargets: function(e){\r
4278         var item = null,\r
4279             itemEl = e.getTarget('li', this.strip);\r
4280         if (itemEl) {\r
4281             item = this.findById(itemEl.id.split(this.idDelimiter)[1]);\r
4282             if (item.disabled) {\r
4283                 return {\r
4284                     expand: null,\r
4285                     item: null,\r
4286                     el: null\r
4287                 };\r
4288             }\r
4289         }\r
4290         return {\r
4291             expand: e.getTarget('.x-grouptabs-expand', this.strip),\r
4292             isGroup: !e.getTarget('ul.x-grouptabs-sub', this.strip),\r
4293             item: item,\r
4294             el: itemEl\r
4295         };\r
4296     },\r
4297     \r
4298     // private\r
4299     onStripMouseDown: function(e){\r
4300         if (e.button != 0) {\r
4301             return;\r
4302         }\r
4303         e.preventDefault();\r
4304         var t = this.findTargets(e);\r
4305         if (t.expand) {\r
4306             this.toggleGroup(t.el);\r
4307         }\r
4308         else if (t.item) {\r
4309             if(t.isGroup) {\r
4310                 t.item.setActiveTab(t.item.getMainItem());\r
4311             }\r
4312             else {\r
4313                 t.item.ownerCt.setActiveTab(t.item);\r
4314             }\r
4315         }\r
4316     },\r
4317     \r
4318     expandGroup: function(groupEl){\r
4319         if(groupEl.isXType) {\r
4320             groupEl = this.getGroupEl(groupEl);\r
4321         }\r
4322         Ext.fly(groupEl).addClass('x-grouptabs-expanded');\r
4323                 this.syncTabJoint();\r
4324     },\r
4325     \r
4326     toggleGroup: function(groupEl){\r
4327         if(groupEl.isXType) {\r
4328             groupEl = this.getGroupEl(groupEl);\r
4329         }        \r
4330         Ext.fly(groupEl).toggleClass('x-grouptabs-expanded');\r
4331                 this.syncTabJoint();\r
4332     },    \r
4333 \r
4334     collapseGroup: function(groupEl){\r
4335         if(groupEl.isXType) {\r
4336             groupEl = this.getGroupEl(groupEl);\r
4337         }\r
4338         Ext.fly(groupEl).removeClass('x-grouptabs-expanded');\r
4339                 this.syncTabJoint();\r
4340     },\r
4341         \r
4342     syncTabJoint: function(groupEl){\r
4343         if (!this.tabJoint) {\r
4344             return;\r
4345         }\r
4346         \r
4347         groupEl = groupEl || this.getGroupEl(this.activeGroup);\r
4348         if(groupEl) {\r
4349             this.tabJoint.setHeight(Ext.fly(groupEl).getHeight() - 2); \r
4350                         \r
4351             var y = Ext.isGecko2 ? 0 : 1;\r
4352             if (this.tabPosition == 'left'){\r
4353                 this.tabJoint.alignTo(groupEl, 'tl-tr', [-2,y]);\r
4354             }\r
4355             else {\r
4356                 this.tabJoint.alignTo(groupEl, 'tr-tl', [1,y]);\r
4357             }           \r
4358         }\r
4359         else {\r
4360             this.tabJoint.hide();\r
4361         }\r
4362     },\r
4363     \r
4364     getActiveTab : function() {\r
4365         if(!this.activeGroup) return null;\r
4366         return this.activeGroup.getTabEl(this.activeGroup.activeTab) || null;  \r
4367     },\r
4368     \r
4369     onResize: function(){\r
4370         Ext.ux.GroupTabPanel.superclass.onResize.apply(this, arguments);\r
4371         this.syncTabJoint();\r
4372     },\r
4373     \r
4374     createCorner: function(el, pos){\r
4375         return Ext.fly(el).createChild({\r
4376             cls: 'x-grouptabs-corner x-grouptabs-corner-' + pos\r
4377         });\r
4378     },\r
4379     \r
4380     initGroup: function(group, index){\r
4381         var before = this.strip.dom.childNodes[index],   \r
4382             p = this.getTemplateArgs(group);\r
4383         if (index === 0) {\r
4384             p.cls += ' x-tab-first';\r
4385         }\r
4386         p.cls += ' x-grouptabs-main';\r
4387         p.text = group.getMainItem().title;\r
4388         \r
4389         var el = before ? this.groupTpl.insertBefore(before, p) : this.groupTpl.append(this.strip, p),\r
4390             tl = this.createCorner(el, 'top-' + this.tabPosition),\r
4391             bl = this.createCorner(el, 'bottom-' + this.tabPosition);\r
4392 \r
4393         group.tabEl = el;\r
4394         if (group.expanded) {\r
4395             this.expandGroup(el);\r
4396         }\r
4397 \r
4398         if (Ext.isIE6 || (Ext.isIE && !Ext.isStrict)){\r
4399             bl.setLeft('-10px');\r
4400             bl.setBottom('-5px');\r
4401             tl.setLeft('-10px');\r
4402             tl.setTop('-5px');\r
4403         }\r
4404 \r
4405         this.mon(group, {\r
4406             scope: this,\r
4407             changemainitem: this.onGroupChangeMainItem,\r
4408             beforetabchange: this.onGroupBeforeTabChange\r
4409         });\r
4410     },\r
4411     \r
4412     setActiveGroup : function(group) {\r
4413         group = this.getComponent(group);\r
4414         if(!group){\r
4415             return false;\r
4416         }\r
4417         if(!this.rendered){\r
4418             this.activeGroup = group;\r
4419             return true;\r
4420         }\r
4421         if(this.activeGroup != group && this.fireEvent('beforegroupchange', this, group, this.activeGroup) !== false){\r
4422             if(this.activeGroup){\r
4423                 this.activeGroup.activeTab = null;\r
4424                 var oldEl = this.getGroupEl(this.activeGroup);\r
4425                 if(oldEl){\r
4426                     Ext.fly(oldEl).removeClass('x-grouptabs-strip-active');\r
4427                 }\r
4428             }\r
4429 \r
4430             var groupEl = this.getGroupEl(group);\r
4431             Ext.fly(groupEl).addClass('x-grouptabs-strip-active');\r
4432                         \r
4433             this.activeGroup = group;\r
4434             this.stack.add(group);\r
4435 \r
4436             this.layout.setActiveItem(group);\r
4437             this.syncTabJoint(groupEl);\r
4438 \r
4439             this.fireEvent('groupchange', this, group);\r
4440             return true;\r
4441         }\r
4442         return false; \r
4443     },\r
4444     \r
4445     onGroupBeforeTabChange: function(group, newTab, oldTab){\r
4446         if(group !== this.activeGroup || newTab !== oldTab) {\r
4447             this.strip.select('.x-grouptabs-sub > li.x-grouptabs-strip-active', true).removeClass('x-grouptabs-strip-active');\r
4448         } \r
4449         this.expandGroup(this.getGroupEl(group));\r
4450         if(group !== this.activeGroup) {\r
4451             return this.setActiveGroup(group);\r
4452         }        \r
4453     },\r
4454     \r
4455     getFrameHeight: function(){\r
4456         var h = this.el.getFrameWidth('tb');\r
4457         h += (this.tbar ? this.tbar.getHeight() : 0) +\r
4458         (this.bbar ? this.bbar.getHeight() : 0);\r
4459         \r
4460         return h;\r
4461     },\r
4462     \r
4463     adjustBodyWidth: function(w){\r
4464         return w - this.tabWidth;\r
4465     }\r
4466 });\r
4467 \r
4468 Ext.reg('grouptabpanel', Ext.ux.GroupTabPanel);/*\r
4469  * Note that this control will most likely remain as an example, and not as a core Ext form\r
4470  * control.  However, the API will be changing in a future release and so should not yet be\r
4471  * treated as a final, stable API at this time.\r
4472  */\r
4473 \r
4474 /**\r
4475  * @class Ext.ux.form.ItemSelector\r
4476  * @extends Ext.form.Field\r
4477  * A control that allows selection of between two Ext.ux.form.MultiSelect controls.\r
4478  *\r
4479  *  @history\r
4480  *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)\r
4481  *\r
4482  * @constructor\r
4483  * Create a new ItemSelector\r
4484  * @param {Object} config Configuration options\r
4485  * @xtype itemselector \r
4486  */\r
4487 Ext.ux.form.ItemSelector = Ext.extend(Ext.form.Field,  {\r
4488     hideNavIcons:false,\r
4489     imagePath:"",\r
4490     iconUp:"up2.gif",\r
4491     iconDown:"down2.gif",\r
4492     iconLeft:"left2.gif",\r
4493     iconRight:"right2.gif",\r
4494     iconTop:"top2.gif",\r
4495     iconBottom:"bottom2.gif",\r
4496     drawUpIcon:true,\r
4497     drawDownIcon:true,\r
4498     drawLeftIcon:true,\r
4499     drawRightIcon:true,\r
4500     drawTopIcon:true,\r
4501     drawBotIcon:true,\r
4502     delimiter:',',\r
4503     bodyStyle:null,\r
4504     border:false,\r
4505     defaultAutoCreate:{tag: "div"},\r
4506     /**\r
4507      * @cfg {Array} multiselects An array of {@link Ext.ux.form.MultiSelect} config objects, with at least all required parameters (e.g., store)\r
4508      */\r
4509     multiselects:null,\r
4510 \r
4511     initComponent: function(){\r
4512         Ext.ux.form.ItemSelector.superclass.initComponent.call(this);\r
4513         this.addEvents({\r
4514             'rowdblclick' : true,\r
4515             'change' : true\r
4516         });\r
4517     },\r
4518 \r
4519     onRender: function(ct, position){\r
4520         Ext.ux.form.ItemSelector.superclass.onRender.call(this, ct, position);\r
4521 \r
4522         // Internal default configuration for both multiselects\r
4523         var msConfig = [{\r
4524             legend: 'Available',\r
4525             draggable: true,\r
4526             droppable: true,\r
4527             width: 100,\r
4528             height: 100\r
4529         },{\r
4530             legend: 'Selected',\r
4531             droppable: true,\r
4532             draggable: true,\r
4533             width: 100,\r
4534             height: 100\r
4535         }];\r
4536 \r
4537         this.fromMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[0], msConfig[0]));\r
4538         this.fromMultiselect.on('dblclick', this.onRowDblClick, this);\r
4539 \r
4540         this.toMultiselect = new Ext.ux.form.MultiSelect(Ext.applyIf(this.multiselects[1], msConfig[1]));\r
4541         this.toMultiselect.on('dblclick', this.onRowDblClick, this);\r
4542 \r
4543         var p = new Ext.Panel({\r
4544             bodyStyle:this.bodyStyle,\r
4545             border:this.border,\r
4546             layout:"table",\r
4547             layoutConfig:{columns:3}\r
4548         });\r
4549 \r
4550         p.add(this.fromMultiselect);\r
4551         var icons = new Ext.Panel({header:false});\r
4552         p.add(icons);\r
4553         p.add(this.toMultiselect);\r
4554         p.render(this.el);\r
4555         icons.el.down('.'+icons.bwrapCls).remove();\r
4556 \r
4557         // ICON HELL!!!\r
4558         if (this.imagePath!="" && this.imagePath.charAt(this.imagePath.length-1)!="/")\r
4559             this.imagePath+="/";\r
4560         this.iconUp = this.imagePath + (this.iconUp || 'up2.gif');\r
4561         this.iconDown = this.imagePath + (this.iconDown || 'down2.gif');\r
4562         this.iconLeft = this.imagePath + (this.iconLeft || 'left2.gif');\r
4563         this.iconRight = this.imagePath + (this.iconRight || 'right2.gif');\r
4564         this.iconTop = this.imagePath + (this.iconTop || 'top2.gif');\r
4565         this.iconBottom = this.imagePath + (this.iconBottom || 'bottom2.gif');\r
4566         var el=icons.getEl();\r
4567         this.toTopIcon = el.createChild({tag:'img', src:this.iconTop, style:{cursor:'pointer', margin:'2px'}});\r
4568         el.createChild({tag: 'br'});\r
4569         this.upIcon = el.createChild({tag:'img', src:this.iconUp, style:{cursor:'pointer', margin:'2px'}});\r
4570         el.createChild({tag: 'br'});\r
4571         this.addIcon = el.createChild({tag:'img', src:this.iconRight, style:{cursor:'pointer', margin:'2px'}});\r
4572         el.createChild({tag: 'br'});\r
4573         this.removeIcon = el.createChild({tag:'img', src:this.iconLeft, style:{cursor:'pointer', margin:'2px'}});\r
4574         el.createChild({tag: 'br'});\r
4575         this.downIcon = el.createChild({tag:'img', src:this.iconDown, style:{cursor:'pointer', margin:'2px'}});\r
4576         el.createChild({tag: 'br'});\r
4577         this.toBottomIcon = el.createChild({tag:'img', src:this.iconBottom, style:{cursor:'pointer', margin:'2px'}});\r
4578         this.toTopIcon.on('click', this.toTop, this);\r
4579         this.upIcon.on('click', this.up, this);\r
4580         this.downIcon.on('click', this.down, this);\r
4581         this.toBottomIcon.on('click', this.toBottom, this);\r
4582         this.addIcon.on('click', this.fromTo, this);\r
4583         this.removeIcon.on('click', this.toFrom, this);\r
4584         if (!this.drawUpIcon || this.hideNavIcons) { this.upIcon.dom.style.display='none'; }\r
4585         if (!this.drawDownIcon || this.hideNavIcons) { this.downIcon.dom.style.display='none'; }\r
4586         if (!this.drawLeftIcon || this.hideNavIcons) { this.addIcon.dom.style.display='none'; }\r
4587         if (!this.drawRightIcon || this.hideNavIcons) { this.removeIcon.dom.style.display='none'; }\r
4588         if (!this.drawTopIcon || this.hideNavIcons) { this.toTopIcon.dom.style.display='none'; }\r
4589         if (!this.drawBotIcon || this.hideNavIcons) { this.toBottomIcon.dom.style.display='none'; }\r
4590 \r
4591         var tb = p.body.first();\r
4592         this.el.setWidth(p.body.first().getWidth());\r
4593         p.body.removeClass();\r
4594 \r
4595         this.hiddenName = this.name;\r
4596         var hiddenTag = {tag: "input", type: "hidden", value: "", name: this.name};\r
4597         this.hiddenField = this.el.createChild(hiddenTag);\r
4598     },\r
4599     \r
4600     doLayout: function(){\r
4601         if(this.rendered){\r
4602             this.fromMultiselect.fs.doLayout();\r
4603             this.toMultiselect.fs.doLayout();\r
4604         }\r
4605     },\r
4606 \r
4607     afterRender: function(){\r
4608         Ext.ux.form.ItemSelector.superclass.afterRender.call(this);\r
4609 \r
4610         this.toStore = this.toMultiselect.store;\r
4611         this.toStore.on('add', this.valueChanged, this);\r
4612         this.toStore.on('remove', this.valueChanged, this);\r
4613         this.toStore.on('load', this.valueChanged, this);\r
4614         this.valueChanged(this.toStore);\r
4615     },\r
4616 \r
4617     toTop : function() {\r
4618         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4619         var records = [];\r
4620         if (selectionsArray.length > 0) {\r
4621             selectionsArray.sort();\r
4622             for (var i=0; i<selectionsArray.length; i++) {\r
4623                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4624                 records.push(record);\r
4625             }\r
4626             selectionsArray = [];\r
4627             for (var i=records.length-1; i>-1; i--) {\r
4628                 record = records[i];\r
4629                 this.toMultiselect.view.store.remove(record);\r
4630                 this.toMultiselect.view.store.insert(0, record);\r
4631                 selectionsArray.push(((records.length - 1) - i));\r
4632             }\r
4633         }\r
4634         this.toMultiselect.view.refresh();\r
4635         this.toMultiselect.view.select(selectionsArray);\r
4636     },\r
4637 \r
4638     toBottom : function() {\r
4639         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4640         var records = [];\r
4641         if (selectionsArray.length > 0) {\r
4642             selectionsArray.sort();\r
4643             for (var i=0; i<selectionsArray.length; i++) {\r
4644                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4645                 records.push(record);\r
4646             }\r
4647             selectionsArray = [];\r
4648             for (var i=0; i<records.length; i++) {\r
4649                 record = records[i];\r
4650                 this.toMultiselect.view.store.remove(record);\r
4651                 this.toMultiselect.view.store.add(record);\r
4652                 selectionsArray.push((this.toMultiselect.view.store.getCount()) - (records.length - i));\r
4653             }\r
4654         }\r
4655         this.toMultiselect.view.refresh();\r
4656         this.toMultiselect.view.select(selectionsArray);\r
4657     },\r
4658 \r
4659     up : function() {\r
4660         var record = null;\r
4661         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4662         selectionsArray.sort();\r
4663         var newSelectionsArray = [];\r
4664         if (selectionsArray.length > 0) {\r
4665             for (var i=0; i<selectionsArray.length; i++) {\r
4666                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4667                 if ((selectionsArray[i] - 1) >= 0) {\r
4668                     this.toMultiselect.view.store.remove(record);\r
4669                     this.toMultiselect.view.store.insert(selectionsArray[i] - 1, record);\r
4670                     newSelectionsArray.push(selectionsArray[i] - 1);\r
4671                 }\r
4672             }\r
4673             this.toMultiselect.view.refresh();\r
4674             this.toMultiselect.view.select(newSelectionsArray);\r
4675         }\r
4676     },\r
4677 \r
4678     down : function() {\r
4679         var record = null;\r
4680         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4681         selectionsArray.sort();\r
4682         selectionsArray.reverse();\r
4683         var newSelectionsArray = [];\r
4684         if (selectionsArray.length > 0) {\r
4685             for (var i=0; i<selectionsArray.length; i++) {\r
4686                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4687                 if ((selectionsArray[i] + 1) < this.toMultiselect.view.store.getCount()) {\r
4688                     this.toMultiselect.view.store.remove(record);\r
4689                     this.toMultiselect.view.store.insert(selectionsArray[i] + 1, record);\r
4690                     newSelectionsArray.push(selectionsArray[i] + 1);\r
4691                 }\r
4692             }\r
4693             this.toMultiselect.view.refresh();\r
4694             this.toMultiselect.view.select(newSelectionsArray);\r
4695         }\r
4696     },\r
4697 \r
4698     fromTo : function() {\r
4699         var selectionsArray = this.fromMultiselect.view.getSelectedIndexes();\r
4700         var records = [];\r
4701         if (selectionsArray.length > 0) {\r
4702             for (var i=0; i<selectionsArray.length; i++) {\r
4703                 record = this.fromMultiselect.view.store.getAt(selectionsArray[i]);\r
4704                 records.push(record);\r
4705             }\r
4706             if(!this.allowDup)selectionsArray = [];\r
4707             for (var i=0; i<records.length; i++) {\r
4708                 record = records[i];\r
4709                 if(this.allowDup){\r
4710                     var x=new Ext.data.Record();\r
4711                     record.id=x.id;\r
4712                     delete x;\r
4713                     this.toMultiselect.view.store.add(record);\r
4714                 }else{\r
4715                     this.fromMultiselect.view.store.remove(record);\r
4716                     this.toMultiselect.view.store.add(record);\r
4717                     selectionsArray.push((this.toMultiselect.view.store.getCount() - 1));\r
4718                 }\r
4719             }\r
4720         }\r
4721         this.toMultiselect.view.refresh();\r
4722         this.fromMultiselect.view.refresh();\r
4723         var si = this.toMultiselect.store.sortInfo;\r
4724         if(si){\r
4725             this.toMultiselect.store.sort(si.field, si.direction);\r
4726         }\r
4727         this.toMultiselect.view.select(selectionsArray);\r
4728     },\r
4729 \r
4730     toFrom : function() {\r
4731         var selectionsArray = this.toMultiselect.view.getSelectedIndexes();\r
4732         var records = [];\r
4733         if (selectionsArray.length > 0) {\r
4734             for (var i=0; i<selectionsArray.length; i++) {\r
4735                 record = this.toMultiselect.view.store.getAt(selectionsArray[i]);\r
4736                 records.push(record);\r
4737             }\r
4738             selectionsArray = [];\r
4739             for (var i=0; i<records.length; i++) {\r
4740                 record = records[i];\r
4741                 this.toMultiselect.view.store.remove(record);\r
4742                 if(!this.allowDup){\r
4743                     this.fromMultiselect.view.store.add(record);\r
4744                     selectionsArray.push((this.fromMultiselect.view.store.getCount() - 1));\r
4745                 }\r
4746             }\r
4747         }\r
4748         this.fromMultiselect.view.refresh();\r
4749         this.toMultiselect.view.refresh();\r
4750         var si = this.fromMultiselect.store.sortInfo;\r
4751         if (si){\r
4752             this.fromMultiselect.store.sort(si.field, si.direction);\r
4753         }\r
4754         this.fromMultiselect.view.select(selectionsArray);\r
4755     },\r
4756 \r
4757     valueChanged: function(store) {\r
4758         var record = null;\r
4759         var values = [];\r
4760         for (var i=0; i<store.getCount(); i++) {\r
4761             record = store.getAt(i);\r
4762             values.push(record.get(this.toMultiselect.valueField));\r
4763         }\r
4764         this.hiddenField.dom.value = values.join(this.delimiter);\r
4765         this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);\r
4766     },\r
4767 \r
4768     getValue : function() {\r
4769         return this.hiddenField.dom.value;\r
4770     },\r
4771 \r
4772     onRowDblClick : function(vw, index, node, e) {\r
4773         if (vw == this.toMultiselect.view){\r
4774             this.toFrom();\r
4775         } else if (vw == this.fromMultiselect.view) {\r
4776             this.fromTo();\r
4777         }\r
4778         return this.fireEvent('rowdblclick', vw, index, node, e);\r
4779     },\r
4780 \r
4781     reset: function(){\r
4782         range = this.toMultiselect.store.getRange();\r
4783         this.toMultiselect.store.removeAll();\r
4784         this.fromMultiselect.store.add(range);\r
4785         var si = this.fromMultiselect.store.sortInfo;\r
4786         if (si){\r
4787             this.fromMultiselect.store.sort(si.field, si.direction);\r
4788         }\r
4789         this.valueChanged(this.toMultiselect.store);\r
4790     }\r
4791 });\r
4792 \r
4793 Ext.reg('itemselector', Ext.ux.form.ItemSelector);\r
4794 \r
4795 //backwards compat\r
4796 Ext.ux.ItemSelector = Ext.ux.form.ItemSelector;\r
4797 Ext.ns('Ext.ux.grid');\r
4798 \r
4799 Ext.ux.grid.LockingGridView = Ext.extend(Ext.grid.GridView, {\r
4800     lockText : 'Lock',\r
4801     unlockText : 'Unlock',\r
4802     rowBorderWidth : 1,\r
4803     lockedBorderWidth : 1,\r
4804     /*\r
4805      * This option ensures that height between the rows is synchronized\r
4806      * between the locked and unlocked sides. This option only needs to be used\r
4807      * when the row heights isn't predictable.\r
4808      */\r
4809     syncHeights: false,\r
4810     initTemplates : function(){\r
4811         var ts = this.templates || {};\r
4812         if(!ts.master){\r
4813             ts.master = new Ext.Template(\r
4814                 '<div class="x-grid3" hidefocus="true">',\r
4815                     '<div class="x-grid3-locked">',\r
4816                         '<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>',\r
4817                         '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{lstyle}">{lockedBody}</div><div class="x-grid3-scroll-spacer"></div></div>',\r
4818                     '</div>',\r
4819                     '<div class="x-grid3-viewport x-grid3-unlocked">',\r
4820                         '<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>',\r
4821                         '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{bstyle}">{body}</div><a href="#" class="x-grid3-focus" tabIndex="-1"></a></div>',\r
4822                     '</div>',\r
4823                     '<div class="x-grid3-resize-marker">&#160;</div>',\r
4824                     '<div class="x-grid3-resize-proxy">&#160;</div>',\r
4825                 '</div>'\r
4826             );\r
4827         }\r
4828         this.templates = ts;\r
4829         Ext.ux.grid.LockingGridView.superclass.initTemplates.call(this);\r
4830     },\r
4831     getEditorParent : function(ed){\r
4832         return this.el.dom;\r
4833     },\r
4834     initElements : function(){\r
4835         var E = Ext.Element;\r
4836         var el = this.grid.getGridEl().dom.firstChild;\r
4837         var cs = el.childNodes;\r
4838         this.el = new E(el);\r
4839         this.lockedWrap = new E(cs[0]);\r
4840         this.lockedHd = new E(this.lockedWrap.dom.firstChild);\r
4841         this.lockedInnerHd = this.lockedHd.dom.firstChild;\r
4842         this.lockedScroller = new E(this.lockedWrap.dom.childNodes[1]);\r
4843         this.lockedBody = new E(this.lockedScroller.dom.firstChild);\r
4844         this.mainWrap = new E(cs[1]);\r
4845         this.mainHd = new E(this.mainWrap.dom.firstChild);\r
4846         if(this.grid.hideHeaders){\r
4847             this.lockedHd.setDisplayed(false);\r
4848             this.mainHd.setDisplayed(false);\r
4849         }\r
4850         this.innerHd = this.mainHd.dom.firstChild;\r
4851         this.scroller = new E(this.mainWrap.dom.childNodes[1]);\r
4852         if(this.forceFit){\r
4853             this.scroller.setStyle('overflow-x', 'hidden');\r
4854         }\r
4855         this.mainBody = new E(this.scroller.dom.firstChild);\r
4856         this.focusEl = new E(this.scroller.dom.childNodes[1]);\r
4857         this.focusEl.swallowEvent('click', true);\r
4858         this.resizeMarker = new E(cs[2]);\r
4859         this.resizeProxy = new E(cs[3]);\r
4860     },\r
4861     \r
4862     getLockedRows : function(){\r
4863         return this.hasRows() ? this.lockedBody.dom.childNodes : [];\r
4864     },\r
4865     \r
4866     getLockedRow : function(row){\r
4867         return this.getLockedRows()[row];\r
4868     },\r
4869     \r
4870     getCell : function(row, col){\r
4871         var llen = this.cm.getLockedCount();\r
4872         if(col < llen){\r
4873             return this.getLockedRow(row).getElementsByTagName('td')[col];\r
4874         }\r
4875         return Ext.ux.grid.LockingGridView.superclass.getCell.call(this, row, col - llen);\r
4876     },\r
4877     \r
4878     getHeaderCell : function(index){\r
4879         var llen = this.cm.getLockedCount();\r
4880         if(index < llen){\r
4881             return this.lockedHd.dom.getElementsByTagName('td')[index];\r
4882         }\r
4883         return Ext.ux.grid.LockingGridView.superclass.getHeaderCell.call(this, index - llen);\r
4884     },\r
4885     \r
4886     addRowClass : function(row, cls){\r
4887         var r = this.getLockedRow(row);\r
4888         if(r){\r
4889             this.fly(r).addClass(cls);\r
4890         }\r
4891         Ext.ux.grid.LockingGridView.superclass.addRowClass.call(this, row, cls);\r
4892     },\r
4893     \r
4894     removeRowClass : function(row, cls){\r
4895         var r = this.getLockedRow(row);\r
4896         if(r){\r
4897             this.fly(r).removeClass(cls);\r
4898         }\r
4899         Ext.ux.grid.LockingGridView.superclass.removeRowClass.call(this, row, cls);\r
4900     },\r
4901     \r
4902     removeRow : function(row) {\r
4903         Ext.removeNode(this.getLockedRow(row));\r
4904         Ext.ux.grid.LockingGridView.superclass.removeRow.call(this, row);\r
4905     },\r
4906     \r
4907     removeRows : function(firstRow, lastRow){\r
4908         var bd = this.lockedBody.dom;\r
4909         for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){\r
4910             Ext.removeNode(bd.childNodes[firstRow]);\r
4911         }\r
4912         Ext.ux.grid.LockingGridView.superclass.removeRows.call(this, firstRow, lastRow);\r
4913     },\r
4914     \r
4915     syncScroll : function(e){\r
4916         var mb = this.scroller.dom;\r
4917         this.lockedScroller.dom.scrollTop = mb.scrollTop;\r
4918         Ext.ux.grid.LockingGridView.superclass.syncScroll.call(this, e);\r
4919     },\r
4920     \r
4921     updateSortIcon : function(col, dir){\r
4922         var sc = this.sortClasses,\r
4923             lhds = this.lockedHd.select('td').removeClass(sc),\r
4924             hds = this.mainHd.select('td').removeClass(sc),\r
4925             llen = this.cm.getLockedCount(),\r
4926             cls = sc[dir == 'DESC' ? 1 : 0];\r
4927         if(col < llen){\r
4928             lhds.item(col).addClass(cls);\r
4929         }else{\r
4930             hds.item(col - llen).addClass(cls);\r
4931         }\r
4932     },\r
4933     \r
4934     updateAllColumnWidths : function(){\r
4935         var tw = this.getTotalWidth(),\r
4936             clen = this.cm.getColumnCount(),\r
4937             lw = this.getLockedWidth(),\r
4938             llen = this.cm.getLockedCount(),\r
4939             ws = [], len, i;\r
4940         this.updateLockedWidth();\r
4941         for(i = 0; i < clen; i++){\r
4942             ws[i] = this.getColumnWidth(i);\r
4943             var hd = this.getHeaderCell(i);\r
4944             hd.style.width = ws[i];\r
4945         }\r
4946         var lns = this.getLockedRows(), ns = this.getRows(), row, trow, j;\r
4947         for(i = 0, len = ns.length; i < len; i++){\r
4948             row = lns[i];\r
4949             row.style.width = lw;\r
4950             if(row.firstChild){\r
4951                 row.firstChild.style.width = lw;\r
4952                 trow = row.firstChild.rows[0];\r
4953                 for (j = 0; j < llen; j++) {\r
4954                    trow.childNodes[j].style.width = ws[j];\r
4955                 }\r
4956             }\r
4957             row = ns[i];\r
4958             row.style.width = tw;\r
4959             if(row.firstChild){\r
4960                 row.firstChild.style.width = tw;\r
4961                 trow = row.firstChild.rows[0];\r
4962                 for (j = llen; j < clen; j++) {\r
4963                    trow.childNodes[j - llen].style.width = ws[j];\r
4964                 }\r
4965             }\r
4966         }\r
4967         this.onAllColumnWidthsUpdated(ws, tw);\r
4968         this.syncHeaderHeight();\r
4969     },\r
4970     \r
4971     updateColumnWidth : function(col, width){\r
4972         var w = this.getColumnWidth(col),\r
4973             llen = this.cm.getLockedCount(),\r
4974             ns, rw, c, row;\r
4975         this.updateLockedWidth();\r
4976         if(col < llen){\r
4977             ns = this.getLockedRows();\r
4978             rw = this.getLockedWidth();\r
4979             c = col;\r
4980         }else{\r
4981             ns = this.getRows();\r
4982             rw = this.getTotalWidth();\r
4983             c = col - llen;\r
4984         }\r
4985         var hd = this.getHeaderCell(col);\r
4986         hd.style.width = w;\r
4987         for(var i = 0, len = ns.length; i < len; i++){\r
4988             row = ns[i];\r
4989             row.style.width = rw;\r
4990             if(row.firstChild){\r
4991                 row.firstChild.style.width = rw;\r
4992                 row.firstChild.rows[0].childNodes[c].style.width = w;\r
4993             }\r
4994         }\r
4995         this.onColumnWidthUpdated(col, w, this.getTotalWidth());\r
4996         this.syncHeaderHeight();\r
4997     },\r
4998     \r
4999     updateColumnHidden : function(col, hidden){\r
5000         var llen = this.cm.getLockedCount(),\r
5001             ns, rw, c, row,\r
5002             display = hidden ? 'none' : '';\r
5003         this.updateLockedWidth();\r
5004         if(col < llen){\r
5005             ns = this.getLockedRows();\r
5006             rw = this.getLockedWidth();\r
5007             c = col;\r
5008         }else{\r
5009             ns = this.getRows();\r
5010             rw = this.getTotalWidth();\r
5011             c = col - llen;\r
5012         }\r
5013         var hd = this.getHeaderCell(col);\r
5014         hd.style.display = display;\r
5015         for(var i = 0, len = ns.length; i < len; i++){\r
5016             row = ns[i];\r
5017             row.style.width = rw;\r
5018             if(row.firstChild){\r
5019                 row.firstChild.style.width = rw;\r
5020                 row.firstChild.rows[0].childNodes[c].style.display = display;\r
5021             }\r
5022         }\r
5023         this.onColumnHiddenUpdated(col, hidden, this.getTotalWidth());\r
5024         delete this.lastViewWidth;\r
5025         this.layout();\r
5026     },\r
5027     \r
5028     doRender : function(cs, rs, ds, startRow, colCount, stripe){\r
5029         var ts = this.templates, ct = ts.cell, rt = ts.row, last = colCount-1,\r
5030             tstyle = 'width:'+this.getTotalWidth()+';',\r
5031             lstyle = 'width:'+this.getLockedWidth()+';',\r
5032             buf = [], lbuf = [], cb, lcb, c, p = {}, rp = {}, r;\r
5033         for(var j = 0, len = rs.length; j < len; j++){\r
5034             r = rs[j]; cb = []; lcb = [];\r
5035             var rowIndex = (j+startRow);\r
5036             for(var i = 0; i < colCount; i++){\r
5037                 c = cs[i];\r
5038                 p.id = c.id;\r
5039                 p.css = (i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '')) +\r
5040                     (this.cm.config[i].cellCls ? ' ' + this.cm.config[i].cellCls : '');\r
5041                 p.attr = p.cellAttr = '';\r
5042                 p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);\r
5043                 p.style = c.style;\r
5044                 if(Ext.isEmpty(p.value)){\r
5045                     p.value = '&#160;';\r
5046                 }\r
5047                 if(this.markDirty && r.dirty && Ext.isDefined(r.modified[c.name])){\r
5048                     p.css += ' x-grid3-dirty-cell';\r
5049                 }\r
5050                 if(c.locked){\r
5051                     lcb[lcb.length] = ct.apply(p);\r
5052                 }else{\r
5053                     cb[cb.length] = ct.apply(p);\r
5054                 }\r
5055             }\r
5056             var alt = [];\r
5057             if(stripe && ((rowIndex+1) % 2 === 0)){\r
5058                 alt[0] = 'x-grid3-row-alt';\r
5059             }\r
5060             if(r.dirty){\r
5061                 alt[1] = ' x-grid3-dirty-row';\r
5062             }\r
5063             rp.cols = colCount;\r
5064             if(this.getRowClass){\r
5065                 alt[2] = this.getRowClass(r, rowIndex, rp, ds);\r
5066             }\r
5067             rp.alt = alt.join(' ');\r
5068             rp.cells = cb.join('');\r
5069             rp.tstyle = tstyle;\r
5070             buf[buf.length] = rt.apply(rp);\r
5071             rp.cells = lcb.join('');\r
5072             rp.tstyle = lstyle;\r
5073             lbuf[lbuf.length] = rt.apply(rp);\r
5074         }\r
5075         return [buf.join(''), lbuf.join('')];\r
5076     },\r
5077     processRows : function(startRow, skipStripe){\r
5078         if(!this.ds || this.ds.getCount() < 1){\r
5079             return;\r
5080         }\r
5081         var rows = this.getRows(),\r
5082             lrows = this.getLockedRows(),\r
5083             row, lrow;\r
5084         skipStripe = skipStripe || !this.grid.stripeRows;\r
5085         startRow = startRow || 0;\r
5086         for(var i = 0, len = rows.length; i < len; ++i){\r
5087             row = rows[i];\r
5088             lrow = lrows[i];\r
5089             row.rowIndex = i;\r
5090             lrow.rowIndex = i;\r
5091             if(!skipStripe){\r
5092                 row.className = row.className.replace(this.rowClsRe, ' ');\r
5093                 lrow.className = lrow.className.replace(this.rowClsRe, ' ');\r
5094                 if ((i + 1) % 2 === 0){\r
5095                     row.className += ' x-grid3-row-alt';\r
5096                     lrow.className += ' x-grid3-row-alt';\r
5097                 }\r
5098             }\r
5099             if(this.syncHeights){\r
5100                 var el1 = Ext.get(row),\r
5101                     el2 = Ext.get(lrow),\r
5102                     h1 = el1.getHeight(),\r
5103                     h2 = el2.getHeight();\r
5104                 \r
5105                 if(h1 > h2){\r
5106                     el2.setHeight(h1);    \r
5107                 }else if(h2 > h1){\r
5108                     el1.setHeight(h2);\r
5109                 }\r
5110             }\r
5111         }\r
5112         if(startRow === 0){\r
5113             Ext.fly(rows[0]).addClass(this.firstRowCls);\r
5114             Ext.fly(lrows[0]).addClass(this.firstRowCls);\r
5115         }\r
5116         Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);\r
5117         Ext.fly(lrows[lrows.length - 1]).addClass(this.lastRowCls);\r
5118     },\r
5119     \r
5120     afterRender : function(){\r
5121         if(!this.ds || !this.cm){\r
5122             return;\r
5123         }\r
5124         var bd = this.renderRows() || ['&#160;', '&#160;'];\r
5125         this.mainBody.dom.innerHTML = bd[0];\r
5126         this.lockedBody.dom.innerHTML = bd[1];\r
5127         this.processRows(0, true);\r
5128         if(this.deferEmptyText !== true){\r
5129             this.applyEmptyText();\r
5130         }\r
5131     },\r
5132     \r
5133     renderUI : function(){\r
5134         var header = this.renderHeaders();\r
5135         var body = this.templates.body.apply({rows:'&#160;'});\r
5136         var html = this.templates.master.apply({\r
5137             body: body,\r
5138             header: header[0],\r
5139             ostyle: 'width:'+this.getOffsetWidth()+';',\r
5140             bstyle: 'width:'+this.getTotalWidth()+';',\r
5141             lockedBody: body,\r
5142             lockedHeader: header[1],\r
5143             lstyle: 'width:'+this.getLockedWidth()+';'\r
5144         });\r
5145         var g = this.grid;\r
5146         g.getGridEl().dom.innerHTML = html;\r
5147         this.initElements();\r
5148         Ext.fly(this.innerHd).on('click', this.handleHdDown, this);\r
5149         Ext.fly(this.lockedInnerHd).on('click', this.handleHdDown, this);\r
5150         this.mainHd.on({\r
5151             scope: this,\r
5152             mouseover: this.handleHdOver,\r
5153             mouseout: this.handleHdOut,\r
5154             mousemove: this.handleHdMove\r
5155         });\r
5156         this.lockedHd.on({\r
5157             scope: this,\r
5158             mouseover: this.handleHdOver,\r
5159             mouseout: this.handleHdOut,\r
5160             mousemove: this.handleHdMove\r
5161         });\r
5162         this.scroller.on('scroll', this.syncScroll,  this);\r
5163         if(g.enableColumnResize !== false){\r
5164             this.splitZone = new Ext.grid.GridView.SplitDragZone(g, this.mainHd.dom);\r
5165             this.splitZone.setOuterHandleElId(Ext.id(this.lockedHd.dom));\r
5166             this.splitZone.setOuterHandleElId(Ext.id(this.mainHd.dom));\r
5167         }\r
5168         if(g.enableColumnMove){\r
5169             this.columnDrag = new Ext.grid.GridView.ColumnDragZone(g, this.innerHd);\r
5170             this.columnDrag.setOuterHandleElId(Ext.id(this.lockedInnerHd));\r
5171             this.columnDrag.setOuterHandleElId(Ext.id(this.innerHd));\r
5172             this.columnDrop = new Ext.grid.HeaderDropZone(g, this.mainHd.dom);\r
5173         }\r
5174         if(g.enableHdMenu !== false){\r
5175             this.hmenu = new Ext.menu.Menu({id: g.id + '-hctx'});\r
5176             this.hmenu.add(\r
5177                 {itemId: 'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'},\r
5178                 {itemId: 'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}\r
5179             );\r
5180             if(this.grid.enableColLock !== false){\r
5181                 this.hmenu.add('-',\r
5182                     {itemId: 'lock', text: this.lockText, cls: 'xg-hmenu-lock'},\r
5183                     {itemId: 'unlock', text: this.unlockText, cls: 'xg-hmenu-unlock'}\r
5184                 );\r
5185             }\r
5186             if(g.enableColumnHide !== false){\r
5187                 this.colMenu = new Ext.menu.Menu({id:g.id + '-hcols-menu'});\r
5188                 this.colMenu.on({\r
5189                     scope: this,\r
5190                     beforeshow: this.beforeColMenuShow,\r
5191                     itemclick: this.handleHdMenuClick\r
5192                 });\r
5193                 this.hmenu.add('-', {\r
5194                     itemId:'columns',\r
5195                     hideOnClick: false,\r
5196                     text: this.columnsText,\r
5197                     menu: this.colMenu,\r
5198                     iconCls: 'x-cols-icon'\r
5199                 });\r
5200             }\r
5201             this.hmenu.on('itemclick', this.handleHdMenuClick, this);\r
5202         }\r
5203         if(g.trackMouseOver){\r
5204             this.mainBody.on({\r
5205                 scope: this,\r
5206                 mouseover: this.onRowOver,\r
5207                 mouseout: this.onRowOut\r
5208             });\r
5209             this.lockedBody.on({\r
5210                 scope: this,\r
5211                 mouseover: this.onRowOver,\r
5212                 mouseout: this.onRowOut\r
5213             });\r
5214         }\r
5215         \r
5216         if(g.enableDragDrop || g.enableDrag){\r
5217             this.dragZone = new Ext.grid.GridDragZone(g, {\r
5218                 ddGroup : g.ddGroup || 'GridDD'\r
5219             });\r
5220         }\r
5221         this.updateHeaderSortState();\r
5222     },\r
5223     \r
5224     layout : function(){\r
5225         if(!this.mainBody){\r
5226             return;\r
5227         }\r
5228         var g = this.grid;\r
5229         var c = g.getGridEl();\r
5230         var csize = c.getSize(true);\r
5231         var vw = csize.width;\r
5232         if(!g.hideHeaders && (vw < 20 || csize.height < 20)){\r
5233             return;\r
5234         }\r
5235         this.syncHeaderHeight();\r
5236         if(g.autoHeight){\r
5237             this.scroller.dom.style.overflow = 'visible';\r
5238             this.lockedScroller.dom.style.overflow = 'visible';\r
5239             if(Ext.isWebKit){\r
5240                 this.scroller.dom.style.position = 'static';\r
5241                 this.lockedScroller.dom.style.position = 'static';\r
5242             }\r
5243         }else{\r
5244             this.el.setSize(csize.width, csize.height);\r
5245             var hdHeight = this.mainHd.getHeight();\r
5246             var vh = csize.height - (hdHeight);\r
5247         }\r
5248         this.updateLockedWidth();\r
5249         if(this.forceFit){\r
5250             if(this.lastViewWidth != vw){\r
5251                 this.fitColumns(false, false);\r
5252                 this.lastViewWidth = vw;\r
5253             }\r
5254         }else {\r
5255             this.autoExpand();\r
5256             this.syncHeaderScroll();\r
5257         }\r
5258         this.onLayout(vw, vh);\r
5259     },\r
5260     \r
5261     getOffsetWidth : function() {\r
5262         return (this.cm.getTotalWidth() - this.cm.getTotalLockedWidth() + this.getScrollOffset()) + 'px';\r
5263     },\r
5264     \r
5265     renderHeaders : function(){\r
5266         var cm = this.cm,\r
5267             ts = this.templates,\r
5268             ct = ts.hcell,\r
5269             cb = [], lcb = [],\r
5270             p = {},\r
5271             len = cm.getColumnCount(),\r
5272             last = len - 1;\r
5273         for(var i = 0; i < len; i++){\r
5274             p.id = cm.getColumnId(i);\r
5275             p.value = cm.getColumnHeader(i) || '';\r
5276             p.style = this.getColumnStyle(i, true);\r
5277             p.tooltip = this.getColumnTooltip(i);\r
5278             p.css = (i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '')) +\r
5279                 (cm.config[i].headerCls ? ' ' + cm.config[i].headerCls : '');\r
5280             if(cm.config[i].align == 'right'){\r
5281                 p.istyle = 'padding-right:16px';\r
5282             } else {\r
5283                 delete p.istyle;\r
5284             }\r
5285             if(cm.isLocked(i)){\r
5286                 lcb[lcb.length] = ct.apply(p);\r
5287             }else{\r
5288                 cb[cb.length] = ct.apply(p);\r
5289             }\r
5290         }\r
5291         return [ts.header.apply({cells: cb.join(''), tstyle:'width:'+this.getTotalWidth()+';'}),\r
5292                 ts.header.apply({cells: lcb.join(''), tstyle:'width:'+this.getLockedWidth()+';'})];\r
5293     },\r
5294     \r
5295     updateHeaders : function(){\r
5296         var hd = this.renderHeaders();\r
5297         this.innerHd.firstChild.innerHTML = hd[0];\r
5298         this.innerHd.firstChild.style.width = this.getOffsetWidth();\r
5299         this.innerHd.firstChild.firstChild.style.width = this.getTotalWidth();\r
5300         this.lockedInnerHd.firstChild.innerHTML = hd[1];\r
5301         var lw = this.getLockedWidth();\r
5302         this.lockedInnerHd.firstChild.style.width = lw;\r
5303         this.lockedInnerHd.firstChild.firstChild.style.width = lw;\r
5304     },\r
5305     \r
5306     getResolvedXY : function(resolved){\r
5307         if(!resolved){\r
5308             return null;\r
5309         }\r
5310         var c = resolved.cell, r = resolved.row;\r
5311         return c ? Ext.fly(c).getXY() : [this.scroller.getX(), Ext.fly(r).getY()];\r
5312     },\r
5313     \r
5314     syncFocusEl : function(row, col, hscroll){\r
5315         Ext.ux.grid.LockingGridView.superclass.syncFocusEl.call(this, row, col, col < this.cm.getLockedCount() ? false : hscroll);\r
5316     },\r
5317     \r
5318     ensureVisible : function(row, col, hscroll){\r
5319         return Ext.ux.grid.LockingGridView.superclass.ensureVisible.call(this, row, col, col < this.cm.getLockedCount() ? false : hscroll);\r
5320     },\r
5321     \r
5322     insertRows : function(dm, firstRow, lastRow, isUpdate){\r
5323         var last = dm.getCount() - 1;\r
5324         if(!isUpdate && firstRow === 0 && lastRow >= last){\r
5325             this.refresh();\r
5326         }else{\r
5327             if(!isUpdate){\r
5328                 this.fireEvent('beforerowsinserted', this, firstRow, lastRow);\r
5329             }\r
5330             var html = this.renderRows(firstRow, lastRow),\r
5331                 before = this.getRow(firstRow);\r
5332             if(before){\r
5333                 if(firstRow === 0){\r
5334                     this.removeRowClass(0, this.firstRowCls);\r
5335                 }\r
5336                 Ext.DomHelper.insertHtml('beforeBegin', before, html[0]);\r
5337                 before = this.getLockedRow(firstRow);\r
5338                 Ext.DomHelper.insertHtml('beforeBegin', before, html[1]);\r
5339             }else{\r
5340                 this.removeRowClass(last - 1, this.lastRowCls);\r
5341                 Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html[0]);\r
5342                 Ext.DomHelper.insertHtml('beforeEnd', this.lockedBody.dom, html[1]);\r
5343             }\r
5344             if(!isUpdate){\r
5345                 this.fireEvent('rowsinserted', this, firstRow, lastRow);\r
5346                 this.processRows(firstRow);\r
5347             }else if(firstRow === 0 || firstRow >= last){\r
5348                 this.addRowClass(firstRow, firstRow === 0 ? this.firstRowCls : this.lastRowCls);\r
5349             }\r
5350         }\r
5351         this.syncFocusEl(firstRow);\r
5352     },\r
5353     \r
5354     getColumnStyle : function(col, isHeader){\r
5355         var style = !isHeader ? this.cm.config[col].cellStyle || this.cm.config[col].css || '' : this.cm.config[col].headerStyle || '';\r
5356         style += 'width:'+this.getColumnWidth(col)+';';\r
5357         if(this.cm.isHidden(col)){\r
5358             style += 'display:none;';\r
5359         }\r
5360         var align = this.cm.config[col].align;\r
5361         if(align){\r
5362             style += 'text-align:'+align+';';\r
5363         }\r
5364         return style;\r
5365     },\r
5366     \r
5367     getLockedWidth : function() {\r
5368         return this.cm.getTotalLockedWidth() + 'px';\r
5369     },\r
5370     \r
5371     getTotalWidth : function() {\r
5372         return (this.cm.getTotalWidth() - this.cm.getTotalLockedWidth()) + 'px';\r
5373     },\r
5374     \r
5375     getColumnData : function(){\r
5376         var cs = [], cm = this.cm, colCount = cm.getColumnCount();\r
5377         for(var i = 0; i < colCount; i++){\r
5378             var name = cm.getDataIndex(i);\r
5379             cs[i] = {\r
5380                 name : (!Ext.isDefined(name) ? this.ds.fields.get(i).name : name),\r
5381                 renderer : cm.getRenderer(i),\r
5382                 id : cm.getColumnId(i),\r
5383                 style : this.getColumnStyle(i),\r
5384                 locked : cm.isLocked(i)\r
5385             };\r
5386         }\r
5387         return cs;\r
5388     },\r
5389     \r
5390     renderBody : function(){\r
5391         var markup = this.renderRows() || ['&#160;', '&#160;'];\r
5392         return [this.templates.body.apply({rows: markup[0]}), this.templates.body.apply({rows: markup[1]})];\r
5393     },\r
5394     \r
5395     refreshRow : function(record){\r
5396         Ext.ux.grid.LockingGridView.superclass.refreshRow.call(this, record);\r
5397         var index = Ext.isNumber(record) ? record : this.ds.indexOf(record);\r
5398         this.getLockedRow(index).rowIndex = index;\r
5399     },\r
5400     \r
5401     refresh : function(headersToo){\r
5402         this.fireEvent('beforerefresh', this);\r
5403         this.grid.stopEditing(true);\r
5404         var result = this.renderBody();\r
5405         this.mainBody.update(result[0]).setWidth(this.getTotalWidth());\r
5406         this.lockedBody.update(result[1]).setWidth(this.getLockedWidth());\r
5407         if(headersToo === true){\r
5408             this.updateHeaders();\r
5409             this.updateHeaderSortState();\r
5410         }\r
5411         this.processRows(0, true);\r
5412         this.layout();\r
5413         this.applyEmptyText();\r
5414         this.fireEvent('refresh', this);\r
5415     },\r
5416     \r
5417     onDenyColumnLock : function(){\r
5418 \r
5419     },\r
5420     \r
5421     initData : function(ds, cm){\r
5422         if(this.cm){\r
5423             this.cm.un('columnlockchange', this.onColumnLock, this);\r
5424         }\r
5425         Ext.ux.grid.LockingGridView.superclass.initData.call(this, ds, cm);\r
5426         if(this.cm){\r
5427             this.cm.on('columnlockchange', this.onColumnLock, this);\r
5428         }\r
5429     },\r
5430     \r
5431     onColumnLock : function(){\r
5432         this.refresh(true);\r
5433     },\r
5434     \r
5435     handleHdMenuClick : function(item){\r
5436         var index = this.hdCtxIndex,\r
5437             cm = this.cm,\r
5438             id = item.getItemId(),\r
5439             llen = cm.getLockedCount();\r
5440         switch(id){\r
5441             case 'lock':\r
5442                 if(cm.getColumnCount(true) <= llen + 1){\r
5443                     this.onDenyColumnLock();\r
5444                     return;\r
5445                 }\r
5446                 if(llen != index){\r
5447                     cm.setLocked(index, true, true);\r
5448                     cm.moveColumn(index, llen);\r
5449                     this.grid.fireEvent('columnmove', index, llen);\r
5450                 }else{\r
5451                     cm.setLocked(index, true);\r
5452                 }\r
5453             break;\r
5454             case 'unlock':\r
5455                 if(llen - 1 != index){\r
5456                     cm.setLocked(index, false, true);\r
5457                     cm.moveColumn(index, llen - 1);\r
5458                     this.grid.fireEvent('columnmove', index, llen - 1);\r
5459                 }else{\r
5460                     cm.setLocked(index, false);\r
5461                 }\r
5462             break;\r
5463             default:\r
5464                 return Ext.ux.grid.LockingGridView.superclass.handleHdMenuClick.call(this, item);\r
5465         }\r
5466         return true;\r
5467     },\r
5468     \r
5469     handleHdDown : function(e, t){\r
5470         Ext.ux.grid.LockingGridView.superclass.handleHdDown.call(this, e, t);\r
5471         if(this.grid.enableColLock !== false){\r
5472             if(Ext.fly(t).hasClass('x-grid3-hd-btn')){\r
5473                 var hd = this.findHeaderCell(t),\r
5474                     index = this.getCellIndex(hd),\r
5475                     ms = this.hmenu.items, cm = this.cm;\r
5476                 ms.get('lock').setDisabled(cm.isLocked(index));\r
5477                 ms.get('unlock').setDisabled(!cm.isLocked(index));\r
5478             }\r
5479         }\r
5480     },\r
5481     \r
5482     syncHeaderHeight: function(){\r
5483         this.innerHd.firstChild.firstChild.style.height = 'auto';\r
5484         this.lockedInnerHd.firstChild.firstChild.style.height = 'auto';\r
5485         var hd = this.innerHd.firstChild.firstChild.offsetHeight,\r
5486             lhd = this.lockedInnerHd.firstChild.firstChild.offsetHeight,\r
5487             height = (lhd > hd ? lhd : hd) + 'px';\r
5488         this.innerHd.firstChild.firstChild.style.height = height;\r
5489         this.lockedInnerHd.firstChild.firstChild.style.height = height;\r
5490     },\r
5491     \r
5492     updateLockedWidth: function(){\r
5493         var lw = this.cm.getTotalLockedWidth(),\r
5494             tw = this.cm.getTotalWidth() - lw,\r
5495             csize = this.grid.getGridEl().getSize(true),\r
5496             lp = Ext.isBorderBox ? 0 : this.lockedBorderWidth,\r
5497             rp = Ext.isBorderBox ? 0 : this.rowBorderWidth,\r
5498             vw = (csize.width - lw - lp - rp) + 'px',\r
5499             so = this.getScrollOffset();\r
5500         if(!this.grid.autoHeight){\r
5501             var vh = (csize.height - this.mainHd.getHeight()) + 'px';\r
5502             this.lockedScroller.dom.style.height = vh;\r
5503             this.scroller.dom.style.height = vh;\r
5504         }\r
5505         this.lockedWrap.dom.style.width = (lw + rp) + 'px';\r
5506         this.scroller.dom.style.width = vw;\r
5507         this.mainWrap.dom.style.left = (lw + lp + rp) + 'px';\r
5508         if(this.innerHd){\r
5509             this.lockedInnerHd.firstChild.style.width = lw + 'px';\r
5510             this.lockedInnerHd.firstChild.firstChild.style.width = lw + 'px';\r
5511             this.innerHd.style.width = vw;\r
5512             this.innerHd.firstChild.style.width = (tw + rp + so) + 'px';\r
5513             this.innerHd.firstChild.firstChild.style.width = tw + 'px';\r
5514         }\r
5515         if(this.mainBody){\r
5516             this.lockedBody.dom.style.width = (lw + rp) + 'px';\r
5517             this.mainBody.dom.style.width = (tw + rp) + 'px';\r
5518         }\r
5519     }\r
5520 });\r
5521 \r
5522 Ext.ux.grid.LockingColumnModel = Ext.extend(Ext.grid.ColumnModel, {\r
5523     isLocked : function(colIndex){\r
5524         return this.config[colIndex].locked === true;\r
5525     },\r
5526     \r
5527     setLocked : function(colIndex, value, suppressEvent){\r
5528         if(this.isLocked(colIndex) == value){\r
5529             return;\r
5530         }\r
5531         this.config[colIndex].locked = value;\r
5532         if(!suppressEvent){\r
5533             this.fireEvent('columnlockchange', this, colIndex, value);\r
5534         }\r
5535     },\r
5536     \r
5537     getTotalLockedWidth : function(){\r
5538         var totalWidth = 0;\r
5539         for(var i = 0, len = this.config.length; i < len; i++){\r
5540             if(this.isLocked(i) && !this.isHidden(i)){\r
5541                 totalWidth += this.getColumnWidth(i);\r
5542             }\r
5543         }\r
5544         return totalWidth;\r
5545     },\r
5546     \r
5547     getLockedCount : function(){\r
5548         for(var i = 0, len = this.config.length; i < len; i++){\r
5549             if(!this.isLocked(i)){\r
5550                 return i;\r
5551             }\r
5552         }\r
5553     },\r
5554     \r
5555     moveColumn : function(oldIndex, newIndex){\r
5556         if(oldIndex < newIndex && this.isLocked(oldIndex) && !this.isLocked(newIndex)){\r
5557             this.setLocked(oldIndex, false, true);\r
5558         }else if(oldIndex > newIndex && !this.isLocked(oldIndex) && this.isLocked(newIndex)){\r
5559             this.setLocked(oldIndex, true, true);\r
5560         }\r
5561         Ext.ux.grid.LockingColumnModel.superclass.moveColumn.apply(this, arguments);\r
5562     }\r
5563 });\r
5564 Ext.ns('Ext.ux.form');\r
5565 \r
5566 /**\r
5567  * @class Ext.ux.form.MultiSelect\r
5568  * @extends Ext.form.Field\r
5569  * A control that allows selection and form submission of multiple list items.\r
5570  *\r
5571  *  @history\r
5572  *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)\r
5573  *    2008-06-19 bpm Docs and demo code clean up\r
5574  *\r
5575  * @constructor\r
5576  * Create a new MultiSelect\r
5577  * @param {Object} config Configuration options\r
5578  * @xtype multiselect\r
5579  */\r
5580 Ext.ux.form.MultiSelect = Ext.extend(Ext.form.Field,  {\r
5581     /**\r
5582      * @cfg {String} legend Wraps the object with a fieldset and specified legend.\r
5583      */\r
5584     /**\r
5585      * @cfg {Ext.ListView} view The {@link Ext.ListView} used to render the multiselect list.\r
5586      */\r
5587     /**\r
5588      * @cfg {String/Array} dragGroup The ddgroup name(s) for the MultiSelect DragZone (defaults to undefined).\r
5589      */\r
5590     /**\r
5591      * @cfg {String/Array} dropGroup The ddgroup name(s) for the MultiSelect DropZone (defaults to undefined).\r
5592      */\r
5593     /**\r
5594      * @cfg {Boolean} ddReorder Whether the items in the MultiSelect list are drag/drop reorderable (defaults to false).\r
5595      */\r
5596     ddReorder:false,\r
5597     /**\r
5598      * @cfg {Object/Array} tbar The top toolbar of the control. This can be a {@link Ext.Toolbar} object, a\r
5599      * toolbar config, or an array of buttons/button configs to be added to the toolbar.\r
5600      */\r
5601     /**\r
5602      * @cfg {String} appendOnly True if the list should only allow append drops when drag/drop is enabled\r
5603      * (use for lists which are sorted, defaults to false).\r
5604      */\r
5605     appendOnly:false,\r
5606     /**\r
5607      * @cfg {Number} width Width in pixels of the control (defaults to 100).\r
5608      */\r
5609     width:100,\r
5610     /**\r
5611      * @cfg {Number} height Height in pixels of the control (defaults to 100).\r
5612      */\r
5613     height:100,\r
5614     /**\r
5615      * @cfg {String/Number} displayField Name/Index of the desired display field in the dataset (defaults to 0).\r
5616      */\r
5617     displayField:0,\r
5618     /**\r
5619      * @cfg {String/Number} valueField Name/Index of the desired value field in the dataset (defaults to 1).\r
5620      */\r
5621     valueField:1,\r
5622     /**\r
5623      * @cfg {Boolean} allowBlank False to require at least one item in the list to be selected, true to allow no\r
5624      * selection (defaults to true).\r
5625      */\r
5626     allowBlank:true,\r
5627     /**\r
5628      * @cfg {Number} minSelections Minimum number of selections allowed (defaults to 0).\r
5629      */\r
5630     minSelections:0,\r
5631     /**\r
5632      * @cfg {Number} maxSelections Maximum number of selections allowed (defaults to Number.MAX_VALUE).\r
5633      */\r
5634     maxSelections:Number.MAX_VALUE,\r
5635     /**\r
5636      * @cfg {String} blankText Default text displayed when the control contains no items (defaults to the same value as\r
5637      * {@link Ext.form.TextField#blankText}.\r
5638      */\r
5639     blankText:Ext.form.TextField.prototype.blankText,\r
5640     /**\r
5641      * @cfg {String} minSelectionsText Validation message displayed when {@link #minSelections} is not met (defaults to 'Minimum {0}\r
5642      * item(s) required').  The {0} token will be replaced by the value of {@link #minSelections}.\r
5643      */\r
5644     minSelectionsText:'Minimum {0} item(s) required',\r
5645     /**\r
5646      * @cfg {String} maxSelectionsText Validation message displayed when {@link #maxSelections} is not met (defaults to 'Maximum {0}\r
5647      * item(s) allowed').  The {0} token will be replaced by the value of {@link #maxSelections}.\r
5648      */\r
5649     maxSelectionsText:'Maximum {0} item(s) allowed',\r
5650     /**\r
5651      * @cfg {String} delimiter The string used to delimit between items when set or returned as a string of values\r
5652      * (defaults to ',').\r
5653      */\r
5654     delimiter:',',\r
5655     /**\r
5656      * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to <tt>undefined</tt>).\r
5657      * Acceptable values for this property are:\r
5658      * <div class="mdetail-params"><ul>\r
5659      * <li><b>any {@link Ext.data.Store Store} subclass</b></li>\r
5660      * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.\r
5661      * <div class="mdetail-params"><ul>\r
5662      * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">\r
5663      * A 1-dimensional array will automatically be expanded (each array item will be the combo\r
5664      * {@link #valueField value} and {@link #displayField text})</div></li>\r
5665      * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">\r
5666      * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo\r
5667      * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.\r
5668      * </div></li></ul></div></li></ul></div>\r
5669      */\r
5670 \r
5671     // private\r
5672     defaultAutoCreate : {tag: "div"},\r
5673 \r
5674     // private\r
5675     initComponent: function(){\r
5676         Ext.ux.form.MultiSelect.superclass.initComponent.call(this);\r
5677 \r
5678         if(Ext.isArray(this.store)){\r
5679             if (Ext.isArray(this.store[0])){\r
5680                 this.store = new Ext.data.ArrayStore({\r
5681                     fields: ['value','text'],\r
5682                     data: this.store\r
5683                 });\r
5684                 this.valueField = 'value';\r
5685             }else{\r
5686                 this.store = new Ext.data.ArrayStore({\r
5687                     fields: ['text'],\r
5688                     data: this.store,\r
5689                     expandData: true\r
5690                 });\r
5691                 this.valueField = 'text';\r
5692             }\r
5693             this.displayField = 'text';\r
5694         } else {\r
5695             this.store = Ext.StoreMgr.lookup(this.store);\r
5696         }\r
5697 \r
5698         this.addEvents({\r
5699             'dblclick' : true,\r
5700             'click' : true,\r
5701             'change' : true,\r
5702             'drop' : true\r
5703         });\r
5704     },\r
5705 \r
5706     // private\r
5707     onRender: function(ct, position){\r
5708         Ext.ux.form.MultiSelect.superclass.onRender.call(this, ct, position);\r
5709 \r
5710         var fs = this.fs = new Ext.form.FieldSet({\r
5711             renderTo: this.el,\r
5712             title: this.legend,\r
5713             height: this.height,\r
5714             width: this.width,\r
5715             style: "padding:0;",\r
5716             tbar: this.tbar\r
5717         });\r
5718         fs.body.addClass('ux-mselect');\r
5719 \r
5720         this.view = new Ext.ListView({\r
5721             multiSelect: true,\r
5722             store: this.store,\r
5723             columns: [{ header: 'Value', width: 1, dataIndex: this.displayField }],\r
5724             hideHeaders: true\r
5725         });\r
5726 \r
5727         fs.add(this.view);\r
5728 \r
5729         this.view.on('click', this.onViewClick, this);\r
5730         this.view.on('beforeclick', this.onViewBeforeClick, this);\r
5731         this.view.on('dblclick', this.onViewDblClick, this);\r
5732 \r
5733         this.hiddenName = this.name || Ext.id();\r
5734         var hiddenTag = { tag: "input", type: "hidden", value: "", name: this.hiddenName };\r
5735         this.hiddenField = this.el.createChild(hiddenTag);\r
5736         this.hiddenField.dom.disabled = this.hiddenName != this.name;\r
5737         fs.doLayout();\r
5738     },\r
5739 \r
5740     // private\r
5741     afterRender: function(){\r
5742         Ext.ux.form.MultiSelect.superclass.afterRender.call(this);\r
5743 \r
5744         if (this.ddReorder && !this.dragGroup && !this.dropGroup){\r
5745             this.dragGroup = this.dropGroup = 'MultiselectDD-' + Ext.id();\r
5746         }\r
5747 \r
5748         if (this.draggable || this.dragGroup){\r
5749             this.dragZone = new Ext.ux.form.MultiSelect.DragZone(this, {\r
5750                 ddGroup: this.dragGroup\r
5751             });\r
5752         }\r
5753         if (this.droppable || this.dropGroup){\r
5754             this.dropZone = new Ext.ux.form.MultiSelect.DropZone(this, {\r
5755                 ddGroup: this.dropGroup\r
5756             });\r
5757         }\r
5758     },\r
5759 \r
5760     // private\r
5761     onViewClick: function(vw, index, node, e) {\r
5762         this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);\r
5763         this.hiddenField.dom.value = this.getValue();\r
5764         this.fireEvent('click', this, e);\r
5765         this.validate();\r
5766     },\r
5767 \r
5768     // private\r
5769     onViewBeforeClick: function(vw, index, node, e) {\r
5770         if (this.disabled) {return false;}\r
5771     },\r
5772 \r
5773     // private\r
5774     onViewDblClick : function(vw, index, node, e) {\r
5775         return this.fireEvent('dblclick', vw, index, node, e);\r
5776     },\r
5777 \r
5778     /**\r
5779      * Returns an array of data values for the selected items in the list. The values will be separated\r
5780      * by {@link #delimiter}.\r
5781      * @return {Array} value An array of string data values\r
5782      */\r
5783     getValue: function(valueField){\r
5784         var returnArray = [];\r
5785         var selectionsArray = this.view.getSelectedIndexes();\r
5786         if (selectionsArray.length == 0) {return '';}\r
5787         for (var i=0; i<selectionsArray.length; i++) {\r
5788             returnArray.push(this.store.getAt(selectionsArray[i]).get((valueField != null) ? valueField : this.valueField));\r
5789         }\r
5790         return returnArray.join(this.delimiter);\r
5791     },\r
5792 \r
5793     /**\r
5794      * Sets a delimited string (using {@link #delimiter}) or array of data values into the list.\r
5795      * @param {String/Array} values The values to set\r
5796      */\r
5797     setValue: function(values) {\r
5798         var index;\r
5799         var selections = [];\r
5800         this.view.clearSelections();\r
5801         this.hiddenField.dom.value = '';\r
5802 \r
5803         if (!values || (values == '')) { return; }\r
5804 \r
5805         if (!Ext.isArray(values)) { values = values.split(this.delimiter); }\r
5806         for (var i=0; i<values.length; i++) {\r
5807             index = this.view.store.indexOf(this.view.store.query(this.valueField,\r
5808                 new RegExp('^' + values[i] + '$', "i")).itemAt(0));\r
5809             selections.push(index);\r
5810         }\r
5811         this.view.select(selections);\r
5812         this.hiddenField.dom.value = this.getValue();\r
5813         this.validate();\r
5814     },\r
5815 \r
5816     // inherit docs\r
5817     reset : function() {\r
5818         this.setValue('');\r
5819     },\r
5820 \r
5821     // inherit docs\r
5822     getRawValue: function(valueField) {\r
5823         var tmp = this.getValue(valueField);\r
5824         if (tmp.length) {\r
5825             tmp = tmp.split(this.delimiter);\r
5826         }\r
5827         else {\r
5828             tmp = [];\r
5829         }\r
5830         return tmp;\r
5831     },\r
5832 \r
5833     // inherit docs\r
5834     setRawValue: function(values){\r
5835         setValue(values);\r
5836     },\r
5837 \r
5838     // inherit docs\r
5839     validateValue : function(value){\r
5840         if (value.length < 1) { // if it has no value\r
5841              if (this.allowBlank) {\r
5842                  this.clearInvalid();\r
5843                  return true;\r
5844              } else {\r
5845                  this.markInvalid(this.blankText);\r
5846                  return false;\r
5847              }\r
5848         }\r
5849         if (value.length < this.minSelections) {\r
5850             this.markInvalid(String.format(this.minSelectionsText, this.minSelections));\r
5851             return false;\r
5852         }\r
5853         if (value.length > this.maxSelections) {\r
5854             this.markInvalid(String.format(this.maxSelectionsText, this.maxSelections));\r
5855             return false;\r
5856         }\r
5857         return true;\r
5858     },\r
5859 \r
5860     // inherit docs\r
5861     disable: function(){\r
5862         this.disabled = true;\r
5863         this.hiddenField.dom.disabled = true;\r
5864         this.fs.disable();\r
5865     },\r
5866 \r
5867     // inherit docs\r
5868     enable: function(){\r
5869         this.disabled = false;\r
5870         this.hiddenField.dom.disabled = false;\r
5871         this.fs.enable();\r
5872     },\r
5873 \r
5874     // inherit docs\r
5875     destroy: function(){\r
5876         Ext.destroy(this.fs, this.dragZone, this.dropZone);\r
5877         Ext.ux.form.MultiSelect.superclass.destroy.call(this);\r
5878     }\r
5879 });\r
5880 \r
5881 \r
5882 Ext.reg('multiselect', Ext.ux.form.MultiSelect);\r
5883 \r
5884 //backwards compat\r
5885 Ext.ux.Multiselect = Ext.ux.form.MultiSelect;\r
5886 \r
5887 \r
5888 Ext.ux.form.MultiSelect.DragZone = function(ms, config){\r
5889     this.ms = ms;\r
5890     this.view = ms.view;\r
5891     var ddGroup = config.ddGroup || 'MultiselectDD';\r
5892     var dd;\r
5893     if (Ext.isArray(ddGroup)){\r
5894         dd = ddGroup.shift();\r
5895     } else {\r
5896         dd = ddGroup;\r
5897         ddGroup = null;\r
5898     }\r
5899     Ext.ux.form.MultiSelect.DragZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });\r
5900     this.setDraggable(ddGroup);\r
5901 };\r
5902 \r
5903 Ext.extend(Ext.ux.form.MultiSelect.DragZone, Ext.dd.DragZone, {\r
5904     onInitDrag : function(x, y){\r
5905         var el = Ext.get(this.dragData.ddel.cloneNode(true));\r
5906         this.proxy.update(el.dom);\r
5907         el.setWidth(el.child('em').getWidth());\r
5908         this.onStartDrag(x, y);\r
5909         return true;\r
5910     },\r
5911 \r
5912     // private\r
5913     collectSelection: function(data) {\r
5914         data.repairXY = Ext.fly(this.view.getSelectedNodes()[0]).getXY();\r
5915         var i = 0;\r
5916         this.view.store.each(function(rec){\r
5917             if (this.view.isSelected(i)) {\r
5918                 var n = this.view.getNode(i);\r
5919                 var dragNode = n.cloneNode(true);\r
5920                 dragNode.id = Ext.id();\r
5921                 data.ddel.appendChild(dragNode);\r
5922                 data.records.push(this.view.store.getAt(i));\r
5923                 data.viewNodes.push(n);\r
5924             }\r
5925             i++;\r
5926         }, this);\r
5927     },\r
5928 \r
5929     // override\r
5930     onEndDrag: function(data, e) {\r
5931         var d = Ext.get(this.dragData.ddel);\r
5932         if (d && d.hasClass("multi-proxy")) {\r
5933             d.remove();\r
5934         }\r
5935     },\r
5936 \r
5937     // override\r
5938     getDragData: function(e){\r
5939         var target = this.view.findItemFromChild(e.getTarget());\r
5940         if(target) {\r
5941             if (!this.view.isSelected(target) && !e.ctrlKey && !e.shiftKey) {\r
5942                 this.view.select(target);\r
5943                 this.ms.setValue(this.ms.getValue());\r
5944             }\r
5945             if (this.view.getSelectionCount() == 0 || e.ctrlKey || e.shiftKey) return false;\r
5946             var dragData = {\r
5947                 sourceView: this.view,\r
5948                 viewNodes: [],\r
5949                 records: []\r
5950             };\r
5951             if (this.view.getSelectionCount() == 1) {\r
5952                 var i = this.view.getSelectedIndexes()[0];\r
5953                 var n = this.view.getNode(i);\r
5954                 dragData.viewNodes.push(dragData.ddel = n);\r
5955                 dragData.records.push(this.view.store.getAt(i));\r
5956                 dragData.repairXY = Ext.fly(n).getXY();\r
5957             } else {\r
5958                 dragData.ddel = document.createElement('div');\r
5959                 dragData.ddel.className = 'multi-proxy';\r
5960                 this.collectSelection(dragData);\r
5961             }\r
5962             return dragData;\r
5963         }\r
5964         return false;\r
5965     },\r
5966 \r
5967     // override the default repairXY.\r
5968     getRepairXY : function(e){\r
5969         return this.dragData.repairXY;\r
5970     },\r
5971 \r
5972     // private\r
5973     setDraggable: function(ddGroup){\r
5974         if (!ddGroup) return;\r
5975         if (Ext.isArray(ddGroup)) {\r
5976             Ext.each(ddGroup, this.setDraggable, this);\r
5977             return;\r
5978         }\r
5979         this.addToGroup(ddGroup);\r
5980     }\r
5981 });\r
5982 \r
5983 Ext.ux.form.MultiSelect.DropZone = function(ms, config){\r
5984     this.ms = ms;\r
5985     this.view = ms.view;\r
5986     var ddGroup = config.ddGroup || 'MultiselectDD';\r
5987     var dd;\r
5988     if (Ext.isArray(ddGroup)){\r
5989         dd = ddGroup.shift();\r
5990     } else {\r
5991         dd = ddGroup;\r
5992         ddGroup = null;\r
5993     }\r
5994     Ext.ux.form.MultiSelect.DropZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });\r
5995     this.setDroppable(ddGroup);\r
5996 };\r
5997 \r
5998 Ext.extend(Ext.ux.form.MultiSelect.DropZone, Ext.dd.DropZone, {\r
5999     /**\r
6000      * Part of the Ext.dd.DropZone interface. If no target node is found, the\r
6001      * whole Element becomes the target, and this causes the drop gesture to append.\r
6002      */\r
6003     getTargetFromEvent : function(e) {\r
6004         var target = e.getTarget();\r
6005         return target;\r
6006     },\r
6007 \r
6008     // private\r
6009     getDropPoint : function(e, n, dd){\r
6010         if (n == this.ms.fs.body.dom) { return "below"; }\r
6011         var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight;\r
6012         var c = t + (b - t) / 2;\r
6013         var y = Ext.lib.Event.getPageY(e);\r
6014         if(y <= c) {\r
6015             return "above";\r
6016         }else{\r
6017             return "below";\r
6018         }\r
6019     },\r
6020 \r
6021     // private\r
6022     isValidDropPoint: function(pt, n, data) {\r
6023         if (!data.viewNodes || (data.viewNodes.length != 1)) {\r
6024             return true;\r
6025         }\r
6026         var d = data.viewNodes[0];\r
6027         if (d == n) {\r
6028             return false;\r
6029         }\r
6030         if ((pt == "below") && (n.nextSibling == d)) {\r
6031             return false;\r
6032         }\r
6033         if ((pt == "above") && (n.previousSibling == d)) {\r
6034             return false;\r
6035         }\r
6036         return true;\r
6037     },\r
6038 \r
6039     // override\r
6040     onNodeEnter : function(n, dd, e, data){\r
6041         return false;\r
6042     },\r
6043 \r
6044     // override\r
6045     onNodeOver : function(n, dd, e, data){\r
6046         var dragElClass = this.dropNotAllowed;\r
6047         var pt = this.getDropPoint(e, n, dd);\r
6048         if (this.isValidDropPoint(pt, n, data)) {\r
6049             if (this.ms.appendOnly) {\r
6050                 return "x-tree-drop-ok-below";\r
6051             }\r
6052 \r
6053             // set the insert point style on the target node\r
6054             if (pt) {\r
6055                 var targetElClass;\r
6056                 if (pt == "above"){\r
6057                     dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";\r
6058                     targetElClass = "x-view-drag-insert-above";\r
6059                 } else {\r
6060                     dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";\r
6061                     targetElClass = "x-view-drag-insert-below";\r
6062                 }\r
6063                 if (this.lastInsertClass != targetElClass){\r
6064                     Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass);\r
6065                     this.lastInsertClass = targetElClass;\r
6066                 }\r
6067             }\r
6068         }\r
6069         return dragElClass;\r
6070     },\r
6071 \r
6072     // private\r
6073     onNodeOut : function(n, dd, e, data){\r
6074         this.removeDropIndicators(n);\r
6075     },\r
6076 \r
6077     // private\r
6078     onNodeDrop : function(n, dd, e, data){\r
6079         if (this.ms.fireEvent("drop", this, n, dd, e, data) === false) {\r
6080             return false;\r
6081         }\r
6082         var pt = this.getDropPoint(e, n, dd);\r
6083         if (n != this.ms.fs.body.dom)\r
6084             n = this.view.findItemFromChild(n);\r
6085 \r
6086         if(this.ms.appendOnly) {\r
6087             insertAt = this.view.store.getCount();\r
6088         } else {\r
6089             insertAt = n == this.ms.fs.body.dom ? this.view.store.getCount() - 1 : this.view.indexOf(n);\r
6090             if (pt == "below") {\r
6091                 insertAt++;\r
6092             }\r
6093         }\r
6094 \r
6095         var dir = false;\r
6096 \r
6097         // Validate if dragging within the same MultiSelect\r
6098         if (data.sourceView == this.view) {\r
6099             // If the first element to be inserted below is the target node, remove it\r
6100             if (pt == "below") {\r
6101                 if (data.viewNodes[0] == n) {\r
6102                     data.viewNodes.shift();\r
6103                 }\r
6104             } else {  // If the last element to be inserted above is the target node, remove it\r
6105                 if (data.viewNodes[data.viewNodes.length - 1] == n) {\r
6106                     data.viewNodes.pop();\r
6107                 }\r
6108             }\r
6109 \r
6110             // Nothing to drop...\r
6111             if (!data.viewNodes.length) {\r
6112                 return false;\r
6113             }\r
6114 \r
6115             // If we are moving DOWN, then because a store.remove() takes place first,\r
6116             // the insertAt must be decremented.\r
6117             if (insertAt > this.view.store.indexOf(data.records[0])) {\r
6118                 dir = 'down';\r
6119                 insertAt--;\r
6120             }\r
6121         }\r
6122 \r
6123         for (var i = 0; i < data.records.length; i++) {\r
6124             var r = data.records[i];\r
6125             if (data.sourceView) {\r
6126                 data.sourceView.store.remove(r);\r
6127             }\r
6128             this.view.store.insert(dir == 'down' ? insertAt : insertAt++, r);\r
6129             var si = this.view.store.sortInfo;\r
6130             if(si){\r
6131                 this.view.store.sort(si.field, si.direction);\r
6132             }\r
6133         }\r
6134         return true;\r
6135     },\r
6136 \r
6137     // private\r
6138     removeDropIndicators : function(n){\r
6139         if(n){\r
6140             Ext.fly(n).removeClass([\r
6141                 "x-view-drag-insert-above",\r
6142                 "x-view-drag-insert-left",\r
6143                 "x-view-drag-insert-right",\r
6144                 "x-view-drag-insert-below"]);\r
6145             this.lastInsertClass = "_noclass";\r
6146         }\r
6147     },\r
6148 \r
6149     // private\r
6150     setDroppable: function(ddGroup){\r
6151         if (!ddGroup) return;\r
6152         if (Ext.isArray(ddGroup)) {\r
6153             Ext.each(ddGroup, this.setDroppable, this);\r
6154             return;\r
6155         }\r
6156         this.addToGroup(ddGroup);\r
6157     }\r
6158 });\r
6159
6160 /* Fix for Opera, which does not seem to include the map function on Array's */
6161 if (!Array.prototype.map) {
6162     Array.prototype.map = function(fun){
6163         var len = this.length;
6164         if (typeof fun != 'function') {
6165             throw new TypeError();
6166         }
6167         var res = new Array(len);
6168         var thisp = arguments[1];
6169         for (var i = 0; i < len; i++) {
6170             if (i in this) {
6171                 res[i] = fun.call(thisp, this[i], i, this);
6172             }
6173         }
6174         return res;
6175     };
6176 }
6177
6178 Ext.ns('Ext.ux.data');
6179
6180 /**
6181  * @class Ext.ux.data.PagingMemoryProxy
6182  * @extends Ext.data.MemoryProxy
6183  * <p>Paging Memory Proxy, allows to use paging grid with in memory dataset</p>
6184  */
6185 Ext.ux.data.PagingMemoryProxy = Ext.extend(Ext.data.MemoryProxy, {
6186     constructor : function(data){
6187         Ext.ux.data.PagingMemoryProxy.superclass.constructor.call(this);
6188         this.data = data;
6189     },
6190     doRequest : function(action, rs, params, reader, callback, scope, options){
6191         params = params ||
6192         {};
6193         var result;
6194         try {
6195             result = reader.readRecords(this.data);
6196         } 
6197         catch (e) {
6198             this.fireEvent('loadexception', this, options, null, e);
6199             callback.call(scope, null, options, false);
6200             return;
6201         }
6202         
6203         // filtering
6204         if (params.filter !== undefined) {
6205             result.records = result.records.filter(function(el){
6206                 if (typeof(el) == 'object') {
6207                     var att = params.filterCol || 0;
6208                     return String(el.data[att]).match(params.filter) ? true : false;
6209                 }
6210                 else {
6211                     return String(el).match(params.filter) ? true : false;
6212                 }
6213             });
6214             result.totalRecords = result.records.length;
6215         }
6216         
6217         // sorting
6218         if (params.sort !== undefined) {
6219             // use integer as params.sort to specify column, since arrays are not named
6220             // params.sort=0; would also match a array without columns
6221             var dir = String(params.dir).toUpperCase() == 'DESC' ? -1 : 1;
6222             var fn = function(v1, v2){
6223                 return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
6224             };
6225             result.records.sort(function(a, b){
6226                 var v = 0;
6227                 if (typeof(a) == 'object') {
6228                     v = fn(a.data[params.sort], b.data[params.sort]) * dir;
6229                 }
6230                 else {
6231                     v = fn(a, b) * dir;
6232                 }
6233                 if (v == 0) {
6234                     v = (a.index < b.index ? -1 : 1);
6235                 }
6236                 return v;
6237             });
6238         }
6239         // paging (use undefined cause start can also be 0 (thus false))
6240         if (params.start !== undefined && params.limit !== undefined) {
6241             result.records = result.records.slice(params.start, params.start + params.limit);
6242         }
6243         callback.call(scope, result, options, true);
6244     }
6245 });
6246
6247 //backwards compat.
6248 Ext.data.PagingMemoryProxy = Ext.ux.data.PagingMemoryProxy;
6249 Ext.ux.PanelResizer = Ext.extend(Ext.util.Observable, {\r
6250     minHeight: 0,\r
6251     maxHeight:10000000,\r
6252 \r
6253     constructor: function(config){\r
6254         Ext.apply(this, config);\r
6255         this.events = {};\r
6256         Ext.ux.PanelResizer.superclass.constructor.call(this, config);\r
6257     },\r
6258 \r
6259     init : function(p){\r
6260         this.panel = p;\r
6261 \r
6262         if(this.panel.elements.indexOf('footer')==-1){\r
6263             p.elements += ',footer';\r
6264         }\r
6265         p.on('render', this.onRender, this);\r
6266     },\r
6267 \r
6268     onRender : function(p){\r
6269         this.handle = p.footer.createChild({cls:'x-panel-resize'});\r
6270 \r
6271         this.tracker = new Ext.dd.DragTracker({\r
6272             onStart: this.onDragStart.createDelegate(this),\r
6273             onDrag: this.onDrag.createDelegate(this),\r
6274             onEnd: this.onDragEnd.createDelegate(this),\r
6275             tolerance: 3,\r
6276             autoStart: 300\r
6277         });\r
6278         this.tracker.initEl(this.handle);\r
6279         p.on('beforedestroy', this.tracker.destroy, this.tracker);\r
6280     },\r
6281 \r
6282         // private\r
6283     onDragStart: function(e){\r
6284         this.dragging = true;\r
6285         this.startHeight = this.panel.el.getHeight();\r
6286         this.fireEvent('dragstart', this, e);\r
6287     },\r
6288 \r
6289         // private\r
6290     onDrag: function(e){\r
6291         this.panel.setHeight((this.startHeight-this.tracker.getOffset()[1]).constrain(this.minHeight, this.maxHeight));\r
6292         this.fireEvent('drag', this, e);\r
6293     },\r
6294 \r
6295         // private\r
6296     onDragEnd: function(e){\r
6297         this.dragging = false;\r
6298         this.fireEvent('dragend', this, e);\r
6299     }\r
6300 });\r
6301 Ext.preg('panelresizer', Ext.ux.PanelResizer);Ext.ux.Portal = Ext.extend(Ext.Panel, {\r
6302     layout : 'column',\r
6303     autoScroll : true,\r
6304     cls : 'x-portal',\r
6305     defaultType : 'portalcolumn',\r
6306     \r
6307     initComponent : function(){\r
6308         Ext.ux.Portal.superclass.initComponent.call(this);\r
6309         this.addEvents({\r
6310             validatedrop:true,\r
6311             beforedragover:true,\r
6312             dragover:true,\r
6313             beforedrop:true,\r
6314             drop:true\r
6315         });\r
6316     },\r
6317 \r
6318     initEvents : function(){\r
6319         Ext.ux.Portal.superclass.initEvents.call(this);\r
6320         this.dd = new Ext.ux.Portal.DropZone(this, this.dropConfig);\r
6321     },\r
6322     \r
6323     beforeDestroy : function() {\r
6324         if(this.dd){\r
6325             this.dd.unreg();\r
6326         }\r
6327         Ext.ux.Portal.superclass.beforeDestroy.call(this);\r
6328     }\r
6329 });\r
6330 \r
6331 Ext.reg('portal', Ext.ux.Portal);\r
6332 \r
6333 \r
6334 Ext.ux.Portal.DropZone = function(portal, cfg){\r
6335     this.portal = portal;\r
6336     Ext.dd.ScrollManager.register(portal.body);\r
6337     Ext.ux.Portal.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg);\r
6338     portal.body.ddScrollConfig = this.ddScrollConfig;\r
6339 };\r
6340 \r
6341 Ext.extend(Ext.ux.Portal.DropZone, Ext.dd.DropTarget, {\r
6342     ddScrollConfig : {\r
6343         vthresh: 50,\r
6344         hthresh: -1,\r
6345         animate: true,\r
6346         increment: 200\r
6347     },\r
6348 \r
6349     createEvent : function(dd, e, data, col, c, pos){\r
6350         return {\r
6351             portal: this.portal,\r
6352             panel: data.panel,\r
6353             columnIndex: col,\r
6354             column: c,\r
6355             position: pos,\r
6356             data: data,\r
6357             source: dd,\r
6358             rawEvent: e,\r
6359             status: this.dropAllowed\r
6360         };\r
6361     },\r
6362 \r
6363     notifyOver : function(dd, e, data){\r
6364         var xy = e.getXY(), portal = this.portal, px = dd.proxy;\r
6365 \r
6366         // case column widths\r
6367         if(!this.grid){\r
6368             this.grid = this.getGrid();\r
6369         }\r
6370 \r
6371         // handle case scroll where scrollbars appear during drag\r
6372         var cw = portal.body.dom.clientWidth;\r
6373         if(!this.lastCW){\r
6374             this.lastCW = cw;\r
6375         }else if(this.lastCW != cw){\r
6376             this.lastCW = cw;\r
6377             portal.doLayout();\r
6378             this.grid = this.getGrid();\r
6379         }\r
6380 \r
6381         // determine column\r
6382         var col = 0, xs = this.grid.columnX, cmatch = false;\r
6383         for(var len = xs.length; col < len; col++){\r
6384             if(xy[0] < (xs[col].x + xs[col].w)){\r
6385                 cmatch = true;\r
6386                 break;\r
6387             }\r
6388         }\r
6389         // no match, fix last index\r
6390         if(!cmatch){\r
6391             col--;\r
6392         }\r
6393 \r
6394         // find insert position\r
6395         var p, match = false, pos = 0,\r
6396             c = portal.items.itemAt(col),\r
6397             items = c.items.items, overSelf = false;\r
6398 \r
6399         for(var len = items.length; pos < len; pos++){\r
6400             p = items[pos];\r
6401             var h = p.el.getHeight();\r
6402             if(h === 0){\r
6403                 overSelf = true;\r
6404             }\r
6405             else if((p.el.getY()+(h/2)) > xy[1]){\r
6406                 match = true;\r
6407                 break;\r
6408             }\r
6409         }\r
6410 \r
6411         pos = (match && p ? pos : c.items.getCount()) + (overSelf ? -1 : 0);\r
6412         var overEvent = this.createEvent(dd, e, data, col, c, pos);\r
6413 \r
6414         if(portal.fireEvent('validatedrop', overEvent) !== false &&\r
6415            portal.fireEvent('beforedragover', overEvent) !== false){\r
6416 \r
6417             // make sure proxy width is fluid\r
6418             px.getProxy().setWidth('auto');\r
6419 \r
6420             if(p){\r
6421                 px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null);\r
6422             }else{\r
6423                 px.moveProxy(c.el.dom, null);\r
6424             }\r
6425 \r
6426             this.lastPos = {c: c, col: col, p: overSelf || (match && p) ? pos : false};\r
6427             this.scrollPos = portal.body.getScroll();\r
6428 \r
6429             portal.fireEvent('dragover', overEvent);\r
6430 \r
6431             return overEvent.status;\r
6432         }else{\r
6433             return overEvent.status;\r
6434         }\r
6435 \r
6436     },\r
6437 \r
6438     notifyOut : function(){\r
6439         delete this.grid;\r
6440     },\r
6441 \r
6442     notifyDrop : function(dd, e, data){\r
6443         delete this.grid;\r
6444         if(!this.lastPos){\r
6445             return;\r
6446         }\r
6447         var c = this.lastPos.c, col = this.lastPos.col, pos = this.lastPos.p;\r
6448 \r
6449         var dropEvent = this.createEvent(dd, e, data, col, c,\r
6450             pos !== false ? pos : c.items.getCount());\r
6451 \r
6452         if(this.portal.fireEvent('validatedrop', dropEvent) !== false &&\r
6453            this.portal.fireEvent('beforedrop', dropEvent) !== false){\r
6454 \r
6455             dd.proxy.getProxy().remove();\r
6456             dd.panel.el.dom.parentNode.removeChild(dd.panel.el.dom);\r
6457             \r
6458             if(pos !== false){\r
6459                 if(c == dd.panel.ownerCt && (c.items.items.indexOf(dd.panel) <= pos)){\r
6460                     pos++;\r
6461                 }\r
6462                 c.insert(pos, dd.panel);\r
6463             }else{\r
6464                 c.add(dd.panel);\r
6465             }\r
6466             \r
6467             c.doLayout();\r
6468 \r
6469             this.portal.fireEvent('drop', dropEvent);\r
6470 \r
6471             // scroll position is lost on drop, fix it\r
6472             var st = this.scrollPos.top;\r
6473             if(st){\r
6474                 var d = this.portal.body.dom;\r
6475                 setTimeout(function(){\r
6476                     d.scrollTop = st;\r
6477                 }, 10);\r
6478             }\r
6479 \r
6480         }\r
6481         delete this.lastPos;\r
6482     },\r
6483 \r
6484     // internal cache of body and column coords\r
6485     getGrid : function(){\r
6486         var box = this.portal.bwrap.getBox();\r
6487         box.columnX = [];\r
6488         this.portal.items.each(function(c){\r
6489              box.columnX.push({x: c.el.getX(), w: c.el.getWidth()});\r
6490         });\r
6491         return box;\r
6492     },\r
6493 \r
6494     // unregister the dropzone from ScrollManager\r
6495     unreg: function() {\r
6496         //Ext.dd.ScrollManager.unregister(this.portal.body);\r
6497         Ext.ux.Portal.DropZone.superclass.unreg.call(this);\r
6498     }\r
6499 });\r
6500 Ext.ux.PortalColumn = Ext.extend(Ext.Container, {\r
6501     layout : 'anchor',\r
6502     //autoEl : 'div',//already defined by Ext.Component\r
6503     defaultType : 'portlet',\r
6504     cls : 'x-portal-column'\r
6505 });\r
6506 \r
6507 Ext.reg('portalcolumn', Ext.ux.PortalColumn);\r
6508 Ext.ux.Portlet = Ext.extend(Ext.Panel, {\r
6509     anchor : '100%',\r
6510     frame : true,\r
6511     collapsible : true,\r
6512     draggable : true,\r
6513     cls : 'x-portlet'\r
6514 });\r
6515 \r
6516 Ext.reg('portlet', Ext.ux.Portlet);\r
6517 /**
6518 * @class Ext.ux.ProgressBarPager
6519 * @extends Object 
6520 * Plugin (ptype = 'tabclosemenu') for displaying a progressbar inside of a paging toolbar instead of plain text
6521
6522 * @ptype progressbarpager 
6523 * @constructor
6524 * Create a new ItemSelector
6525 * @param {Object} config Configuration options
6526 * @xtype itemselector 
6527 */
6528 Ext.ux.ProgressBarPager  = Ext.extend(Object, {
6529         /**
6530         * @cfg {Integer} progBarWidth
6531         * <p>The default progress bar width.  Default is 225.</p>
6532         */
6533         progBarWidth   : 225,
6534         /**
6535         * @cfg {String} defaultText
6536         * <p>The text to display while the store is loading.  Default is 'Loading...'</p>
6537         */
6538         defaultText    : 'Loading...',
6539         /**
6540         * @cfg {Object} defaultAnimCfg 
6541         * <p>A {@link Ext.Fx Ext.Fx} configuration object.  Default is  { duration : 1, easing : 'bounceOut' }.</p>
6542         */
6543         defaultAnimCfg : {
6544                 duration   : 1,
6545                 easing     : 'bounceOut'        
6546         },                                                                                                
6547         constructor : function(config) {
6548                 if (config) {
6549                         Ext.apply(this, config);
6550                 }
6551         },
6552         //public
6553         init : function (parent) {
6554                 
6555                 if(parent.displayInfo){
6556                         this.parent = parent;
6557                         var ind  = parent.items.indexOf(parent.displayItem);
6558                         parent.remove(parent.displayItem, true);
6559                         this.progressBar = new Ext.ProgressBar({
6560                                 text    : this.defaultText,
6561                                 width   : this.progBarWidth,
6562                                 animate :  this.defaultAnimCfg
6563                         });                                     
6564                    
6565                         parent.displayItem = this.progressBar;
6566                         
6567                         parent.add(parent.displayItem); 
6568                         parent.doLayout();
6569                         Ext.apply(parent, this.parentOverrides);                
6570                         
6571                         this.progressBar.on('render', function(pb) {
6572                 pb.mon(pb.getEl().applyStyles('cursor:pointer'), 'click', this.handleProgressBarClick, this);
6573             }, this, {single: true});
6574                                                 
6575                 }
6576                   
6577         },
6578         // private
6579         // This method handles the click for the progress bar
6580         handleProgressBarClick : function(e){
6581                 var parent = this.parent,
6582                     displayItem = parent.displayItem,
6583                     box = this.progressBar.getBox(),
6584                     xy = e.getXY(),
6585                     position = xy[0]-box.x,
6586                     pages = Math.ceil(parent.store.getTotalCount()/parent.pageSize),
6587                     newpage = Math.ceil(position/(displayItem.width/pages));
6588             
6589                 parent.changePage(newpage);
6590         },
6591         
6592         // private, overriddes
6593         parentOverrides  : {
6594                 // private
6595                 // This method updates the information via the progress bar.
6596                 updateInfo : function(){
6597                         if(this.displayItem){
6598                                 var count = this.store.getCount(),
6599                                     pgData = this.getPageData(),
6600                                     pageNum = this.readPage(pgData),
6601                                     msg = count == 0 ?
6602                                         this.emptyMsg :
6603                                         String.format(
6604                                                 this.displayMsg,
6605                                                 this.cursor+1, this.cursor+count, this.store.getTotalCount()
6606                                         );
6607                                         
6608                                 pageNum = pgData.activePage; ;  
6609                                 
6610                                 var pct = pageNum / pgData.pages;       
6611                                 
6612                                 this.displayItem.updateProgress(pct, msg, this.animate || this.defaultAnimConfig);
6613                         }
6614                 }
6615         }
6616 });
6617 Ext.preg('progressbarpager', Ext.ux.ProgressBarPager);
6618
6619 Ext.ns('Ext.ux.grid');
6620
6621 /**
6622  * @class Ext.ux.grid.RowEditor
6623  * @extends Ext.Panel
6624  * Plugin (ptype = 'roweditor') that adds the ability to rapidly edit full rows in a grid.
6625  * A validation mode may be enabled which uses AnchorTips to notify the user of all
6626  * validation errors at once.
6627  *
6628  * @ptype roweditor
6629  */
6630 Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
6631     floating: true,
6632     shadow: false,
6633     layout: 'hbox',
6634     cls: 'x-small-editor',
6635     buttonAlign: 'center',
6636     baseCls: 'x-row-editor',
6637     elements: 'header,footer,body',
6638     frameWidth: 5,
6639     buttonPad: 3,
6640     clicksToEdit: 'auto',
6641     monitorValid: true,
6642     focusDelay: 250,
6643     errorSummary: true,
6644
6645     saveText: 'Save',
6646     cancelText: 'Cancel',
6647     commitChangesText: 'You need to commit or cancel your changes',
6648     errorText: 'Errors',
6649
6650     defaults: {
6651         normalWidth: true
6652     },
6653
6654     initComponent: function(){
6655         Ext.ux.grid.RowEditor.superclass.initComponent.call(this);
6656         this.addEvents(
6657             /**
6658              * @event beforeedit
6659              * Fired before the row editor is activated.
6660              * If the listener returns <tt>false</tt> the editor will not be activated.
6661              * @param {Ext.ux.grid.RowEditor} roweditor This object
6662              * @param {Number} rowIndex The rowIndex of the row just edited
6663              */
6664             'beforeedit',
6665             /**
6666              * @event canceledit
6667              * Fired when the editor is cancelled.
6668              * @param {Ext.ux.grid.RowEditor} roweditor This object
6669              * @param {Boolean} forced True if the cancel button is pressed, false is the editor was invalid.
6670              */
6671             'canceledit',
6672             /**
6673              * @event validateedit
6674              * Fired after a row is edited and passes validation.
6675              * If the listener returns <tt>false</tt> changes to the record will not be set.
6676              * @param {Ext.ux.grid.RowEditor} roweditor This object
6677              * @param {Object} changes Object with changes made to the record.
6678              * @param {Ext.data.Record} r The Record that was edited.
6679              * @param {Number} rowIndex The rowIndex of the row just edited
6680              */
6681             'validateedit',
6682             /**
6683              * @event afteredit
6684              * Fired after a row is edited and passes validation.  This event is fired
6685              * after the store's update event is fired with this edit.
6686              * @param {Ext.ux.grid.RowEditor} roweditor This object
6687              * @param {Object} changes Object with changes made to the record.
6688              * @param {Ext.data.Record} r The Record that was edited.
6689              * @param {Number} rowIndex The rowIndex of the row just edited
6690              */
6691             'afteredit'
6692         );
6693     },
6694
6695     init: function(grid){
6696         this.grid = grid;
6697         this.ownerCt = grid;
6698         if(this.clicksToEdit === 2){
6699             grid.on('rowdblclick', this.onRowDblClick, this);
6700         }else{
6701             grid.on('rowclick', this.onRowClick, this);
6702             if(Ext.isIE){
6703                 grid.on('rowdblclick', this.onRowDblClick, this);
6704             }
6705         }
6706
6707         // stopEditing without saving when a record is removed from Store.
6708         grid.getStore().on('remove', function() {
6709             this.stopEditing(false);
6710         },this);
6711
6712         grid.on({
6713             scope: this,
6714             keydown: this.onGridKey,
6715             columnresize: this.verifyLayout,
6716             columnmove: this.refreshFields,
6717             reconfigure: this.refreshFields,
6718             beforedestroy : this.beforedestroy,
6719             destroy : this.destroy,
6720             bodyscroll: {
6721                 buffer: 250,
6722                 fn: this.positionButtons
6723             }
6724         });
6725         grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {delay:1});
6726         grid.getView().on('refresh', this.stopEditing.createDelegate(this, []));
6727     },
6728
6729     beforedestroy: function() {
6730         this.grid.getStore().un('remove', this.onStoreRemove, this);
6731         this.stopEditing(false);
6732         Ext.destroy(this.btns);
6733     },
6734
6735     refreshFields: function(){
6736         this.initFields();
6737         this.verifyLayout();
6738     },
6739
6740     isDirty: function(){
6741         var dirty;
6742         this.items.each(function(f){
6743             if(String(this.values[f.id]) !== String(f.getValue())){
6744                 dirty = true;
6745                 return false;
6746             }
6747         }, this);
6748         return dirty;
6749     },
6750
6751     startEditing: function(rowIndex, doFocus){
6752         if(this.editing && this.isDirty()){
6753             this.showTooltip(this.commitChangesText);
6754             return;
6755         }
6756         if(Ext.isObject(rowIndex)){
6757             rowIndex = this.grid.getStore().indexOf(rowIndex);
6758         }
6759         if(this.fireEvent('beforeedit', this, rowIndex) !== false){
6760             this.editing = true;
6761             var g = this.grid, view = g.getView(),
6762                 row = view.getRow(rowIndex),
6763                 record = g.store.getAt(rowIndex);
6764
6765             this.record = record;
6766             this.rowIndex = rowIndex;
6767             this.values = {};
6768             if(!this.rendered){
6769                 this.render(view.getEditorParent());
6770             }
6771             var w = Ext.fly(row).getWidth();
6772             this.setSize(w);
6773             if(!this.initialized){
6774                 this.initFields();
6775             }
6776             var cm = g.getColumnModel(), fields = this.items.items, f, val;
6777             for(var i = 0, len = cm.getColumnCount(); i < len; i++){
6778                 val = this.preEditValue(record, cm.getDataIndex(i));
6779                 f = fields[i];
6780                 f.setValue(val);
6781                 this.values[f.id] = Ext.isEmpty(val) ? '' : val;
6782             }
6783             this.verifyLayout(true);
6784             if(!this.isVisible()){
6785                 this.setPagePosition(Ext.fly(row).getXY());
6786             } else{
6787                 this.el.setXY(Ext.fly(row).getXY(), {duration:0.15});
6788             }
6789             if(!this.isVisible()){
6790                 this.show().doLayout();
6791             }
6792             if(doFocus !== false){
6793                 this.doFocus.defer(this.focusDelay, this);
6794             }
6795         }
6796     },
6797
6798     stopEditing : function(saveChanges){
6799         this.editing = false;
6800         if(!this.isVisible()){
6801             return;
6802         }
6803         if(saveChanges === false || !this.isValid()){
6804             this.hide();
6805             this.fireEvent('canceledit', this, saveChanges === false);
6806             return;
6807         }
6808         var changes = {},
6809             r = this.record,
6810             hasChange = false,
6811             cm = this.grid.colModel,
6812             fields = this.items.items;
6813         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
6814             if(!cm.isHidden(i)){
6815                 var dindex = cm.getDataIndex(i);
6816                 if(!Ext.isEmpty(dindex)){
6817                     var oldValue = r.data[dindex],
6818                         value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
6819                     if(String(oldValue) !== String(value)){
6820                         changes[dindex] = value;
6821                         hasChange = true;
6822                     }
6823                 }
6824             }
6825         }
6826         if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){
6827             r.beginEdit();
6828             Ext.iterate(changes, function(name, value){
6829                 r.set(name, value);
6830             });
6831             r.endEdit();
6832             this.fireEvent('afteredit', this, changes, r, this.rowIndex);
6833         }
6834         this.hide();
6835     },
6836
6837     verifyLayout: function(force){
6838         if(this.el && (this.isVisible() || force === true)){
6839             var row = this.grid.getView().getRow(this.rowIndex);
6840             this.setSize(Ext.fly(row).getWidth(), Ext.isIE ? Ext.fly(row).getHeight() + 9 : undefined);
6841             var cm = this.grid.colModel, fields = this.items.items;
6842             for(var i = 0, len = cm.getColumnCount(); i < len; i++){
6843                 if(!cm.isHidden(i)){
6844                     var adjust = 0;
6845                     if(i === (len - 1)){
6846                         adjust += 3; // outer padding
6847                     } else{
6848                         adjust += 1;
6849                     }
6850                     fields[i].show();
6851                     fields[i].setWidth(cm.getColumnWidth(i) - adjust);
6852                 } else{
6853                     fields[i].hide();
6854                 }
6855             }
6856             this.doLayout();
6857             this.positionButtons();
6858         }
6859     },
6860
6861     slideHide : function(){
6862         this.hide();
6863     },
6864
6865     initFields: function(){
6866         var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
6867         this.removeAll(false);
6868         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
6869             var c = cm.getColumnAt(i),
6870                 ed = c.getEditor();
6871             if(!ed){
6872                 ed = c.displayEditor || new Ext.form.DisplayField();
6873             }else{
6874                 ed = ed.field;
6875             }
6876             if(i == 0){
6877                 ed.margins = pm('0 1 2 1');
6878             } else if(i == len - 1){
6879                 ed.margins = pm('0 0 2 1');
6880             } else{
6881                 ed.margins = pm('0 1 2');
6882             }
6883             ed.setWidth(cm.getColumnWidth(i));
6884             ed.column = c;
6885             if(ed.ownerCt !== this){
6886                 ed.on('focus', this.ensureVisible, this);
6887                 ed.on('specialkey', this.onKey, this);
6888             }
6889             this.insert(i, ed);
6890         }
6891         this.initialized = true;
6892     },
6893
6894     onKey: function(f, e){
6895         if(e.getKey() === e.ENTER){
6896             this.stopEditing(true);
6897             e.stopPropagation();
6898         }
6899     },
6900
6901     onGridKey: function(e){
6902         if(e.getKey() === e.ENTER && !this.isVisible()){
6903             var r = this.grid.getSelectionModel().getSelected();
6904             if(r){
6905                 var index = this.grid.store.indexOf(r);
6906                 this.startEditing(index);
6907                 e.stopPropagation();
6908             }
6909         }
6910     },
6911
6912     ensureVisible: function(editor){
6913         if(this.isVisible()){
6914              this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
6915         }
6916     },
6917
6918     onRowClick: function(g, rowIndex, e){
6919         if(this.clicksToEdit == 'auto'){
6920             var li = this.lastClickIndex;
6921             this.lastClickIndex = rowIndex;
6922             if(li != rowIndex && !this.isVisible()){
6923                 return;
6924             }
6925         }
6926         this.startEditing(rowIndex, false);
6927         this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
6928     },
6929
6930     onRowDblClick: function(g, rowIndex, e){
6931         this.startEditing(rowIndex, false);
6932         this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
6933     },
6934
6935     onRender: function(){
6936         Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments);
6937         this.el.swallowEvent(['keydown', 'keyup', 'keypress']);
6938         this.btns = new Ext.Panel({
6939             baseCls: 'x-plain',
6940             cls: 'x-btns',
6941             elements:'body',
6942             layout: 'table',
6943             width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE
6944             items: [{
6945                 ref: 'saveBtn',
6946                 itemId: 'saveBtn',
6947                 xtype: 'button',
6948                 text: this.saveText,
6949                 width: this.minButtonWidth,
6950                 handler: this.stopEditing.createDelegate(this, [true])
6951             }, {
6952                 xtype: 'button',
6953                 text: this.cancelText,
6954                 width: this.minButtonWidth,
6955                 handler: this.stopEditing.createDelegate(this, [false])
6956             }]
6957         });
6958         this.btns.render(this.bwrap);
6959     },
6960
6961     afterRender: function(){
6962         Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
6963         this.positionButtons();
6964         if(this.monitorValid){
6965             this.startMonitoring();
6966         }
6967     },
6968
6969     onShow: function(){
6970         if(this.monitorValid){
6971             this.startMonitoring();
6972         }
6973         Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments);
6974     },
6975
6976     onHide: function(){
6977         Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments);
6978         this.stopMonitoring();
6979         this.grid.getView().focusRow(this.rowIndex);
6980     },
6981
6982     positionButtons: function(){
6983         if(this.btns){
6984             var g = this.grid,
6985                 h = this.el.dom.clientHeight,
6986                 view = g.getView(),
6987                 scroll = view.scroller.dom.scrollLeft,
6988                 bw = this.btns.getWidth(),
6989                 width = Math.min(g.getWidth(), g.getColumnModel().getTotalWidth());
6990
6991             this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2});
6992         }
6993     },
6994
6995     // private
6996     preEditValue : function(r, field){
6997         var value = r.data[field];
6998         return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
6999     },
7000
7001     // private
7002     postEditValue : function(value, originalValue, r, field){
7003         return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
7004     },
7005
7006     doFocus: function(pt){
7007         if(this.isVisible()){
7008             var index = 0,
7009                 cm = this.grid.getColumnModel(),
7010                 c,
7011                 ed;
7012             if(pt){
7013                 index = this.getTargetColumnIndex(pt);
7014             }
7015             for(var i = index||0, len = cm.getColumnCount(); i < len; i++){
7016                 c = cm.getColumnAt(i);
7017                 ed = c.getEditor();
7018                 if(!c.hidden && ed){
7019                     ed.field.focus();
7020                     break;
7021                 }
7022             }
7023         }
7024     },
7025
7026     getTargetColumnIndex: function(pt){
7027         var grid = this.grid,
7028             v = grid.view,
7029             x = pt.left,
7030             cms = grid.colModel.config,
7031             i = 0,
7032             match = false;
7033         for(var len = cms.length, c; c = cms[i]; i++){
7034             if(!c.hidden){
7035                 if(Ext.fly(v.getHeaderCell(i)).getRegion().right >= x){
7036                     match = i;
7037                     break;
7038                 }
7039             }
7040         }
7041         return match;
7042     },
7043
7044     startMonitoring : function(){
7045         if(!this.bound && this.monitorValid){
7046             this.bound = true;
7047             Ext.TaskMgr.start({
7048                 run : this.bindHandler,
7049                 interval : this.monitorPoll || 200,
7050                 scope: this
7051             });
7052         }
7053     },
7054
7055     stopMonitoring : function(){
7056         this.bound = false;
7057         if(this.tooltip){
7058             this.tooltip.hide();
7059         }
7060     },
7061
7062     isValid: function(){
7063         var valid = true;
7064         this.items.each(function(f){
7065             if(!f.isValid(true)){
7066                 valid = false;
7067                 return false;
7068             }
7069         });
7070         return valid;
7071     },
7072
7073     // private
7074     bindHandler : function(){
7075         if(!this.bound){
7076             return false; // stops binding
7077         }
7078         var valid = this.isValid();
7079         if(!valid && this.errorSummary){
7080             this.showTooltip(this.getErrorText().join(''));
7081         }
7082         this.btns.saveBtn.setDisabled(!valid);
7083         this.fireEvent('validation', this, valid);
7084     },
7085
7086     showTooltip: function(msg){
7087         var t = this.tooltip;
7088         if(!t){
7089             t = this.tooltip = new Ext.ToolTip({
7090                 maxWidth: 600,
7091                 cls: 'errorTip',
7092                 width: 300,
7093                 title: this.errorText,
7094                 autoHide: false,
7095                 anchor: 'left',
7096                 anchorToTarget: true,
7097                 mouseOffset: [40,0]
7098             });
7099         }
7100         var v = this.grid.getView(),
7101             top = parseInt(this.el.dom.style.top, 10),
7102             scroll = v.scroller.dom.scrollTop,
7103             h = this.el.getHeight();
7104
7105         if(top + h >= scroll){
7106             t.initTarget(this.items.last().getEl());
7107             if(!t.rendered){
7108                 t.show();
7109                 t.hide();
7110             }
7111             t.body.update(msg);
7112             t.doAutoWidth(20);
7113             t.show();
7114         }else if(t.rendered){
7115             t.hide();
7116         }
7117     },
7118
7119     getErrorText: function(){
7120         var data = ['<ul>'];
7121         this.items.each(function(f){
7122             if(!f.isValid(true)){
7123                 data.push('<li>', f.getActiveError(), '</li>');
7124             }
7125         });
7126         data.push('</ul>');
7127         return data;
7128     }
7129 });
7130 Ext.preg('roweditor', Ext.ux.grid.RowEditor);
7131 Ext.ns('Ext.ux.grid');\r
7132 \r
7133 /**\r
7134  * @class Ext.ux.grid.RowExpander\r
7135  * @extends Ext.util.Observable\r
7136  * Plugin (ptype = 'rowexpander') that adds the ability to have a Column in a grid which enables\r
7137  * a second row body which expands/contracts.  The expand/contract behavior is configurable to react\r
7138  * on clicking of the column, double click of the row, and/or hitting enter while a row is selected.\r
7139  *\r
7140  * @ptype rowexpander\r
7141  */\r
7142 Ext.ux.grid.RowExpander = Ext.extend(Ext.util.Observable, {\r
7143     /**\r
7144      * @cfg {Boolean} expandOnEnter\r
7145      * <tt>true</tt> to toggle selected row(s) between expanded/collapsed when the enter\r
7146      * key is pressed (defaults to <tt>true</tt>).\r
7147      */\r
7148     expandOnEnter : true,\r
7149     /**\r
7150      * @cfg {Boolean} expandOnDblClick\r
7151      * <tt>true</tt> to toggle a row between expanded/collapsed when double clicked\r
7152      * (defaults to <tt>true</tt>).\r
7153      */\r
7154     expandOnDblClick : true,\r
7155 \r
7156     header : '',\r
7157     width : 20,\r
7158     sortable : false,\r
7159     fixed : true,\r
7160     menuDisabled : true,\r
7161     dataIndex : '',\r
7162     id : 'expander',\r
7163     lazyRender : true,\r
7164     enableCaching : true,\r
7165 \r
7166     constructor: function(config){\r
7167         Ext.apply(this, config);\r
7168 \r
7169         this.addEvents({\r
7170             /**\r
7171              * @event beforeexpand\r
7172              * Fires before the row expands. Have the listener return false to prevent the row from expanding.\r
7173              * @param {Object} this RowExpander object.\r
7174              * @param {Object} Ext.data.Record Record for the selected row.\r
7175              * @param {Object} body body element for the secondary row.\r
7176              * @param {Number} rowIndex The current row index.\r
7177              */\r
7178             beforeexpand: true,\r
7179             /**\r
7180              * @event expand\r
7181              * Fires after the row expands.\r
7182              * @param {Object} this RowExpander object.\r
7183              * @param {Object} Ext.data.Record Record for the selected row.\r
7184              * @param {Object} body body element for the secondary row.\r
7185              * @param {Number} rowIndex The current row index.\r
7186              */\r
7187             expand: true,\r
7188             /**\r
7189              * @event beforecollapse\r
7190              * Fires before the row collapses. Have the listener return false to prevent the row from collapsing.\r
7191              * @param {Object} this RowExpander object.\r
7192              * @param {Object} Ext.data.Record Record for the selected row.\r
7193              * @param {Object} body body element for the secondary row.\r
7194              * @param {Number} rowIndex The current row index.\r
7195              */\r
7196             beforecollapse: true,\r
7197             /**\r
7198              * @event collapse\r
7199              * Fires after the row collapses.\r
7200              * @param {Object} this RowExpander object.\r
7201              * @param {Object} Ext.data.Record Record for the selected row.\r
7202              * @param {Object} body body element for the secondary row.\r
7203              * @param {Number} rowIndex The current row index.\r
7204              */\r
7205             collapse: true\r
7206         });\r
7207 \r
7208         Ext.ux.grid.RowExpander.superclass.constructor.call(this);\r
7209 \r
7210         if(this.tpl){\r
7211             if(typeof this.tpl == 'string'){\r
7212                 this.tpl = new Ext.Template(this.tpl);\r
7213             }\r
7214             this.tpl.compile();\r
7215         }\r
7216 \r
7217         this.state = {};\r
7218         this.bodyContent = {};\r
7219     },\r
7220 \r
7221     getRowClass : function(record, rowIndex, p, ds){\r
7222         p.cols = p.cols-1;\r
7223         var content = this.bodyContent[record.id];\r
7224         if(!content && !this.lazyRender){\r
7225             content = this.getBodyContent(record, rowIndex);\r
7226         }\r
7227         if(content){\r
7228             p.body = content;\r
7229         }\r
7230         return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';\r
7231     },\r
7232 \r
7233     init : function(grid){\r
7234         this.grid = grid;\r
7235 \r
7236         var view = grid.getView();\r
7237         view.getRowClass = this.getRowClass.createDelegate(this);\r
7238 \r
7239         view.enableRowBody = true;\r
7240 \r
7241 \r
7242         grid.on('render', this.onRender, this);\r
7243         grid.on('destroy', this.onDestroy, this);\r
7244     },\r
7245 \r
7246     // @private\r
7247     onRender: function() {\r
7248         var grid = this.grid;\r
7249         var mainBody = grid.getView().mainBody;\r
7250         mainBody.on('mousedown', this.onMouseDown, this, {delegate: '.x-grid3-row-expander'});\r
7251         if (this.expandOnEnter) {\r
7252             this.keyNav = new Ext.KeyNav(this.grid.getGridEl(), {\r
7253                 'enter' : this.onEnter,\r
7254                 scope: this\r
7255             });\r
7256         }\r
7257         if (this.expandOnDblClick) {\r
7258             grid.on('rowdblclick', this.onRowDblClick, this);\r
7259         }\r
7260     },\r
7261     \r
7262     // @private    \r
7263     onDestroy: function() {\r
7264         if(this.keyNav){\r
7265             this.keyNav.disable();\r
7266             delete this.keyNav;\r
7267         }\r
7268         /*\r
7269          * A majority of the time, the plugin will be destroyed along with the grid,\r
7270          * which means the mainBody won't be available. On the off chance that the plugin\r
7271          * isn't destroyed with the grid, take care of removing the listener.\r
7272          */\r
7273         var mainBody = this.grid.getView().mainBody;\r
7274         if(mainBody){\r
7275             mainBody.un('mousedown', this.onMouseDown, this);\r
7276         }\r
7277     },\r
7278     // @private\r
7279     onRowDblClick: function(grid, rowIdx, e) {\r
7280         this.toggleRow(rowIdx);\r
7281     },\r
7282 \r
7283     onEnter: function(e) {\r
7284         var g = this.grid;\r
7285         var sm = g.getSelectionModel();\r
7286         var sels = sm.getSelections();\r
7287         for (var i = 0, len = sels.length; i < len; i++) {\r
7288             var rowIdx = g.getStore().indexOf(sels[i]);\r
7289             this.toggleRow(rowIdx);\r
7290         }\r
7291     },\r
7292 \r
7293     getBodyContent : function(record, index){\r
7294         if(!this.enableCaching){\r
7295             return this.tpl.apply(record.data);\r
7296         }\r
7297         var content = this.bodyContent[record.id];\r
7298         if(!content){\r
7299             content = this.tpl.apply(record.data);\r
7300             this.bodyContent[record.id] = content;\r
7301         }\r
7302         return content;\r
7303     },\r
7304 \r
7305     onMouseDown : function(e, t){\r
7306         e.stopEvent();\r
7307         var row = e.getTarget('.x-grid3-row');\r
7308         this.toggleRow(row);\r
7309     },\r
7310 \r
7311     renderer : function(v, p, record){\r
7312         p.cellAttr = 'rowspan="2"';\r
7313         return '<div class="x-grid3-row-expander">&#160;</div>';\r
7314     },\r
7315 \r
7316     beforeExpand : function(record, body, rowIndex){\r
7317         if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false){\r
7318             if(this.tpl && this.lazyRender){\r
7319                 body.innerHTML = this.getBodyContent(record, rowIndex);\r
7320             }\r
7321             return true;\r
7322         }else{\r
7323             return false;\r
7324         }\r
7325     },\r
7326 \r
7327     toggleRow : function(row){\r
7328         if(typeof row == 'number'){\r
7329             row = this.grid.view.getRow(row);\r
7330         }\r
7331         this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row);\r
7332     },\r
7333 \r
7334     expandRow : function(row){\r
7335         if(typeof row == 'number'){\r
7336             row = this.grid.view.getRow(row);\r
7337         }\r
7338         var record = this.grid.store.getAt(row.rowIndex);\r
7339         var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);\r
7340         if(this.beforeExpand(record, body, row.rowIndex)){\r
7341             this.state[record.id] = true;\r
7342             Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');\r
7343             this.fireEvent('expand', this, record, body, row.rowIndex);\r
7344         }\r
7345     },\r
7346 \r
7347     collapseRow : function(row){\r
7348         if(typeof row == 'number'){\r
7349             row = this.grid.view.getRow(row);\r
7350         }\r
7351         var record = this.grid.store.getAt(row.rowIndex);\r
7352         var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);\r
7353         if(this.fireEvent('beforecollapse', this, record, body, row.rowIndex) !== false){\r
7354             this.state[record.id] = false;\r
7355             Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');\r
7356             this.fireEvent('collapse', this, record, body, row.rowIndex);\r
7357         }\r
7358     }\r
7359 });\r
7360 \r
7361 Ext.preg('rowexpander', Ext.ux.grid.RowExpander);\r
7362 \r
7363 //backwards compat\r
7364 Ext.grid.RowExpander = Ext.ux.grid.RowExpander;// We are adding these custom layouts to a namespace that does not
7365 // exist by default in Ext, so we have to add the namespace first:
7366 Ext.ns('Ext.ux.layout');
7367
7368 /**
7369  * @class Ext.ux.layout.RowLayout
7370  * @extends Ext.layout.ContainerLayout
7371  * <p>This is the layout style of choice for creating structural layouts in a multi-row format where the height of
7372  * each row can be specified as a percentage or fixed height.  Row widths can also be fixed, percentage or auto.
7373  * This class is intended to be extended or created via the layout:'ux.row' {@link Ext.Container#layout} config,
7374  * and should generally not need to be created directly via the new keyword.</p>
7375  * <p>RowLayout does not have any direct config options (other than inherited ones), but it does support a
7376  * specific config property of <b><tt>rowHeight</tt></b> that can be included in the config of any panel added to it.  The
7377  * layout will use the rowHeight (if present) or height of each panel during layout to determine how to size each panel.
7378  * If height or rowHeight is not specified for a given panel, its height will default to the panel's height (or auto).</p>
7379  * <p>The height property is always evaluated as pixels, and must be a number greater than or equal to 1.
7380  * The rowHeight property is always evaluated as a percentage, and must be a decimal value greater than 0 and
7381  * less than 1 (e.g., .25).</p>
7382  * <p>The basic rules for specifying row heights are pretty simple.  The logic makes two passes through the
7383  * set of contained panels.  During the first layout pass, all panels that either have a fixed height or none
7384  * specified (auto) are skipped, but their heights are subtracted from the overall container height.  During the second
7385  * pass, all panels with rowHeights are assigned pixel heights in proportion to their percentages based on
7386  * the total <b>remaining</b> container height.  In other words, percentage height panels are designed to fill the space
7387  * left over by all the fixed-height and/or auto-height panels.  Because of this, while you can specify any number of rows
7388  * with different percentages, the rowHeights must always add up to 1 (or 100%) when added together, otherwise your
7389  * layout may not render as expected.  Example usage:</p>
7390  * <pre><code>
7391 // All rows are percentages -- they must add up to 1
7392 var p = new Ext.Panel({
7393     title: 'Row Layout - Percentage Only',
7394     layout:'ux.row',
7395     items: [{
7396         title: 'Row 1',
7397         rowHeight: .25
7398     },{
7399         title: 'Row 2',
7400         rowHeight: .6
7401     },{
7402         title: 'Row 3',
7403         rowHeight: .15
7404     }]
7405 });
7406
7407 // Mix of height and rowHeight -- all rowHeight values must add
7408 // up to 1. The first row will take up exactly 120px, and the last two
7409 // rows will fill the remaining container height.
7410 var p = new Ext.Panel({
7411     title: 'Row Layout - Mixed',
7412     layout:'ux.row',
7413     items: [{
7414         title: 'Row 1',
7415         height: 120,
7416         // standard panel widths are still supported too:
7417         width: '50%' // or 200
7418     },{
7419         title: 'Row 2',
7420         rowHeight: .8,
7421         width: 300
7422     },{
7423         title: 'Row 3',
7424         rowHeight: .2
7425     }]
7426 });
7427 </code></pre>
7428  */
7429 Ext.ux.layout.RowLayout = Ext.extend(Ext.layout.ContainerLayout, {
7430     // private
7431     monitorResize:true,
7432
7433     type: 'row',
7434
7435     // private
7436     allowContainerRemove: false,
7437
7438     // private
7439     isValidParent : function(c, target){
7440         return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
7441     },
7442
7443     getLayoutTargetSize : function() {
7444         var target = this.container.getLayoutTarget(), ret;
7445         if (target) {
7446             ret = target.getViewSize();
7447             ret.width -= target.getPadding('lr');
7448             ret.height -= target.getPadding('tb');
7449         }
7450         return ret;
7451     },
7452
7453     renderAll : function(ct, target) {
7454         if(!this.innerCt){
7455             // the innerCt prevents wrapping and shuffling while
7456             // the container is resizing
7457             this.innerCt = target.createChild({cls:'x-column-inner'});
7458             this.innerCt.createChild({cls:'x-clear'});
7459         }
7460         Ext.layout.ColumnLayout.superclass.renderAll.call(this, ct, this.innerCt);
7461     },
7462
7463     // private
7464     onLayout : function(ct, target){
7465         var rs = ct.items.items, len = rs.length, r, i;
7466
7467         this.renderAll(ct, target);
7468
7469         var size = this.getLayoutTargetSize();
7470
7471         if(size.width < 1 && size.height < 1){ // display none?
7472             return;
7473         }
7474
7475         var h = size.height,
7476             ph = h;
7477
7478         this.innerCt.setSize({height:h});
7479
7480         // some rows can be percentages while others are fixed
7481         // so we need to make 2 passes
7482
7483         for(i = 0; i < len; i++){
7484             r = rs[i];
7485             if(!r.rowHeight){
7486                 ph -= (r.getHeight() + r.getEl().getMargins('tb'));
7487             }
7488         }
7489
7490         ph = ph < 0 ? 0 : ph;
7491
7492         for(i = 0; i < len; i++){
7493             r = rs[i];
7494             if(r.rowHeight){
7495                 r.setSize({height: Math.floor(r.rowHeight*ph) - r.getEl().getMargins('tb')});
7496             }
7497         }
7498
7499         // Browsers differ as to when they account for scrollbars.  We need to re-measure to see if the scrollbar
7500         // spaces were accounted for properly.  If not, re-layout.
7501         if (Ext.isIE) {
7502             if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) {
7503                 var ts = this.getLayoutTargetSize();
7504                 if (ts.width != size.width){
7505                     this.adjustmentPass = true;
7506                     this.layoutTargetSize = ts;
7507                     this.onLayout(ct, target);
7508                 }
7509             }
7510         }
7511         delete this.adjustmentPass;
7512     }
7513
7514     /**
7515      * @property activeItem
7516      * @hide
7517      */
7518 });
7519
7520 Ext.Container.LAYOUTS['ux.row'] = Ext.ux.layout.RowLayout;
7521 Ext.ns('Ext.ux.form');\r
7522 \r
7523 Ext.ux.form.SearchField = Ext.extend(Ext.form.TwinTriggerField, {\r
7524     initComponent : function(){\r
7525         Ext.ux.form.SearchField.superclass.initComponent.call(this);\r
7526         this.on('specialkey', function(f, e){\r
7527             if(e.getKey() == e.ENTER){\r
7528                 this.onTrigger2Click();\r
7529             }\r
7530         }, this);\r
7531     },\r
7532 \r
7533     validationEvent:false,\r
7534     validateOnBlur:false,\r
7535     trigger1Class:'x-form-clear-trigger',\r
7536     trigger2Class:'x-form-search-trigger',\r
7537     hideTrigger1:true,\r
7538     width:180,\r
7539     hasSearch : false,\r
7540     paramName : 'query',\r
7541 \r
7542     onTrigger1Click : function(){\r
7543         if(this.hasSearch){\r
7544             this.el.dom.value = '';\r
7545             var o = {start: 0};\r
7546             this.store.baseParams = this.store.baseParams || {};\r
7547             this.store.baseParams[this.paramName] = '';\r
7548             this.store.reload({params:o});\r
7549             this.triggers[0].hide();\r
7550             this.hasSearch = false;\r
7551         }\r
7552     },\r
7553 \r
7554     onTrigger2Click : function(){\r
7555         var v = this.getRawValue();\r
7556         if(v.length < 1){\r
7557             this.onTrigger1Click();\r
7558             return;\r
7559         }\r
7560         var o = {start: 0};\r
7561         this.store.baseParams = this.store.baseParams || {};\r
7562         this.store.baseParams[this.paramName] = v;\r
7563         this.store.reload({params:o});\r
7564         this.hasSearch = true;\r
7565         this.triggers[0].show();\r
7566     }\r
7567 });Ext.ns('Ext.ux.form');\r
7568 \r
7569 /**\r
7570  * @class Ext.ux.form.SelectBox\r
7571  * @extends Ext.form.ComboBox\r
7572  * <p>Makes a ComboBox more closely mimic an HTML SELECT.  Supports clicking and dragging\r
7573  * through the list, with item selection occurring when the mouse button is released.\r
7574  * When used will automatically set {@link #editable} to false and call {@link Ext.Element#unselectable}\r
7575  * on inner elements.  Re-enabling editable after calling this will NOT work.</p>\r
7576  * @author Corey Gilmore http://extjs.com/forum/showthread.php?t=6392\r
7577  * @history 2007-07-08 jvs\r
7578  * Slight mods for Ext 2.0\r
7579  * @xtype selectbox\r
7580  */\r
7581 Ext.ux.form.SelectBox = Ext.extend(Ext.form.ComboBox, {\r
7582         constructor: function(config){\r
7583                 this.searchResetDelay = 1000;\r
7584                 config = config || {};\r
7585                 config = Ext.apply(config || {}, {\r
7586                         editable: false,\r
7587                         forceSelection: true,\r
7588                         rowHeight: false,\r
7589                         lastSearchTerm: false,\r
7590                         triggerAction: 'all',\r
7591                         mode: 'local'\r
7592                 });\r
7593 \r
7594                 Ext.ux.form.SelectBox.superclass.constructor.apply(this, arguments);\r
7595 \r
7596                 this.lastSelectedIndex = this.selectedIndex || 0;\r
7597         },\r
7598 \r
7599         initEvents : function(){\r
7600                 Ext.ux.form.SelectBox.superclass.initEvents.apply(this, arguments);\r
7601                 // you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE\r
7602                 this.el.on('keydown', this.keySearch, this, true);\r
7603                 this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistory, this);\r
7604         },\r
7605 \r
7606         keySearch : function(e, target, options) {\r
7607                 var raw = e.getKey();\r
7608                 var key = String.fromCharCode(raw);\r
7609                 var startIndex = 0;\r
7610 \r
7611                 if( !this.store.getCount() ) {\r
7612                         return;\r
7613                 }\r
7614 \r
7615                 switch(raw) {\r
7616                         case Ext.EventObject.HOME:\r
7617                                 e.stopEvent();\r
7618                                 this.selectFirst();\r
7619                                 return;\r
7620 \r
7621                         case Ext.EventObject.END:\r
7622                                 e.stopEvent();\r
7623                                 this.selectLast();\r
7624                                 return;\r
7625 \r
7626                         case Ext.EventObject.PAGEDOWN:\r
7627                                 this.selectNextPage();\r
7628                                 e.stopEvent();\r
7629                                 return;\r
7630 \r
7631                         case Ext.EventObject.PAGEUP:\r
7632                                 this.selectPrevPage();\r
7633                                 e.stopEvent();\r
7634                                 return;\r
7635                 }\r
7636 \r
7637                 // skip special keys other than the shift key\r
7638                 if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) {\r
7639                         return;\r
7640                 }\r
7641                 if( this.lastSearchTerm == key ) {\r
7642                         startIndex = this.lastSelectedIndex;\r
7643                 }\r
7644                 this.search(this.displayField, key, startIndex);\r
7645                 this.cshTask.delay(this.searchResetDelay);\r
7646         },\r
7647 \r
7648         onRender : function(ct, position) {\r
7649                 this.store.on('load', this.calcRowsPerPage, this);\r
7650                 Ext.ux.form.SelectBox.superclass.onRender.apply(this, arguments);\r
7651                 if( this.mode == 'local' ) {\r
7652             this.initList();\r
7653                         this.calcRowsPerPage();\r
7654                 }\r
7655         },\r
7656 \r
7657         onSelect : function(record, index, skipCollapse){\r
7658                 if(this.fireEvent('beforeselect', this, record, index) !== false){\r
7659                         this.setValue(record.data[this.valueField || this.displayField]);\r
7660                         if( !skipCollapse ) {\r
7661                                 this.collapse();\r
7662                         }\r
7663                         this.lastSelectedIndex = index + 1;\r
7664                         this.fireEvent('select', this, record, index);\r
7665                 }\r
7666         },\r
7667 \r
7668         afterRender : function() {\r
7669                 Ext.ux.form.SelectBox.superclass.afterRender.apply(this, arguments);\r
7670                 if(Ext.isWebKit) {\r
7671                         this.el.swallowEvent('mousedown', true);\r
7672                 }\r
7673                 this.el.unselectable();\r
7674                 this.innerList.unselectable();\r
7675                 this.trigger.unselectable();\r
7676                 this.innerList.on('mouseup', function(e, target, options) {\r
7677                         if( target.id && target.id == this.innerList.id ) {\r
7678                                 return;\r
7679                         }\r
7680                         this.onViewClick();\r
7681                 }, this);\r
7682 \r
7683                 this.innerList.on('mouseover', function(e, target, options) {\r
7684                         if( target.id && target.id == this.innerList.id ) {\r
7685                                 return;\r
7686                         }\r
7687                         this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;\r
7688                         this.cshTask.delay(this.searchResetDelay);\r
7689                 }, this);\r
7690 \r
7691                 this.trigger.un('click', this.onTriggerClick, this);\r
7692                 this.trigger.on('mousedown', function(e, target, options) {\r
7693                         e.preventDefault();\r
7694                         this.onTriggerClick();\r
7695                 }, this);\r
7696 \r
7697                 this.on('collapse', function(e, target, options) {\r
7698                         Ext.getDoc().un('mouseup', this.collapseIf, this);\r
7699                 }, this, true);\r
7700 \r
7701                 this.on('expand', function(e, target, options) {\r
7702                         Ext.getDoc().on('mouseup', this.collapseIf, this);\r
7703                 }, this, true);\r
7704         },\r
7705 \r
7706         clearSearchHistory : function() {\r
7707                 this.lastSelectedIndex = 0;\r
7708                 this.lastSearchTerm = false;\r
7709         },\r
7710 \r
7711         selectFirst : function() {\r
7712                 this.focusAndSelect(this.store.data.first());\r
7713         },\r
7714 \r
7715         selectLast : function() {\r
7716                 this.focusAndSelect(this.store.data.last());\r
7717         },\r
7718 \r
7719         selectPrevPage : function() {\r
7720                 if( !this.rowHeight ) {\r
7721                         return;\r
7722                 }\r
7723                 var index = Math.max(this.selectedIndex-this.rowsPerPage, 0);\r
7724                 this.focusAndSelect(this.store.getAt(index));\r
7725         },\r
7726 \r
7727         selectNextPage : function() {\r
7728                 if( !this.rowHeight ) {\r
7729                         return;\r
7730                 }\r
7731                 var index = Math.min(this.selectedIndex+this.rowsPerPage, this.store.getCount() - 1);\r
7732                 this.focusAndSelect(this.store.getAt(index));\r
7733         },\r
7734 \r
7735         search : function(field, value, startIndex) {\r
7736                 field = field || this.displayField;\r
7737                 this.lastSearchTerm = value;\r
7738                 var index = this.store.find.apply(this.store, arguments);\r
7739                 if( index !== -1 ) {\r
7740                         this.focusAndSelect(index);\r
7741                 }\r
7742         },\r
7743 \r
7744         focusAndSelect : function(record) {\r
7745         var index = Ext.isNumber(record) ? record : this.store.indexOf(record);\r
7746         this.select(index, this.isExpanded());\r
7747         this.onSelect(this.store.getAt(index), index, this.isExpanded());\r
7748         },\r
7749 \r
7750         calcRowsPerPage : function() {\r
7751                 if( this.store.getCount() ) {\r
7752                         this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight();\r
7753                         this.rowsPerPage = this.maxHeight / this.rowHeight;\r
7754                 } else {\r
7755                         this.rowHeight = false;\r
7756                 }\r
7757         }\r
7758 \r
7759 });\r
7760 \r
7761 Ext.reg('selectbox', Ext.ux.form.SelectBox);\r
7762 \r
7763 //backwards compat\r
7764 Ext.ux.SelectBox = Ext.ux.form.SelectBox;\r
7765 /**\r
7766  * @class Ext.ux.SliderTip\r
7767  * @extends Ext.Tip\r
7768  * Simple plugin for using an Ext.Tip with a slider to show the slider value\r
7769  */\r
7770 Ext.ux.SliderTip = Ext.extend(Ext.Tip, {\r
7771     minWidth: 10,\r
7772     offsets : [0, -10],\r
7773     init : function(slider){\r
7774         slider.on('dragstart', this.onSlide, this);\r
7775         slider.on('drag', this.onSlide, this);\r
7776         slider.on('dragend', this.hide, this);\r
7777         slider.on('destroy', this.destroy, this);\r
7778     },\r
7779 \r
7780     onSlide : function(slider){\r
7781         this.show();\r
7782         this.body.update(this.getText(slider));\r
7783         this.doAutoWidth();\r
7784         this.el.alignTo(slider.thumb, 'b-t?', this.offsets);\r
7785     },\r
7786 \r
7787     getText : function(slider){\r
7788         return String(slider.getValue());\r
7789     }\r
7790 });\r
7791 Ext.ux.SlidingPager = Ext.extend(Object, {\r
7792     init : function(pbar){\r
7793         var idx = pbar.items.indexOf(pbar.inputItem);\r
7794         Ext.each(pbar.items.getRange(idx - 2, idx + 2), function(c){\r
7795             c.hide();\r
7796         });\r
7797         var slider = new Ext.Slider({\r
7798             width: 114,\r
7799             minValue: 1,\r
7800             maxValue: 1,\r
7801             plugins: new Ext.ux.SliderTip({\r
7802                 getText : function(s){\r
7803                     return String.format('Page <b>{0}</b> of <b>{1}</b>', s.value, s.maxValue);\r
7804                 }\r
7805             }),\r
7806             listeners: {\r
7807                 changecomplete: function(s, v){\r
7808                     pbar.changePage(v);\r
7809                 }\r
7810             }\r
7811         });\r
7812         pbar.insert(idx + 1, slider);\r
7813         pbar.on({\r
7814             change: function(pb, data){\r
7815                 slider.setMaxValue(data.pages);\r
7816                 slider.setValue(data.activePage);\r
7817             }\r
7818         });\r
7819     }\r
7820 });Ext.ns('Ext.ux.form');\r
7821 \r
7822 /**\r
7823  * @class Ext.ux.form.SpinnerField\r
7824  * @extends Ext.form.NumberField\r
7825  * Creates a field utilizing Ext.ux.Spinner\r
7826  * @xtype spinnerfield\r
7827  */\r
7828 Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, {\r
7829     actionMode: 'wrap',\r
7830     deferHeight: true,\r
7831     autoSize: Ext.emptyFn,\r
7832     onBlur: Ext.emptyFn,\r
7833     adjustSize: Ext.BoxComponent.prototype.adjustSize,\r
7834 \r
7835         constructor: function(config) {\r
7836                 var spinnerConfig = Ext.copyTo({}, config, 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass');\r
7837 \r
7838                 var spl = this.spinner = new Ext.ux.Spinner(spinnerConfig);\r
7839 \r
7840                 var plugins = config.plugins\r
7841                         ? (Ext.isArray(config.plugins)\r
7842                                 ? config.plugins.push(spl)\r
7843                                 : [config.plugins, spl])\r
7844                         : spl;\r
7845 \r
7846                 Ext.ux.form.SpinnerField.superclass.constructor.call(this, Ext.apply(config, {plugins: plugins}));\r
7847         },\r
7848 \r
7849     // private\r
7850     getResizeEl: function(){\r
7851         return this.wrap;\r
7852     },\r
7853 \r
7854     // private\r
7855     getPositionEl: function(){\r
7856         return this.wrap;\r
7857     },\r
7858 \r
7859     // private\r
7860     alignErrorIcon: function(){\r
7861         if (this.wrap) {\r
7862             this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);\r
7863         }\r
7864     },\r
7865 \r
7866     validateBlur: function(){\r
7867         return true;\r
7868     }\r
7869 });\r
7870 \r
7871 Ext.reg('spinnerfield', Ext.ux.form.SpinnerField);\r
7872 \r
7873 //backwards compat\r
7874 Ext.form.SpinnerField = Ext.ux.form.SpinnerField;\r
7875 /**\r
7876  * @class Ext.ux.Spinner\r
7877  * @extends Ext.util.Observable\r
7878  * Creates a Spinner control utilized by Ext.ux.form.SpinnerField\r
7879  */\r
7880 Ext.ux.Spinner = Ext.extend(Ext.util.Observable, {\r
7881     incrementValue: 1,\r
7882     alternateIncrementValue: 5,\r
7883     triggerClass: 'x-form-spinner-trigger',\r
7884     splitterClass: 'x-form-spinner-splitter',\r
7885     alternateKey: Ext.EventObject.shiftKey,\r
7886     defaultValue: 0,\r
7887     accelerate: false,\r
7888 \r
7889     constructor: function(config){\r
7890         Ext.ux.Spinner.superclass.constructor.call(this, config);\r
7891         Ext.apply(this, config);\r
7892         this.mimicing = false;\r
7893     },\r
7894 \r
7895     init: function(field){\r
7896         this.field = field;\r
7897 \r
7898         field.afterMethod('onRender', this.doRender, this);\r
7899         field.afterMethod('onEnable', this.doEnable, this);\r
7900         field.afterMethod('onDisable', this.doDisable, this);\r
7901         field.afterMethod('afterRender', this.doAfterRender, this);\r
7902         field.afterMethod('onResize', this.doResize, this);\r
7903         field.afterMethod('onFocus', this.doFocus, this);\r
7904         field.beforeMethod('onDestroy', this.doDestroy, this);\r
7905     },\r
7906 \r
7907     doRender: function(ct, position){\r
7908         var el = this.el = this.field.getEl();\r
7909         var f = this.field;\r
7910 \r
7911         if (!f.wrap) {\r
7912             f.wrap = this.wrap = el.wrap({\r
7913                 cls: "x-form-field-wrap"\r
7914             });\r
7915         }\r
7916         else {\r
7917             this.wrap = f.wrap.addClass('x-form-field-wrap');\r
7918         }\r
7919 \r
7920         this.trigger = this.wrap.createChild({\r
7921             tag: "img",\r
7922             src: Ext.BLANK_IMAGE_URL,\r
7923             cls: "x-form-trigger " + this.triggerClass\r
7924         });\r
7925 \r
7926         if (!f.width) {\r
7927             this.wrap.setWidth(el.getWidth() + this.trigger.getWidth());\r
7928         }\r
7929 \r
7930         this.splitter = this.wrap.createChild({\r
7931             tag: 'div',\r
7932             cls: this.splitterClass,\r
7933             style: 'width:13px; height:2px;'\r
7934         });\r
7935         this.splitter.setRight((Ext.isIE) ? 1 : 2).setTop(10).show();\r
7936 \r
7937         this.proxy = this.trigger.createProxy('', this.splitter, true);\r
7938         this.proxy.addClass("x-form-spinner-proxy");\r
7939         this.proxy.setStyle('left', '0px');\r
7940         this.proxy.setSize(14, 1);\r
7941         this.proxy.hide();\r
7942         this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", {\r
7943             dragElId: this.proxy.id\r
7944         });\r
7945 \r
7946         this.initTrigger();\r
7947         this.initSpinner();\r
7948     },\r
7949 \r
7950     doAfterRender: function(){\r
7951         var y;\r
7952         if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) {\r
7953             this.el.position();\r
7954             this.el.setY(y);\r
7955         }\r
7956     },\r
7957 \r
7958     doEnable: function(){\r
7959         if (this.wrap) {\r
7960             this.wrap.removeClass(this.field.disabledClass);\r
7961         }\r
7962     },\r
7963 \r
7964     doDisable: function(){\r
7965         if (this.wrap) {\r
7966             this.wrap.addClass(this.field.disabledClass);\r
7967             this.el.removeClass(this.field.disabledClass);\r
7968         }\r
7969     },\r
7970 \r
7971     doResize: function(w, h){\r
7972         if (typeof w == 'number') {\r
7973             this.el.setWidth(w - this.trigger.getWidth());\r
7974         }\r
7975         this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth());\r
7976     },\r
7977 \r
7978     doFocus: function(){\r
7979         if (!this.mimicing) {\r
7980             this.wrap.addClass('x-trigger-wrap-focus');\r
7981             this.mimicing = true;\r
7982             Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {\r
7983                 delay: 10\r
7984             });\r
7985             this.el.on('keydown', this.checkTab, this);\r
7986         }\r
7987     },\r
7988 \r
7989     // private\r
7990     checkTab: function(e){\r
7991         if (e.getKey() == e.TAB) {\r
7992             this.triggerBlur();\r
7993         }\r
7994     },\r
7995 \r
7996     // private\r
7997     mimicBlur: function(e){\r
7998         if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) {\r
7999             this.triggerBlur();\r
8000         }\r
8001     },\r
8002 \r
8003     // private\r
8004     triggerBlur: function(){\r
8005         this.mimicing = false;\r
8006         Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);\r
8007         this.el.un("keydown", this.checkTab, this);\r
8008         this.field.beforeBlur();\r
8009         this.wrap.removeClass('x-trigger-wrap-focus');\r
8010         this.field.onBlur.call(this.field);\r
8011     },\r
8012 \r
8013     initTrigger: function(){\r
8014         this.trigger.addClassOnOver('x-form-trigger-over');\r
8015         this.trigger.addClassOnClick('x-form-trigger-click');\r
8016     },\r
8017 \r
8018     initSpinner: function(){\r
8019         this.field.addEvents({\r
8020             'spin': true,\r
8021             'spinup': true,\r
8022             'spindown': true\r
8023         });\r
8024 \r
8025         this.keyNav = new Ext.KeyNav(this.el, {\r
8026             "up": function(e){\r
8027                 e.preventDefault();\r
8028                 this.onSpinUp();\r
8029             },\r
8030 \r
8031             "down": function(e){\r
8032                 e.preventDefault();\r
8033                 this.onSpinDown();\r
8034             },\r
8035 \r
8036             "pageUp": function(e){\r
8037                 e.preventDefault();\r
8038                 this.onSpinUpAlternate();\r
8039             },\r
8040 \r
8041             "pageDown": function(e){\r
8042                 e.preventDefault();\r
8043                 this.onSpinDownAlternate();\r
8044             },\r
8045 \r
8046             scope: this\r
8047         });\r
8048 \r
8049         this.repeater = new Ext.util.ClickRepeater(this.trigger, {\r
8050             accelerate: this.accelerate\r
8051         });\r
8052         this.field.mon(this.repeater, "click", this.onTriggerClick, this, {\r
8053             preventDefault: true\r
8054         });\r
8055 \r
8056         this.field.mon(this.trigger, {\r
8057             mouseover: this.onMouseOver,\r
8058             mouseout: this.onMouseOut,\r
8059             mousemove: this.onMouseMove,\r
8060             mousedown: this.onMouseDown,\r
8061             mouseup: this.onMouseUp,\r
8062             scope: this,\r
8063             preventDefault: true\r
8064         });\r
8065 \r
8066         this.field.mon(this.wrap, "mousewheel", this.handleMouseWheel, this);\r
8067 \r
8068         this.dd.setXConstraint(0, 0, 10)\r
8069         this.dd.setYConstraint(1500, 1500, 10);\r
8070         this.dd.endDrag = this.endDrag.createDelegate(this);\r
8071         this.dd.startDrag = this.startDrag.createDelegate(this);\r
8072         this.dd.onDrag = this.onDrag.createDelegate(this);\r
8073     },\r
8074 \r
8075     onMouseOver: function(){\r
8076         if (this.disabled) {\r
8077             return;\r
8078         }\r
8079         var middle = this.getMiddle();\r
8080         this.tmpHoverClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown';\r
8081         this.trigger.addClass(this.tmpHoverClass);\r
8082     },\r
8083 \r
8084     //private\r
8085     onMouseOut: function(){\r
8086         this.trigger.removeClass(this.tmpHoverClass);\r
8087     },\r
8088 \r
8089     //private\r
8090     onMouseMove: function(){\r
8091         if (this.disabled) {\r
8092             return;\r
8093         }\r
8094         var middle = this.getMiddle();\r
8095         if (((Ext.EventObject.getPageY() > middle) && this.tmpHoverClass == "x-form-spinner-overup") ||\r
8096         ((Ext.EventObject.getPageY() < middle) && this.tmpHoverClass == "x-form-spinner-overdown")) {\r
8097         }\r
8098     },\r
8099 \r
8100     //private\r
8101     onMouseDown: function(){\r
8102         if (this.disabled) {\r
8103             return;\r
8104         }\r
8105         var middle = this.getMiddle();\r
8106         this.tmpClickClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown';\r
8107         this.trigger.addClass(this.tmpClickClass);\r
8108     },\r
8109 \r
8110     //private\r
8111     onMouseUp: function(){\r
8112         this.trigger.removeClass(this.tmpClickClass);\r
8113     },\r
8114 \r
8115     //private\r
8116     onTriggerClick: function(){\r
8117         if (this.disabled || this.el.dom.readOnly) {\r
8118             return;\r
8119         }\r
8120         var middle = this.getMiddle();\r
8121         var ud = (Ext.EventObject.getPageY() < middle) ? 'Up' : 'Down';\r
8122         this['onSpin' + ud]();\r
8123     },\r
8124 \r
8125     //private\r
8126     getMiddle: function(){\r
8127         var t = this.trigger.getTop();\r
8128         var h = this.trigger.getHeight();\r
8129         var middle = t + (h / 2);\r
8130         return middle;\r
8131     },\r
8132 \r
8133     //private\r
8134     //checks if control is allowed to spin\r
8135     isSpinnable: function(){\r
8136         if (this.disabled || this.el.dom.readOnly) {\r
8137             Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly\r
8138             return false;\r
8139         }\r
8140         return true;\r
8141     },\r
8142 \r
8143     handleMouseWheel: function(e){\r
8144         //disable scrolling when not focused\r
8145         if (this.wrap.hasClass('x-trigger-wrap-focus') == false) {\r
8146             return;\r
8147         }\r
8148 \r
8149         var delta = e.getWheelDelta();\r
8150         if (delta > 0) {\r
8151             this.onSpinUp();\r
8152             e.stopEvent();\r
8153         }\r
8154         else\r
8155             if (delta < 0) {\r
8156                 this.onSpinDown();\r
8157                 e.stopEvent();\r
8158             }\r
8159     },\r
8160 \r
8161     //private\r
8162     startDrag: function(){\r
8163         this.proxy.show();\r
8164         this._previousY = Ext.fly(this.dd.getDragEl()).getTop();\r
8165     },\r
8166 \r
8167     //private\r
8168     endDrag: function(){\r
8169         this.proxy.hide();\r
8170     },\r
8171 \r
8172     //private\r
8173     onDrag: function(){\r
8174         if (this.disabled) {\r
8175             return;\r
8176         }\r
8177         var y = Ext.fly(this.dd.getDragEl()).getTop();\r
8178         var ud = '';\r
8179 \r
8180         if (this._previousY > y) {\r
8181             ud = 'Up';\r
8182         } //up\r
8183         if (this._previousY < y) {\r
8184             ud = 'Down';\r
8185         } //down\r
8186         if (ud != '') {\r
8187             this['onSpin' + ud]();\r
8188         }\r
8189 \r
8190         this._previousY = y;\r
8191     },\r
8192 \r
8193     //private\r
8194     onSpinUp: function(){\r
8195         if (this.isSpinnable() == false) {\r
8196             return;\r
8197         }\r
8198         if (Ext.EventObject.shiftKey == true) {\r
8199             this.onSpinUpAlternate();\r
8200             return;\r
8201         }\r
8202         else {\r
8203             this.spin(false, false);\r
8204         }\r
8205         this.field.fireEvent("spin", this);\r
8206         this.field.fireEvent("spinup", this);\r
8207     },\r
8208 \r
8209     //private\r
8210     onSpinDown: function(){\r
8211         if (this.isSpinnable() == false) {\r
8212             return;\r
8213         }\r
8214         if (Ext.EventObject.shiftKey == true) {\r
8215             this.onSpinDownAlternate();\r
8216             return;\r
8217         }\r
8218         else {\r
8219             this.spin(true, false);\r
8220         }\r
8221         this.field.fireEvent("spin", this);\r
8222         this.field.fireEvent("spindown", this);\r
8223     },\r
8224 \r
8225     //private\r
8226     onSpinUpAlternate: function(){\r
8227         if (this.isSpinnable() == false) {\r
8228             return;\r
8229         }\r
8230         this.spin(false, true);\r
8231         this.field.fireEvent("spin", this);\r
8232         this.field.fireEvent("spinup", this);\r
8233     },\r
8234 \r
8235     //private\r
8236     onSpinDownAlternate: function(){\r
8237         if (this.isSpinnable() == false) {\r
8238             return;\r
8239         }\r
8240         this.spin(true, true);\r
8241         this.field.fireEvent("spin", this);\r
8242         this.field.fireEvent("spindown", this);\r
8243     },\r
8244 \r
8245     spin: function(down, alternate){\r
8246         var v = parseFloat(this.field.getValue());\r
8247         var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;\r
8248         (down == true) ? v -= incr : v += incr;\r
8249 \r
8250         v = (isNaN(v)) ? this.defaultValue : v;\r
8251         v = this.fixBoundries(v);\r
8252         this.field.setRawValue(v);\r
8253     },\r
8254 \r
8255     fixBoundries: function(value){\r
8256         var v = value;\r
8257 \r
8258         if (this.field.minValue != undefined && v < this.field.minValue) {\r
8259             v = this.field.minValue;\r
8260         }\r
8261         if (this.field.maxValue != undefined && v > this.field.maxValue) {\r
8262             v = this.field.maxValue;\r
8263         }\r
8264 \r
8265         return this.fixPrecision(v);\r
8266     },\r
8267 \r
8268     // private\r
8269     fixPrecision: function(value){\r
8270         var nan = isNaN(value);\r
8271         if (!this.field.allowDecimals || this.field.decimalPrecision == -1 || nan || !value) {\r
8272             return nan ? '' : value;\r
8273         }\r
8274         return parseFloat(parseFloat(value).toFixed(this.field.decimalPrecision));\r
8275     },\r
8276 \r
8277     doDestroy: function(){\r
8278         if (this.trigger) {\r
8279             this.trigger.remove();\r
8280         }\r
8281         if (this.wrap) {\r
8282             this.wrap.remove();\r
8283             delete this.field.wrap;\r
8284         }\r
8285 \r
8286         if (this.splitter) {\r
8287             this.splitter.remove();\r
8288         }\r
8289 \r
8290         if (this.dd) {\r
8291             this.dd.unreg();\r
8292             this.dd = null;\r
8293         }\r
8294 \r
8295         if (this.proxy) {\r
8296             this.proxy.remove();\r
8297         }\r
8298 \r
8299         if (this.repeater) {\r
8300             this.repeater.purgeListeners();\r
8301         }\r
8302     }\r
8303 });\r
8304 \r
8305 //backwards compat\r
8306 Ext.form.Spinner = Ext.ux.Spinner;Ext.ux.Spotlight = function(config){\r
8307     Ext.apply(this, config);\r
8308 }\r
8309 Ext.ux.Spotlight.prototype = {\r
8310     active : false,\r
8311     animate : true,\r
8312     duration: .25,\r
8313     easing:'easeNone',\r
8314 \r
8315     // private\r
8316     animated : false,\r
8317 \r
8318     createElements : function(){\r
8319         var bd = Ext.getBody();\r
8320 \r
8321         this.right = bd.createChild({cls:'x-spotlight'});\r
8322         this.left = bd.createChild({cls:'x-spotlight'});\r
8323         this.top = bd.createChild({cls:'x-spotlight'});\r
8324         this.bottom = bd.createChild({cls:'x-spotlight'});\r
8325 \r
8326         this.all = new Ext.CompositeElement([this.right, this.left, this.top, this.bottom]);\r
8327     },\r
8328 \r
8329     show : function(el, callback, scope){\r
8330         if(this.animated){\r
8331             this.show.defer(50, this, [el, callback, scope]);\r
8332             return;\r
8333         }\r
8334         this.el = Ext.get(el);\r
8335         if(!this.right){\r
8336             this.createElements();\r
8337         }\r
8338         if(!this.active){\r
8339             this.all.setDisplayed('');\r
8340             this.applyBounds(true, false);\r
8341             this.active = true;\r
8342             Ext.EventManager.onWindowResize(this.syncSize, this);\r
8343             this.applyBounds(false, this.animate, false, callback, scope);\r
8344         }else{\r
8345             this.applyBounds(false, false, false, callback, scope); // all these booleans look hideous\r
8346         }\r
8347     },\r
8348 \r
8349     hide : function(callback, scope){\r
8350         if(this.animated){\r
8351             this.hide.defer(50, this, [callback, scope]);\r
8352             return;\r
8353         }\r
8354         Ext.EventManager.removeResizeListener(this.syncSize, this);\r
8355         this.applyBounds(true, this.animate, true, callback, scope);\r
8356     },\r
8357 \r
8358     doHide : function(){\r
8359         this.active = false;\r
8360         this.all.setDisplayed(false);\r
8361     },\r
8362 \r
8363     syncSize : function(){\r
8364         this.applyBounds(false, false);\r
8365     },\r
8366 \r
8367     applyBounds : function(basePts, anim, doHide, callback, scope){\r
8368 \r
8369         var rg = this.el.getRegion();\r
8370 \r
8371         var dw = Ext.lib.Dom.getViewWidth(true);\r
8372         var dh = Ext.lib.Dom.getViewHeight(true);\r
8373 \r
8374         var c = 0, cb = false;\r
8375         if(anim){\r
8376             cb = {\r
8377                 callback: function(){\r
8378                     c++;\r
8379                     if(c == 4){\r
8380                         this.animated = false;\r
8381                         if(doHide){\r
8382                             this.doHide();\r
8383                         }\r
8384                         Ext.callback(callback, scope, [this]);\r
8385                     }\r
8386                 },\r
8387                 scope: this,\r
8388                 duration: this.duration,\r
8389                 easing: this.easing\r
8390             };\r
8391             this.animated = true;\r
8392         }\r
8393 \r
8394         this.right.setBounds(\r
8395                 rg.right,\r
8396                 basePts ? dh : rg.top,\r
8397                 dw - rg.right,\r
8398                 basePts ? 0 : (dh - rg.top),\r
8399                 cb);\r
8400 \r
8401         this.left.setBounds(\r
8402                 0,\r
8403                 0,\r
8404                 rg.left,\r
8405                 basePts ? 0 : rg.bottom,\r
8406                 cb);\r
8407 \r
8408         this.top.setBounds(\r
8409                 basePts ? dw : rg.left,\r
8410                 0,\r
8411                 basePts ? 0 : dw - rg.left,\r
8412                 rg.top,\r
8413                 cb);\r
8414 \r
8415         this.bottom.setBounds(\r
8416                 0,\r
8417                 rg.bottom,\r
8418                 basePts ? 0 : rg.right,\r
8419                 dh - rg.bottom,\r
8420                 cb);\r
8421 \r
8422         if(!anim){\r
8423             if(doHide){\r
8424                 this.doHide();\r
8425             }\r
8426             if(callback){\r
8427                 Ext.callback(callback, scope, [this]);\r
8428             }\r
8429         }\r
8430     },\r
8431 \r
8432     destroy : function(){\r
8433         this.doHide();\r
8434         Ext.destroy(\r
8435             this.right,\r
8436             this.left,\r
8437             this.top,\r
8438             this.bottom);\r
8439         delete this.el;\r
8440         delete this.all;\r
8441     }\r
8442 };\r
8443 \r
8444 //backwards compat\r
8445 Ext.Spotlight = Ext.ux.Spotlight;/**
8446  * @class Ext.ux.StatusBar
8447  * <p>Basic status bar component that can be used as the bottom toolbar of any {@link Ext.Panel}.  In addition to
8448  * supporting the standard {@link Ext.Toolbar} interface for adding buttons, menus and other items, the StatusBar
8449  * provides a greedy status element that can be aligned to either side and has convenient methods for setting the
8450  * status text and icon.  You can also indicate that something is processing using the {@link #showBusy} method.</p>
8451  * <pre><code>
8452 new Ext.Panel({
8453     title: 'StatusBar',
8454     // etc.
8455     bbar: new Ext.ux.StatusBar({
8456         id: 'my-status',
8457
8458         // defaults to use when the status is cleared:
8459         defaultText: 'Default status text',
8460         defaultIconCls: 'default-icon',
8461
8462         // values to set initially:
8463         text: 'Ready',
8464         iconCls: 'ready-icon',
8465
8466         // any standard Toolbar items:
8467         items: [{
8468             text: 'A Button'
8469         }, '-', 'Plain Text']
8470     })
8471 });
8472
8473 // Update the status bar later in code:
8474 var sb = Ext.getCmp('my-status');
8475 sb.setStatus({
8476     text: 'OK',
8477     iconCls: 'ok-icon',
8478     clear: true // auto-clear after a set interval
8479 });
8480
8481 // Set the status bar to show that something is processing:
8482 sb.showBusy();
8483
8484 // processing....
8485
8486 sb.clearStatus(); // once completeed
8487 </code></pre>
8488  * @extends Ext.Toolbar
8489  * @constructor
8490  * Creates a new StatusBar
8491  * @param {Object/Array} config A config object
8492  */
8493 Ext.ux.StatusBar = Ext.extend(Ext.Toolbar, {
8494     /**
8495      * @cfg {String} statusAlign
8496      * The alignment of the status element within the overall StatusBar layout.  When the StatusBar is rendered,
8497      * it creates an internal div containing the status text and icon.  Any additional Toolbar items added in the
8498      * StatusBar's {@link #items} config, or added via {@link #add} or any of the supported add* methods, will be
8499      * rendered, in added order, to the opposite side.  The status element is greedy, so it will automatically
8500      * expand to take up all sapce left over by any other items.  Example usage:
8501      * <pre><code>
8502 // Create a left-aligned status bar containing a button,
8503 // separator and text item that will be right-aligned (default):
8504 new Ext.Panel({
8505     title: 'StatusBar',
8506     // etc.
8507     bbar: new Ext.ux.StatusBar({
8508         defaultText: 'Default status text',
8509         id: 'status-id',
8510         items: [{
8511             text: 'A Button'
8512         }, '-', 'Plain Text']
8513     })
8514 });
8515
8516 // By adding the statusAlign config, this will create the
8517 // exact same toolbar, except the status and toolbar item
8518 // layout will be reversed from the previous example:
8519 new Ext.Panel({
8520     title: 'StatusBar',
8521     // etc.
8522     bbar: new Ext.ux.StatusBar({
8523         defaultText: 'Default status text',
8524         id: 'status-id',
8525         statusAlign: 'right',
8526         items: [{
8527             text: 'A Button'
8528         }, '-', 'Plain Text']
8529     })
8530 });
8531 </code></pre>
8532      */
8533     /**
8534      * @cfg {String} defaultText
8535      * The default {@link #text} value.  This will be used anytime the status bar is cleared with the
8536      * <tt>useDefaults:true</tt> option (defaults to '').
8537      */
8538     /**
8539      * @cfg {String} defaultIconCls
8540      * The default {@link #iconCls} value (see the iconCls docs for additional details about customizing the icon).
8541      * This will be used anytime the status bar is cleared with the <tt>useDefaults:true</tt> option (defaults to '').
8542      */
8543     /**
8544      * @cfg {String} text
8545      * A string that will be <b>initially</b> set as the status message.  This string
8546      * will be set as innerHTML (html tags are accepted) for the toolbar item.
8547      * If not specified, the value set for <code>{@link #defaultText}</code>
8548      * will be used.
8549      */
8550     /**
8551      * @cfg {String} iconCls
8552      * A CSS class that will be <b>initially</b> set as the status bar icon and is
8553      * expected to provide a background image (defaults to '').
8554      * Example usage:<pre><code>
8555 // Example CSS rule:
8556 .x-statusbar .x-status-custom {
8557     padding-left: 25px;
8558     background: transparent url(images/custom-icon.gif) no-repeat 3px 2px;
8559 }
8560
8561 // Setting a default icon:
8562 var sb = new Ext.ux.StatusBar({
8563     defaultIconCls: 'x-status-custom'
8564 });
8565
8566 // Changing the icon:
8567 sb.setStatus({
8568     text: 'New status',
8569     iconCls: 'x-status-custom'
8570 });
8571 </code></pre>
8572      */
8573
8574     /**
8575      * @cfg {String} cls
8576      * The base class applied to the containing element for this component on render (defaults to 'x-statusbar')
8577      */
8578     cls : 'x-statusbar',
8579     /**
8580      * @cfg {String} busyIconCls
8581      * The default <code>{@link #iconCls}</code> applied when calling
8582      * <code>{@link #showBusy}</code> (defaults to <tt>'x-status-busy'</tt>).
8583      * It can be overridden at any time by passing the <code>iconCls</code>
8584      * argument into <code>{@link #showBusy}</code>.
8585      */
8586     busyIconCls : 'x-status-busy',
8587     /**
8588      * @cfg {String} busyText
8589      * The default <code>{@link #text}</code> applied when calling
8590      * <code>{@link #showBusy}</code> (defaults to <tt>'Loading...'</tt>).
8591      * It can be overridden at any time by passing the <code>text</code>
8592      * argument into <code>{@link #showBusy}</code>.
8593      */
8594     busyText : 'Loading...',
8595     /**
8596      * @cfg {Number} autoClear
8597      * The number of milliseconds to wait after setting the status via
8598      * <code>{@link #setStatus}</code> before automatically clearing the status
8599      * text and icon (defaults to <tt>5000</tt>).  Note that this only applies
8600      * when passing the <tt>clear</tt> argument to <code>{@link #setStatus}</code>
8601      * since that is the only way to defer clearing the status.  This can
8602      * be overridden by specifying a different <tt>wait</tt> value in
8603      * <code>{@link #setStatus}</code>. Calls to <code>{@link #clearStatus}</code>
8604      * always clear the status bar immediately and ignore this value.
8605      */
8606     autoClear : 5000,
8607
8608     /**
8609      * @cfg {String} emptyText
8610      * The text string to use if no text has been set.  Defaults to
8611      * <tt>'&nbsp;'</tt>).  If there are no other items in the toolbar using
8612      * an empty string (<tt>''</tt>) for this value would end up in the toolbar
8613      * height collapsing since the empty string will not maintain the toolbar
8614      * height.  Use <tt>''</tt> if the toolbar should collapse in height
8615      * vertically when no text is specified and there are no other items in
8616      * the toolbar.
8617      */
8618     emptyText : '&nbsp;',
8619
8620     // private
8621     activeThreadId : 0,
8622
8623     // private
8624     initComponent : function(){
8625         if(this.statusAlign=='right'){
8626             this.cls += ' x-status-right';
8627         }
8628         Ext.ux.StatusBar.superclass.initComponent.call(this);
8629     },
8630
8631     // private
8632     afterRender : function(){
8633         Ext.ux.StatusBar.superclass.afterRender.call(this);
8634
8635         var right = this.statusAlign == 'right';
8636         this.currIconCls = this.iconCls || this.defaultIconCls;
8637         this.statusEl = new Ext.Toolbar.TextItem({
8638             cls: 'x-status-text ' + (this.currIconCls || ''),
8639             text: this.text || this.defaultText || ''
8640         });
8641
8642         if(right){
8643             this.add('->');
8644             this.add(this.statusEl);
8645         }else{
8646             this.insert(0, this.statusEl);
8647             this.insert(1, '->');
8648         }
8649         this.doLayout();
8650     },
8651
8652     /**
8653      * Sets the status {@link #text} and/or {@link #iconCls}. Also supports automatically clearing the
8654      * status that was set after a specified interval.
8655      * @param {Object/String} config A config object specifying what status to set, or a string assumed
8656      * to be the status text (and all other options are defaulted as explained below). A config
8657      * object containing any or all of the following properties can be passed:<ul>
8658      * <li><tt>text</tt> {String} : (optional) The status text to display.  If not specified, any current
8659      * status text will remain unchanged.</li>
8660      * <li><tt>iconCls</tt> {String} : (optional) The CSS class used to customize the status icon (see
8661      * {@link #iconCls} for details). If not specified, any current iconCls will remain unchanged.</li>
8662      * <li><tt>clear</tt> {Boolean/Number/Object} : (optional) Allows you to set an internal callback that will
8663      * automatically clear the status text and iconCls after a specified amount of time has passed. If clear is not
8664      * specified, the new status will not be auto-cleared and will stay until updated again or cleared using
8665      * {@link #clearStatus}. If <tt>true</tt> is passed, the status will be cleared using {@link #autoClear},
8666      * {@link #defaultText} and {@link #defaultIconCls} via a fade out animation. If a numeric value is passed,
8667      * it will be used as the callback interval (in milliseconds), overriding the {@link #autoClear} value.
8668      * All other options will be defaulted as with the boolean option.  To customize any other options,
8669      * you can pass an object in the format:<ul>
8670      *    <li><tt>wait</tt> {Number} : (optional) The number of milliseconds to wait before clearing
8671      *    (defaults to {@link #autoClear}).</li>
8672      *    <li><tt>anim</tt> {Number} : (optional) False to clear the status immediately once the callback
8673      *    executes (defaults to true which fades the status out).</li>
8674      *    <li><tt>useDefaults</tt> {Number} : (optional) False to completely clear the status text and iconCls
8675      *    (defaults to true which uses {@link #defaultText} and {@link #defaultIconCls}).</li>
8676      * </ul></li></ul>
8677      * Example usage:<pre><code>
8678 // Simple call to update the text
8679 statusBar.setStatus('New status');
8680
8681 // Set the status and icon, auto-clearing with default options:
8682 statusBar.setStatus({
8683     text: 'New status',
8684     iconCls: 'x-status-custom',
8685     clear: true
8686 });
8687
8688 // Auto-clear with custom options:
8689 statusBar.setStatus({
8690     text: 'New status',
8691     iconCls: 'x-status-custom',
8692     clear: {
8693         wait: 8000,
8694         anim: false,
8695         useDefaults: false
8696     }
8697 });
8698 </code></pre>
8699      * @return {Ext.ux.StatusBar} this
8700      */
8701     setStatus : function(o){
8702         o = o || {};
8703
8704         if(typeof o == 'string'){
8705             o = {text:o};
8706         }
8707         if(o.text !== undefined){
8708             this.setText(o.text);
8709         }
8710         if(o.iconCls !== undefined){
8711             this.setIcon(o.iconCls);
8712         }
8713
8714         if(o.clear){
8715             var c = o.clear,
8716                 wait = this.autoClear,
8717                 defaults = {useDefaults: true, anim: true};
8718
8719             if(typeof c == 'object'){
8720                 c = Ext.applyIf(c, defaults);
8721                 if(c.wait){
8722                     wait = c.wait;
8723                 }
8724             }else if(typeof c == 'number'){
8725                 wait = c;
8726                 c = defaults;
8727             }else if(typeof c == 'boolean'){
8728                 c = defaults;
8729             }
8730
8731             c.threadId = this.activeThreadId;
8732             this.clearStatus.defer(wait, this, [c]);
8733         }
8734         return this;
8735     },
8736
8737     /**
8738      * Clears the status {@link #text} and {@link #iconCls}. Also supports clearing via an optional fade out animation.
8739      * @param {Object} config (optional) A config object containing any or all of the following properties.  If this
8740      * object is not specified the status will be cleared using the defaults below:<ul>
8741      * <li><tt>anim</tt> {Boolean} : (optional) True to clear the status by fading out the status element (defaults
8742      * to false which clears immediately).</li>
8743      * <li><tt>useDefaults</tt> {Boolean} : (optional) True to reset the text and icon using {@link #defaultText} and
8744      * {@link #defaultIconCls} (defaults to false which sets the text to '' and removes any existing icon class).</li>
8745      * </ul>
8746      * @return {Ext.ux.StatusBar} this
8747      */
8748     clearStatus : function(o){
8749         o = o || {};
8750
8751         if(o.threadId && o.threadId !== this.activeThreadId){
8752             // this means the current call was made internally, but a newer
8753             // thread has set a message since this call was deferred.  Since
8754             // we don't want to overwrite a newer message just ignore.
8755             return this;
8756         }
8757
8758         var text = o.useDefaults ? this.defaultText : this.emptyText,
8759             iconCls = o.useDefaults ? (this.defaultIconCls ? this.defaultIconCls : '') : '';
8760
8761         if(o.anim){
8762             // animate the statusEl Ext.Element
8763             this.statusEl.el.fadeOut({
8764                 remove: false,
8765                 useDisplay: true,
8766                 scope: this,
8767                 callback: function(){
8768                     this.setStatus({
8769                             text: text,
8770                             iconCls: iconCls
8771                         });
8772
8773                     this.statusEl.el.show();
8774                 }
8775             });
8776         }else{
8777             // hide/show the el to avoid jumpy text or icon
8778             this.statusEl.hide();
8779                 this.setStatus({
8780                     text: text,
8781                     iconCls: iconCls
8782                 });
8783             this.statusEl.show();
8784         }
8785         return this;
8786     },
8787
8788     /**
8789      * Convenience method for setting the status text directly.  For more flexible options see {@link #setStatus}.
8790      * @param {String} text (optional) The text to set (defaults to '')
8791      * @return {Ext.ux.StatusBar} this
8792      */
8793     setText : function(text){
8794         this.activeThreadId++;
8795         this.text = text || '';
8796         if(this.rendered){
8797             this.statusEl.setText(this.text);
8798         }
8799         return this;
8800     },
8801
8802     /**
8803      * Returns the current status text.
8804      * @return {String} The status text
8805      */
8806     getText : function(){
8807         return this.text;
8808     },
8809
8810     /**
8811      * Convenience method for setting the status icon directly.  For more flexible options see {@link #setStatus}.
8812      * See {@link #iconCls} for complete details about customizing the icon.
8813      * @param {String} iconCls (optional) The icon class to set (defaults to '', and any current icon class is removed)
8814      * @return {Ext.ux.StatusBar} this
8815      */
8816     setIcon : function(cls){
8817         this.activeThreadId++;
8818         cls = cls || '';
8819
8820         if(this.rendered){
8821                 if(this.currIconCls){
8822                     this.statusEl.removeClass(this.currIconCls);
8823                     this.currIconCls = null;
8824                 }
8825                 if(cls.length > 0){
8826                     this.statusEl.addClass(cls);
8827                     this.currIconCls = cls;
8828                 }
8829         }else{
8830             this.currIconCls = cls;
8831         }
8832         return this;
8833     },
8834
8835     /**
8836      * Convenience method for setting the status text and icon to special values that are pre-configured to indicate
8837      * a "busy" state, usually for loading or processing activities.
8838      * @param {Object/String} config (optional) A config object in the same format supported by {@link #setStatus}, or a
8839      * string to use as the status text (in which case all other options for setStatus will be defaulted).  Use the
8840      * <tt>text</tt> and/or <tt>iconCls</tt> properties on the config to override the default {@link #busyText}
8841      * and {@link #busyIconCls} settings. If the config argument is not specified, {@link #busyText} and
8842      * {@link #busyIconCls} will be used in conjunction with all of the default options for {@link #setStatus}.
8843      * @return {Ext.ux.StatusBar} this
8844      */
8845     showBusy : function(o){
8846         if(typeof o == 'string'){
8847             o = {text:o};
8848         }
8849         o = Ext.applyIf(o || {}, {
8850             text: this.busyText,
8851             iconCls: this.busyIconCls
8852         });
8853         return this.setStatus(o);
8854     }
8855 });
8856 Ext.reg('statusbar', Ext.ux.StatusBar);
8857 /**\r
8858  * @class Ext.ux.TabCloseMenu\r
8859  * @extends Object \r
8860  * Plugin (ptype = 'tabclosemenu') for adding a close context menu to tabs.\r
8861  * \r
8862  * @ptype tabclosemenu\r
8863  */\r
8864 Ext.ux.TabCloseMenu = function(){\r
8865     var tabs, menu, ctxItem;\r
8866     this.init = function(tp){\r
8867         tabs = tp;\r
8868         tabs.on('contextmenu', onContextMenu);\r
8869     };\r
8870 \r
8871     function onContextMenu(ts, item, e){\r
8872         if(!menu){ // create context menu on first right click\r
8873             menu = new Ext.menu.Menu({            \r
8874             items: [{\r
8875                 id: tabs.id + '-close',\r
8876                 text: 'Close Tab',\r
8877                 handler : function(){\r
8878                     tabs.remove(ctxItem);\r
8879                 }\r
8880             },{\r
8881                 id: tabs.id + '-close-others',\r
8882                 text: 'Close Other Tabs',\r
8883                 handler : function(){\r
8884                     tabs.items.each(function(item){\r
8885                         if(item.closable && item != ctxItem){\r
8886                             tabs.remove(item);\r
8887                         }\r
8888                     });\r
8889                 }\r
8890             }]});\r
8891         }\r
8892         ctxItem = item;\r
8893         var items = menu.items;\r
8894         items.get(tabs.id + '-close').setDisabled(!item.closable);\r
8895         var disableOthers = true;\r
8896         tabs.items.each(function(){\r
8897             if(this != item && this.closable){\r
8898                 disableOthers = false;\r
8899                 return false;\r
8900             }\r
8901         });\r
8902         items.get(tabs.id + '-close-others').setDisabled(disableOthers);\r
8903         e.stopEvent();\r
8904         menu.showAt(e.getPoint());\r
8905     }\r
8906 };\r
8907 \r
8908 Ext.preg('tabclosemenu', Ext.ux.TabCloseMenu);\r
8909 Ext.ns('Ext.ux.grid');
8910
8911 /**
8912  * @class Ext.ux.grid.TableGrid
8913  * @extends Ext.grid.GridPanel
8914  * A Grid which creates itself from an existing HTML table element.
8915  * @history
8916  * 2007-03-01 Original version by Nige "Animal" White
8917  * 2007-03-10 jvs Slightly refactored to reuse existing classes * @constructor
8918  * @param {String/HTMLElement/Ext.Element} table The table element from which this grid will be created -
8919  * The table MUST have some type of size defined for the grid to fill. The container will be
8920  * automatically set to position relative if it isn't already.
8921  * @param {Object} config A config object that sets properties on this grid and has two additional (optional)
8922  * properties: fields and columns which allow for customizing data fields and columns for this grid.
8923  */
8924 Ext.ux.grid.TableGrid = function(table, config){
8925     config = config ||
8926     {};
8927     Ext.apply(this, config);
8928     var cf = config.fields || [], ch = config.columns || [];
8929     table = Ext.get(table);
8930     
8931     var ct = table.insertSibling();
8932     
8933     var fields = [], cols = [];
8934     var headers = table.query("thead th");
8935     for (var i = 0, h; h = headers[i]; i++) {
8936         var text = h.innerHTML;
8937         var name = 'tcol-' + i;
8938         
8939         fields.push(Ext.applyIf(cf[i] ||
8940         {}, {
8941             name: name,
8942             mapping: 'td:nth(' + (i + 1) + ')/@innerHTML'
8943         }));
8944         
8945         cols.push(Ext.applyIf(ch[i] ||
8946         {}, {
8947             'header': text,
8948             'dataIndex': name,
8949             'width': h.offsetWidth,
8950             'tooltip': h.title,
8951             'sortable': true
8952         }));
8953     }
8954     
8955     var ds = new Ext.data.Store({
8956         reader: new Ext.data.XmlReader({
8957             record: 'tbody tr'
8958         }, fields)
8959     });
8960     
8961     ds.loadData(table.dom);
8962     
8963     var cm = new Ext.grid.ColumnModel(cols);
8964     
8965     if (config.width || config.height) {
8966         ct.setSize(config.width || 'auto', config.height || 'auto');
8967     }
8968     else {
8969         ct.setWidth(table.getWidth());
8970     }
8971     
8972     if (config.remove !== false) {
8973         table.remove();
8974     }
8975     
8976     Ext.applyIf(this, {
8977         'ds': ds,
8978         'cm': cm,
8979         'sm': new Ext.grid.RowSelectionModel(),
8980         autoHeight: true,
8981         autoWidth: false
8982     });
8983     Ext.ux.grid.TableGrid.superclass.constructor.call(this, ct, {});
8984 };
8985
8986 Ext.extend(Ext.ux.grid.TableGrid, Ext.grid.GridPanel);
8987
8988 //backwards compat
8989 Ext.grid.TableGrid = Ext.ux.grid.TableGrid;
8990 Ext.ns('Ext.ux');
8991 /**
8992  * @class Ext.ux.TabScrollerMenu
8993  * @extends Object 
8994  * Plugin (ptype = 'tabscrollermenu') for adding a tab scroller menu to tabs.
8995  * @constructor 
8996  * @param {Object} config Configuration options
8997  * @ptype tabscrollermenu
8998  */
8999 Ext.ux.TabScrollerMenu =  Ext.extend(Object, {
9000     /**
9001      * @cfg {Number} pageSize How many items to allow per submenu.
9002      */
9003         pageSize       : 10,
9004     /**
9005      * @cfg {Number} maxText How long should the title of each {@link Ext.menu.Item} be.
9006      */
9007         maxText        : 15,
9008     /**
9009      * @cfg {String} menuPrefixText Text to prefix the submenus.
9010      */    
9011         menuPrefixText : 'Items',
9012         constructor    : function(config) {
9013                 config = config || {};
9014                 Ext.apply(this, config);
9015         },
9016     //private
9017         init : function(tabPanel) {
9018                 Ext.apply(tabPanel, this.parentOverrides);
9019                 
9020                 tabPanel.tabScrollerMenu = this;
9021                 var thisRef = this;
9022                 
9023                 tabPanel.on({
9024                         render : {
9025                                 scope  : tabPanel,
9026                                 single : true,
9027                                 fn     : function() { 
9028                                         var newFn = tabPanel.createScrollers.createSequence(thisRef.createPanelsMenu, this);
9029                                         tabPanel.createScrollers = newFn;
9030                                 }
9031                         }
9032                 });
9033         },
9034         // private && sequeneced
9035         createPanelsMenu : function() {
9036                 var h = this.stripWrap.dom.offsetHeight;
9037                 
9038                 //move the right menu item to the left 18px
9039                 var rtScrBtn = this.header.dom.firstChild;
9040                 Ext.fly(rtScrBtn).applyStyles({
9041                         right : '18px'
9042                 });
9043                 
9044                 var stripWrap = Ext.get(this.strip.dom.parentNode);
9045                 stripWrap.applyStyles({
9046                          'margin-right' : '36px'
9047                 });
9048                 
9049                 // Add the new righthand menu
9050                 var scrollMenu = this.header.insertFirst({
9051                         cls:'x-tab-tabmenu-right'
9052                 });
9053                 scrollMenu.setHeight(h);
9054                 scrollMenu.addClassOnOver('x-tab-tabmenu-over');
9055                 scrollMenu.on('click', this.showTabsMenu, this);        
9056                 
9057                 this.scrollLeft.show = this.scrollLeft.show.createSequence(function() {
9058                         scrollMenu.show();                                                                                                                                               
9059                 });
9060                 
9061                 this.scrollLeft.hide = this.scrollLeft.hide.createSequence(function() {
9062                         scrollMenu.hide();                                                              
9063                 });
9064                 
9065         },
9066     /**
9067      * Returns an the current page size (this.pageSize);
9068      * @return {Number} this.pageSize The current page size.
9069      */
9070         getPageSize : function() {
9071                 return this.pageSize;
9072         },
9073     /**
9074      * Sets the number of menu items per submenu "page size".
9075      * @param {Number} pageSize The page size
9076      */
9077     setPageSize : function(pageSize) {
9078                 this.pageSize = pageSize;
9079         },
9080     /**
9081      * Returns the current maxText length;
9082      * @return {Number} this.maxText The current max text length.
9083      */
9084     getMaxText : function() {
9085                 return this.maxText;
9086         },
9087     /**
9088      * Sets the maximum text size for each menu item.
9089      * @param {Number} t The max text per each menu item.
9090      */
9091     setMaxText : function(t) {
9092                 this.maxText = t;
9093         },
9094     /**
9095      * Returns the current menu prefix text String.;
9096      * @return {String} this.menuPrefixText The current menu prefix text.
9097      */
9098         getMenuPrefixText : function() {
9099                 return this.menuPrefixText;
9100         },
9101     /**
9102      * Sets the menu prefix text String.
9103      * @param {String} t The menu prefix text.
9104      */    
9105         setMenuPrefixText : function(t) {
9106                 this.menuPrefixText = t;
9107         },
9108         // private && applied to the tab panel itself.
9109         parentOverrides : {
9110                 // all execute within the scope of the tab panel
9111                 // private      
9112                 showTabsMenu : function(e) {            
9113                         if  (this.tabsMenu) {
9114                                 this.tabsMenu.destroy();
9115                 this.un('destroy', this.tabsMenu.destroy, this.tabsMenu);
9116                 this.tabsMenu = null;
9117                         }
9118             this.tabsMenu =  new Ext.menu.Menu();
9119             this.on('destroy', this.tabsMenu.destroy, this.tabsMenu);
9120
9121             this.generateTabMenuItems();
9122
9123             var target = Ext.get(e.getTarget());
9124                         var xy     = target.getXY();
9125 //
9126                         //Y param + 24 pixels
9127                         xy[1] += 24;
9128                         
9129                         this.tabsMenu.showAt(xy);
9130                 },
9131                 // private      
9132                 generateTabMenuItems : function() {
9133                         var curActive  = this.getActiveTab();
9134                         var totalItems = this.items.getCount();
9135                         var pageSize   = this.tabScrollerMenu.getPageSize();
9136                         
9137                         
9138                         if (totalItems > pageSize)  {
9139                                 var numSubMenus = Math.floor(totalItems / pageSize);
9140                                 var remainder   = totalItems % pageSize;
9141                                 
9142                                 // Loop through all of the items and create submenus in chunks of 10
9143                                 for (var i = 0 ; i < numSubMenus; i++) {
9144                                         var curPage = (i + 1) * pageSize;
9145                                         var menuItems = [];
9146                                         
9147                                         
9148                                         for (var x = 0; x < pageSize; x++) {                            
9149                                                 index = x + curPage - pageSize;
9150                                                 var item = this.items.get(index);
9151                                                 menuItems.push(this.autoGenMenuItem(item));
9152                                         }
9153                                         
9154                                         this.tabsMenu.add({
9155                                                 text : this.tabScrollerMenu.getMenuPrefixText() + ' '  + (curPage - pageSize + 1) + ' - ' + curPage,
9156                                                 menu : menuItems
9157                                         });
9158                                         
9159                                 }
9160                                 // remaining items
9161                                 if (remainder > 0) {
9162                                         var start = numSubMenus * pageSize;
9163                                         menuItems = [];
9164                                         for (var i = start ; i < totalItems; i ++ ) {                                   
9165                                                 var item = this.items.get(i);
9166                                                 menuItems.push(this.autoGenMenuItem(item));
9167                                         }
9168                                         
9169                                         this.tabsMenu.add({
9170                                                 text : this.tabScrollerMenu.menuPrefixText  + ' ' + (start + 1) + ' - ' + (start + menuItems.length),
9171                                                 menu : menuItems
9172                                         });
9173
9174                                 }
9175                         }
9176                         else {
9177                                 this.items.each(function(item) {
9178                                         if (item.id != curActive.id && ! item.hidden) {
9179                                                 menuItems.push(this.autoGenMenuItem(item));
9180                                         }
9181                                 }, this);
9182                         }
9183                 },
9184                 // private
9185                 autoGenMenuItem : function(item) {
9186                         var maxText = this.tabScrollerMenu.getMaxText();
9187                         var text    = Ext.util.Format.ellipsis(item.title, maxText);
9188                         
9189                         return {
9190                                 text      : text,
9191                                 handler   : this.showTabFromMenu,
9192                                 scope     : this,
9193                                 disabled  : item.disabled,
9194                                 tabToShow : item,
9195                                 iconCls   : item.iconCls
9196                         }
9197                 
9198                 },
9199                 // private
9200                 showTabFromMenu : function(menuItem) {
9201                         this.setActiveTab(menuItem.tabToShow);
9202                 }       
9203         }       
9204 });
9205
9206 Ext.reg('tabscrollermenu', Ext.ux.TabScrollerMenu);
9207 Ext.ns('Ext.ux.tree');
9208
9209 /**
9210  * @class Ext.ux.tree.XmlTreeLoader
9211  * @extends Ext.tree.TreeLoader
9212  * <p>A TreeLoader that can convert an XML document into a hierarchy of {@link Ext.tree.TreeNode}s.
9213  * Any text value included as a text node in the XML will be added to the parent node as an attribute
9214  * called <tt>innerText</tt>.  Also, the tag name of each XML node will be added to the tree node as
9215  * an attribute called <tt>tagName</tt>.</p>
9216  * <p>By default, this class expects that your source XML will provide the necessary attributes on each
9217  * node as expected by the {@link Ext.tree.TreePanel} to display and load properly.  However, you can
9218  * provide your own custom processing of node attributes by overriding the {@link #processNode} method
9219  * and modifying the attributes as needed before they are used to create the associated TreeNode.</p>
9220  * @constructor
9221  * Creates a new XmlTreeloader.
9222  * @param {Object} config A config object containing config properties.
9223  */
9224 Ext.ux.tree.XmlTreeLoader = Ext.extend(Ext.tree.TreeLoader, {
9225     /**
9226      * @property  XML_NODE_ELEMENT
9227      * XML element node (value 1, read-only)
9228      * @type Number
9229      */
9230     XML_NODE_ELEMENT : 1,
9231     /**
9232      * @property  XML_NODE_TEXT
9233      * XML text node (value 3, read-only)
9234      * @type Number
9235      */
9236     XML_NODE_TEXT : 3,
9237
9238     // private override
9239     processResponse : function(response, node, callback){
9240         var xmlData = response.responseXML;
9241         var root = xmlData.documentElement || xmlData;
9242
9243         try{
9244             node.beginUpdate();
9245             node.appendChild(this.parseXml(root));
9246             node.endUpdate();
9247
9248             if(typeof callback == "function"){
9249                 callback(this, node);
9250             }
9251         }catch(e){
9252             this.handleFailure(response);
9253         }
9254     },
9255
9256     // private
9257     parseXml : function(node) {
9258         var nodes = [];
9259         Ext.each(node.childNodes, function(n){
9260             if(n.nodeType == this.XML_NODE_ELEMENT){
9261                 var treeNode = this.createNode(n);
9262                 if(n.childNodes.length > 0){
9263                     var child = this.parseXml(n);
9264                     if(typeof child == 'string'){
9265                         treeNode.attributes.innerText = child;
9266                     }else{
9267                         treeNode.appendChild(child);
9268                     }
9269                 }
9270                 nodes.push(treeNode);
9271             }
9272             else if(n.nodeType == this.XML_NODE_TEXT){
9273                 var text = n.nodeValue.trim();
9274                 if(text.length > 0){
9275                     return nodes = text;
9276                 }
9277             }
9278         }, this);
9279
9280         return nodes;
9281     },
9282
9283     // private override
9284     createNode : function(node){
9285         var attr = {
9286             tagName: node.tagName
9287         };
9288
9289         Ext.each(node.attributes, function(a){
9290             attr[a.nodeName] = a.nodeValue;
9291         });
9292
9293         this.processAttributes(attr);
9294
9295         return Ext.ux.tree.XmlTreeLoader.superclass.createNode.call(this, attr);
9296     },
9297
9298     /*
9299      * Template method intended to be overridden by subclasses that need to provide
9300      * custom attribute processing prior to the creation of each TreeNode.  This method
9301      * will be passed a config object containing existing TreeNode attribute name/value
9302      * pairs which can be modified as needed directly (no need to return the object).
9303      */
9304     processAttributes: Ext.emptyFn
9305 });
9306
9307 //backwards compat
9308 Ext.ux.XmlTreeLoader = Ext.ux.tree.XmlTreeLoader;
9309 /**
9310  * @class Ext.ux.ValidationStatus
9311  * A {@link Ext.StatusBar} plugin that provides automatic error notification when the
9312  * associated form contains validation errors.
9313  * @extends Ext.Component
9314  * @constructor
9315  * Creates a new ValiationStatus plugin
9316  * @param {Object} config A config object
9317  */
9318 Ext.ux.ValidationStatus = Ext.extend(Ext.Component, {
9319     /**
9320      * @cfg {String} errorIconCls
9321      * The {@link #iconCls} value to be applied to the status message when there is a
9322      * validation error. Defaults to <tt>'x-status-error'</tt>.
9323      */
9324     errorIconCls : 'x-status-error',
9325     /**
9326      * @cfg {String} errorListCls
9327      * The css class to be used for the error list when there are validation errors.
9328      * Defaults to <tt>'x-status-error-list'</tt>.
9329      */
9330     errorListCls : 'x-status-error-list',
9331     /**
9332      * @cfg {String} validIconCls
9333      * The {@link #iconCls} value to be applied to the status message when the form
9334      * validates. Defaults to <tt>'x-status-valid'</tt>.
9335      */
9336     validIconCls : 'x-status-valid',
9337     
9338     /**
9339      * @cfg {String} showText
9340      * The {@link #text} value to be applied when there is a form validation error.
9341      * Defaults to <tt>'The form has errors (click for details...)'</tt>.
9342      */
9343     showText : 'The form has errors (click for details...)',
9344     /**
9345      * @cfg {String} showText
9346      * The {@link #text} value to display when the error list is displayed.
9347      * Defaults to <tt>'Click again to hide the error list'</tt>.
9348      */
9349     hideText : 'Click again to hide the error list',
9350     /**
9351      * @cfg {String} submitText
9352      * The {@link #text} value to be applied when the form is being submitted.
9353      * Defaults to <tt>'Saving...'</tt>.
9354      */
9355     submitText : 'Saving...',
9356     
9357     // private
9358     init : function(sb){
9359         sb.on('render', function(){
9360             this.statusBar = sb;
9361             this.monitor = true;
9362             this.errors = new Ext.util.MixedCollection();
9363             this.listAlign = (sb.statusAlign=='right' ? 'br-tr?' : 'bl-tl?');
9364             
9365             if(this.form){
9366                 this.form = Ext.getCmp(this.form).getForm();
9367                 this.startMonitoring();
9368                 this.form.on('beforeaction', function(f, action){
9369                     if(action.type == 'submit'){
9370                         // Ignore monitoring while submitting otherwise the field validation
9371                         // events cause the status message to reset too early
9372                         this.monitor = false;
9373                     }
9374                 }, this);
9375                 var startMonitor = function(){
9376                     this.monitor = true;
9377                 };
9378                 this.form.on('actioncomplete', startMonitor, this);
9379                 this.form.on('actionfailed', startMonitor, this);
9380             }
9381         }, this, {single:true});
9382         sb.on({
9383             scope: this,
9384             afterlayout:{
9385                 single: true,
9386                 fn: function(){
9387                     // Grab the statusEl after the first layout.
9388                     sb.statusEl.getEl().on('click', this.onStatusClick, this, {buffer:200});
9389                 } 
9390             }, 
9391             beforedestroy:{
9392                 single: true,
9393                 fn: this.onDestroy
9394             } 
9395         });
9396     },
9397     
9398     // private
9399     startMonitoring : function(){
9400         this.form.items.each(function(f){
9401             f.on('invalid', this.onFieldValidation, this);
9402             f.on('valid', this.onFieldValidation, this);
9403         }, this);
9404     },
9405     
9406     // private
9407     stopMonitoring : function(){
9408         this.form.items.each(function(f){
9409             f.un('invalid', this.onFieldValidation, this);
9410             f.un('valid', this.onFieldValidation, this);
9411         }, this);
9412     },
9413     
9414     // private
9415     onDestroy : function(){
9416         this.stopMonitoring();
9417         this.statusBar.statusEl.un('click', this.onStatusClick, this);
9418         Ext.ux.ValidationStatus.superclass.onDestroy.call(this);
9419     },
9420     
9421     // private
9422     onFieldValidation : function(f, msg){
9423         if(!this.monitor){
9424             return false;
9425         }
9426         if(msg){
9427             this.errors.add(f.id, {field:f, msg:msg});
9428         }else{
9429             this.errors.removeKey(f.id);
9430         }
9431         this.updateErrorList();
9432         if(this.errors.getCount() > 0){
9433             if(this.statusBar.getText() != this.showText){
9434                 this.statusBar.setStatus({text:this.showText, iconCls:this.errorIconCls});
9435             }
9436         }else{
9437             this.statusBar.clearStatus().setIcon(this.validIconCls);
9438         }
9439     },
9440     
9441     // private
9442     updateErrorList : function(){
9443         if(this.errors.getCount() > 0){
9444                 var msg = '<ul>';
9445                 this.errors.each(function(err){
9446                     msg += ('<li id="x-err-'+ err.field.id +'"><a href="#">' + err.msg + '</a></li>');
9447                 }, this);
9448                 this.getMsgEl().update(msg+'</ul>');
9449         }else{
9450             this.getMsgEl().update('');
9451         }
9452     },
9453     
9454     // private
9455     getMsgEl : function(){
9456         if(!this.msgEl){
9457             this.msgEl = Ext.DomHelper.append(Ext.getBody(), {
9458                 cls: this.errorListCls+' x-hide-offsets'
9459             }, true);
9460             
9461             this.msgEl.on('click', function(e){
9462                 var t = e.getTarget('li', 10, true);
9463                 if(t){
9464                     Ext.getCmp(t.id.split('x-err-')[1]).focus();
9465                     this.hideErrors();
9466                 }
9467             }, this, {stopEvent:true}); // prevent anchor click navigation
9468         }
9469         return this.msgEl;
9470     },
9471     
9472     // private
9473     showErrors : function(){
9474         this.updateErrorList();
9475         this.getMsgEl().alignTo(this.statusBar.getEl(), this.listAlign).slideIn('b', {duration:0.3, easing:'easeOut'});
9476         this.statusBar.setText(this.hideText);
9477         this.form.getEl().on('click', this.hideErrors, this, {single:true}); // hide if the user clicks directly into the form
9478     },
9479     
9480     // private
9481     hideErrors : function(){
9482         var el = this.getMsgEl();
9483         if(el.isVisible()){
9484                 el.slideOut('b', {duration:0.2, easing:'easeIn'});
9485                 this.statusBar.setText(this.showText);
9486         }
9487         this.form.getEl().un('click', this.hideErrors, this);
9488     },
9489     
9490     // private
9491     onStatusClick : function(){
9492         if(this.getMsgEl().isVisible()){
9493             this.hideErrors();
9494         }else if(this.errors.getCount() > 0){
9495             this.showErrors();
9496         }
9497     }
9498 });(function() {    
9499     Ext.override(Ext.list.Column, {
9500         init : function() {            
9501             if(!this.type){
9502                 this.type = "auto";
9503             }
9504
9505             var st = Ext.data.SortTypes;
9506             // named sortTypes are supported, here we look them up
9507             if(typeof this.sortType == "string"){
9508                 this.sortType = st[this.sortType];
9509             }
9510
9511             // set default sortType for strings and dates
9512             if(!this.sortType){
9513                 switch(this.type){
9514                     case "string":
9515                         this.sortType = st.asUCString;
9516                         break;
9517                     case "date":
9518                         this.sortType = st.asDate;
9519                         break;
9520                     default:
9521                         this.sortType = st.none;
9522                 }
9523             }
9524         }
9525     });
9526
9527     Ext.tree.Column = Ext.extend(Ext.list.Column, {});
9528     Ext.tree.NumberColumn = Ext.extend(Ext.list.NumberColumn, {});
9529     Ext.tree.DateColumn = Ext.extend(Ext.list.DateColumn, {});
9530     Ext.tree.BooleanColumn = Ext.extend(Ext.list.BooleanColumn, {});
9531
9532     Ext.reg('tgcolumn', Ext.tree.Column);
9533     Ext.reg('tgnumbercolumn', Ext.tree.NumberColumn);
9534     Ext.reg('tgdatecolumn', Ext.tree.DateColumn);
9535     Ext.reg('tgbooleancolumn', Ext.tree.BooleanColumn);
9536 })();
9537 /**
9538  * @class Ext.ux.tree.TreeGridNodeUI
9539  * @extends Ext.tree.TreeNodeUI
9540  */
9541 Ext.ux.tree.TreeGridNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
9542     isTreeGridNodeUI: true,
9543
9544     renderElements : function(n, a, targetNode, bulkRender){
9545         var t = n.getOwnerTree(),
9546             cols = t.columns,
9547             c = cols[0],
9548             i, buf, len;
9549
9550         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
9551
9552         buf = [
9553              '<tbody class="x-tree-node">',
9554                 '<tr ext:tree-node-id="', n.id ,'" class="x-tree-node-el x-tree-node-leaf ', a.cls, '">',
9555                     '<td class="x-treegrid-col">',
9556                         '<span class="x-tree-node-indent">', this.indentMarkup, "</span>",
9557                         '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow">',
9558                         '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon', (a.icon ? " x-tree-node-inline-icon" : ""), (a.iconCls ? " "+a.iconCls : ""), '" unselectable="on">',
9559                         '<a hidefocus="on" class="x-tree-node-anchor" href="', a.href ? a.href : '#', '" tabIndex="1" ',
9560                             a.hrefTarget ? ' target="'+a.hrefTarget+'"' : '', '>',
9561                         '<span unselectable="on">', (c.tpl ? c.tpl.apply(a) : a[c.dataIndex] || c.text), '</span></a>',
9562                     '</td>'
9563         ];
9564
9565         for(i = 1, len = cols.length; i < len; i++){
9566             c = cols[i];
9567             buf.push(
9568                     '<td class="x-treegrid-col ', (c.cls ? c.cls : ''), '">',
9569                         '<div unselectable="on" class="x-treegrid-text"', (c.align ? ' style="text-align: ' + c.align + ';"' : ''), '>',
9570                             (c.tpl ? c.tpl.apply(a) : a[c.dataIndex]),
9571                         '</div>',
9572                     '</td>'
9573             );
9574         }
9575
9576         buf.push(
9577             '</tr><tr class="x-tree-node-ct"><td colspan="', cols.length, '">',
9578             '<table class="x-treegrid-node-ct-table" cellpadding="0" cellspacing="0" style="table-layout: fixed; display: none; width: ', t.innerCt.getWidth() ,'px;"><colgroup>'
9579         );
9580         for(i = 0, len = cols.length; i<len; i++) {
9581             buf.push('<col style="width: ', (cols[i].hidden ? 0 : cols[i].width) ,'px;" />');
9582         }
9583         buf.push('</colgroup></table></td></tr></tbody>');
9584
9585         if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
9586             this.wrap = Ext.DomHelper.insertHtml("beforeBegin", n.nextSibling.ui.getEl(), buf.join(''));
9587         }else{
9588             this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(''));
9589         }
9590
9591         this.elNode = this.wrap.childNodes[0];
9592         this.ctNode = this.wrap.childNodes[1].firstChild.firstChild;
9593         var cs = this.elNode.firstChild.childNodes;
9594         this.indentNode = cs[0];
9595         this.ecNode = cs[1];
9596         this.iconNode = cs[2];
9597         this.anchor = cs[3];
9598         this.textNode = cs[3].firstChild;
9599     },
9600
9601     // private
9602     animExpand : function(cb){
9603         this.ctNode.style.display = "";
9604         Ext.ux.tree.TreeGridNodeUI.superclass.animExpand.call(this, cb);
9605     }
9606 });
9607
9608 Ext.ux.tree.TreeGridRootNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
9609     isTreeGridNodeUI: true,
9610
9611     // private
9612     render : function(){
9613         if(!this.rendered){
9614             this.wrap = this.ctNode = this.node.ownerTree.innerCt.dom;
9615             this.node.expanded = true;
9616         }
9617
9618         if(Ext.isWebKit) {
9619             // weird table-layout: fixed issue in webkit
9620             var ct = this.ctNode;
9621             ct.style.tableLayout = null;
9622             (function() {
9623                 ct.style.tableLayout = 'fixed';
9624             }).defer(1);
9625         }
9626     },
9627
9628     destroy : function(){
9629         if(this.elNode){
9630             Ext.dd.Registry.unregister(this.elNode.id);
9631         }
9632         delete this.node;
9633     },
9634
9635     collapse : Ext.emptyFn,
9636     expand : Ext.emptyFn
9637 });/**
9638  * @class Ext.tree.ColumnResizer
9639  * @extends Ext.util.Observable
9640  */
9641 Ext.tree.ColumnResizer = Ext.extend(Ext.util.Observable, {
9642     /**
9643      * @cfg {Number} minWidth The minimum width the column can be dragged to.
9644      * Defaults to <tt>14</tt>.
9645      */
9646     minWidth: 14,
9647
9648     constructor: function(config){
9649         Ext.apply(this, config);
9650         Ext.tree.ColumnResizer.superclass.constructor.call(this);
9651     },
9652
9653     init : function(tree){
9654         this.tree = tree;
9655         tree.on('render', this.initEvents, this);
9656     },
9657
9658     initEvents : function(tree){
9659         tree.mon(tree.innerHd, 'mousemove', this.handleHdMove, this);
9660         this.tracker = new Ext.dd.DragTracker({
9661             onBeforeStart: this.onBeforeStart.createDelegate(this),
9662             onStart: this.onStart.createDelegate(this),
9663             onDrag: this.onDrag.createDelegate(this),
9664             onEnd: this.onEnd.createDelegate(this),
9665             tolerance: 3,
9666             autoStart: 300
9667         });
9668         this.tracker.initEl(tree.innerHd);
9669         tree.on('beforedestroy', this.tracker.destroy, this.tracker);
9670     },
9671
9672     handleHdMove : function(e, t){
9673         var hw = 5,
9674             x = e.getPageX(),
9675             hd = e.getTarget('.x-treegrid-hd', 3, true);
9676         
9677         if(hd){                                 
9678             var r = hd.getRegion(),
9679                 ss = hd.dom.style,
9680                 pn = hd.dom.parentNode;
9681             
9682             if(x - r.left <= hw && hd.dom !== pn.firstChild) {
9683                 var ps = hd.dom.previousSibling;
9684                 while(ps && Ext.fly(ps).hasClass('x-treegrid-hd-hidden')) {
9685                     ps = ps.previousSibling;
9686                 }
9687                 if(ps) {                    
9688                     this.activeHd = Ext.get(ps);
9689                                 ss.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize';
9690                 }
9691             } else if(r.right - x <= hw) {
9692                 var ns = hd.dom;
9693                 while(ns && Ext.fly(ns).hasClass('x-treegrid-hd-hidden')) {
9694                     ns = ns.previousSibling;
9695                 }
9696                 if(ns) {
9697                     this.activeHd = Ext.get(ns);
9698                                 ss.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize';                    
9699                 }
9700             } else{
9701                 delete this.activeHd;
9702                 ss.cursor = '';
9703             }
9704         }
9705     },
9706
9707     onBeforeStart : function(e){
9708         this.dragHd = this.activeHd;
9709         return !!this.dragHd;
9710     },
9711
9712     onStart : function(e){
9713         this.tree.headersDisabled = true;
9714         this.proxy = this.tree.body.createChild({cls:'x-treegrid-resizer'});
9715         this.proxy.setHeight(this.tree.body.getHeight());
9716
9717         var x = this.tracker.getXY()[0];
9718
9719         this.hdX = this.dragHd.getX();
9720         this.hdIndex = this.tree.findHeaderIndex(this.dragHd);
9721
9722         this.proxy.setX(this.hdX);
9723         this.proxy.setWidth(x-this.hdX);
9724
9725         this.maxWidth = this.tree.outerCt.getWidth() - this.tree.innerBody.translatePoints(this.hdX).left;
9726     },
9727
9728     onDrag : function(e){
9729         var cursorX = this.tracker.getXY()[0];
9730         this.proxy.setWidth((cursorX-this.hdX).constrain(this.minWidth, this.maxWidth));
9731     },
9732
9733     onEnd : function(e){
9734         var nw = this.proxy.getWidth(),
9735             tree = this.tree;
9736         
9737         this.proxy.remove();
9738         delete this.dragHd;
9739         
9740         tree.columns[this.hdIndex].width = nw;
9741         tree.updateColumnWidths();
9742         
9743         setTimeout(function(){
9744             tree.headersDisabled = false;
9745         }, 100);
9746     }
9747 });Ext.ns('Ext.ux.tree');
9748
9749 /**
9750  * @class Ext.ux.tree.TreeGridSorter
9751  * @extends Ext.tree.TreeSorter
9752  * Provides sorting of nodes in a {@link Ext.ux.tree.TreeGrid}.  The TreeGridSorter automatically monitors events on the
9753  * associated TreeGrid that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange).
9754  * Example usage:<br />
9755  * <pre><code>
9756  new Ext.ux.tree.TreeGridSorter(myTreeGrid, {
9757      folderSort: true,
9758      dir: "desc",
9759      sortType: function(node) {
9760          // sort by a custom, typed attribute:
9761          return parseInt(node.id, 10);
9762      }
9763  });
9764  </code></pre>
9765  * @constructor
9766  * @param {TreeGrid} tree
9767  * @param {Object} config
9768  */
9769 Ext.ux.tree.TreeGridSorter = Ext.extend(Ext.tree.TreeSorter, {
9770     /**
9771      * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to <tt>['sort-asc', 'sort-desc']</tt>)
9772      */
9773     sortClasses : ['sort-asc', 'sort-desc'],
9774     /**
9775      * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to <tt>'Sort Ascending'</tt>)
9776      */
9777     sortAscText : 'Sort Ascending',
9778     /**
9779      * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to <tt>'Sort Descending'</tt>)
9780      */
9781     sortDescText : 'Sort Descending',
9782
9783     constructor : function(tree, config) {
9784         if(!Ext.isObject(config)) {
9785             config = {
9786                 property: tree.columns[0].dataIndex || 'text',
9787                 folderSort: true
9788             }
9789         }
9790
9791         Ext.ux.tree.TreeGridSorter.superclass.constructor.apply(this, arguments);
9792
9793         this.tree = tree;
9794         tree.on('headerclick', this.onHeaderClick, this);
9795         tree.ddAppendOnly = true;
9796
9797         me = this;
9798         this.defaultSortFn = function(n1, n2){
9799
9800             var dsc = me.dir && me.dir.toLowerCase() == 'desc';
9801             var p = me.property || 'text';
9802             var sortType = me.sortType;
9803             var fs = me.folderSort;
9804             var cs = me.caseSensitive === true;
9805             var leafAttr = me.leafAttr || 'leaf';
9806
9807             if(fs){
9808                 if(n1.attributes[leafAttr] && !n2.attributes[leafAttr]){
9809                     return 1;
9810                 }
9811                 if(!n1.attributes[leafAttr] && n2.attributes[leafAttr]){
9812                     return -1;
9813                 }
9814             }
9815             var v1 = sortType ? sortType(n1) : (cs ? n1.attributes[p] : n1.attributes[p].toUpperCase());
9816             var v2 = sortType ? sortType(n2) : (cs ? n2.attributes[p] : n2.attributes[p].toUpperCase());
9817             if(v1 < v2){
9818                 return dsc ? +1 : -1;
9819             }else if(v1 > v2){
9820                 return dsc ? -1 : +1;
9821             }else{
9822                 return 0;
9823             }
9824         };
9825
9826         tree.on('afterrender', this.onAfterTreeRender, this, {single: true});
9827         tree.on('headermenuclick', this.onHeaderMenuClick, this);
9828     },
9829
9830     onAfterTreeRender : function() {
9831         if(this.tree.hmenu){
9832             this.tree.hmenu.insert(0,
9833                 {itemId:'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'},
9834                 {itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
9835             );
9836         }
9837         this.updateSortIcon(0, 'asc');
9838     },
9839
9840     onHeaderMenuClick : function(c, id, index) {
9841         if(id === 'asc' || id === 'desc') {
9842             this.onHeaderClick(c, null, index);
9843             return false;
9844         }
9845     },
9846
9847     onHeaderClick : function(c, el, i) {
9848         if(c && !this.tree.headersDisabled){
9849             var me = this;
9850
9851             me.property = c.dataIndex;
9852             me.dir = c.dir = (c.dir === 'desc' ? 'asc' : 'desc');
9853             me.sortType = c.sortType;
9854             me.caseSensitive === Ext.isBoolean(c.caseSensitive) ? c.caseSensitive : this.caseSensitive;
9855             me.sortFn = c.sortFn || this.defaultSortFn;
9856
9857             this.tree.root.cascade(function(n) {
9858                 if(!n.isLeaf()) {
9859                     me.updateSort(me.tree, n);
9860                 }
9861             });
9862
9863             this.updateSortIcon(i, c.dir);
9864         }
9865     },
9866
9867     // private
9868     updateSortIcon : function(col, dir){
9869         var sc = this.sortClasses;
9870         var hds = this.tree.innerHd.select('td').removeClass(sc);
9871         hds.item(col).addClass(sc[dir == 'desc' ? 1 : 0]);
9872     }
9873 });/**
9874  * @class Ext.ux.tree.TreeGridLoader
9875  * @extends Ext.tree.TreeLoader
9876  */
9877 Ext.ux.tree.TreeGridLoader = Ext.extend(Ext.tree.TreeLoader, {
9878     createNode : function(attr) {
9879         if (!attr.uiProvider) {
9880             attr.uiProvider = Ext.ux.tree.TreeGridNodeUI;
9881         }
9882         return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
9883     }
9884 });/**
9885  * @class Ext.ux.tree.TreeGrid
9886  * @extends Ext.tree.TreePanel
9887  * 
9888  * @xtype treegrid
9889  */
9890 Ext.ux.tree.TreeGrid = Ext.extend(Ext.tree.TreePanel, {
9891     rootVisible : false,
9892     useArrows : true,
9893     lines : false,
9894     borderWidth : Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
9895     cls : 'x-treegrid',
9896
9897     columnResize : true,
9898     enableSort : true,
9899     reserveScrollOffset : true,
9900     enableHdMenu : true,
9901     
9902     columnsText : 'Columns',
9903
9904     initComponent : function() {
9905         if(!this.root) {
9906             this.root = new Ext.tree.AsyncTreeNode({text: 'Root'});
9907         }
9908         
9909         // initialize the loader
9910         var l = this.loader;
9911         if(!l){
9912             l = new Ext.ux.tree.TreeGridLoader({
9913                 dataUrl: this.dataUrl,
9914                 requestMethod: this.requestMethod,
9915                 store: this.store
9916             });
9917         }else if(Ext.isObject(l) && !l.load){
9918             l = new Ext.ux.tree.TreeGridLoader(l);
9919         }
9920         else if(l) {
9921             l.createNode = function(attr) {
9922                 if (!attr.uiProvider) {
9923                     attr.uiProvider = Ext.ux.tree.TreeGridNodeUI;
9924                 }
9925                 return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
9926             }
9927         }
9928         this.loader = l;
9929                             
9930         Ext.ux.tree.TreeGrid.superclass.initComponent.call(this);                    
9931         
9932         this.initColumns();
9933         
9934         if(this.enableSort) {
9935             this.treeGridSorter = new Ext.ux.tree.TreeGridSorter(this, this.enableSort);
9936         }
9937         
9938         if(this.columnResize){
9939             this.colResizer = new Ext.tree.ColumnResizer(this.columnResize);
9940             this.colResizer.init(this);
9941         }
9942         
9943         var c = this.columns;
9944         if(!this.internalTpl){                                
9945             this.internalTpl = new Ext.XTemplate(
9946                 '<div class="x-grid3-header">',
9947                     '<div class="x-treegrid-header-inner">',
9948                         '<div class="x-grid3-header-offset">',
9949                             '<table cellspacing="0" cellpadding="0" border="0"><colgroup><tpl for="columns"><col /></tpl></colgroup>',
9950                             '<thead><tr class="x-grid3-hd-row">',
9951                             '<tpl for="columns">',
9952                             '<td class="x-grid3-hd x-grid3-cell x-treegrid-hd" style="text-align: {align};" id="', this.id, '-xlhd-{#}">',
9953                                 '<div class="x-grid3-hd-inner x-treegrid-hd-inner" unselectable="on">',
9954                                      this.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '',
9955                                      '{header}<img class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
9956                                  '</div>',
9957                             '</td></tpl>',
9958                             '</tr></thead>',
9959                         '</div></table>',
9960                     '</div></div>',
9961                 '</div>',
9962                 '<div class="x-treegrid-root-node">',
9963                     '<table class="x-treegrid-root-table" cellpadding="0" cellspacing="0" style="table-layout: fixed;"></table>',
9964                 '</div>'
9965             );
9966         }
9967         
9968         if(!this.colgroupTpl) {
9969             this.colgroupTpl = new Ext.XTemplate(
9970                 '<colgroup><tpl for="columns"><col style="width: {width}px"/></tpl></colgroup>'
9971             );
9972         }
9973     },
9974
9975     initColumns : function() {
9976         var cs = this.columns,
9977             len = cs.length, 
9978             columns = [],
9979             i, c;
9980
9981         for(i = 0; i < len; i++){
9982             c = cs[i];
9983             if(!c.isColumn) {
9984                 c.xtype = c.xtype ? (/^tg/.test(c.xtype) ? c.xtype : 'tg' + c.xtype) : 'tgcolumn';
9985                 c = Ext.create(c);
9986             }
9987             c.init(this);
9988             columns.push(c);
9989             
9990             if(this.enableSort !== false && c.sortable !== false) {
9991                 c.sortable = true;
9992                 this.enableSort = true;
9993             }
9994         }
9995
9996         this.columns = columns;
9997     },
9998
9999     onRender : function(){
10000         Ext.tree.TreePanel.superclass.onRender.apply(this, arguments);
10001
10002         this.el.addClass('x-treegrid');
10003         
10004         this.outerCt = this.body.createChild({
10005             cls:'x-tree-root-ct x-treegrid-ct ' + (this.useArrows ? 'x-tree-arrows' : this.lines ? 'x-tree-lines' : 'x-tree-no-lines')
10006         });
10007         
10008         this.internalTpl.overwrite(this.outerCt, {columns: this.columns});
10009         
10010         this.mainHd = Ext.get(this.outerCt.dom.firstChild);
10011         this.innerHd = Ext.get(this.mainHd.dom.firstChild);
10012         this.innerBody = Ext.get(this.outerCt.dom.lastChild);
10013         this.innerCt = Ext.get(this.innerBody.dom.firstChild);
10014         
10015         this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns});
10016         
10017         if(this.hideHeaders){
10018             this.header.dom.style.display = 'none';
10019         }
10020         else if(this.enableHdMenu !== false){
10021             this.hmenu = new Ext.menu.Menu({id: this.id + '-hctx'});
10022             if(this.enableColumnHide !== false){
10023                 this.colMenu = new Ext.menu.Menu({id: this.id + '-hcols-menu'});
10024                 this.colMenu.on({
10025                     scope: this,
10026                     beforeshow: this.beforeColMenuShow,
10027                     itemclick: this.handleHdMenuClick
10028                 });
10029                 this.hmenu.add({
10030                     itemId:'columns',
10031                     hideOnClick: false,
10032                     text: this.columnsText,
10033                     menu: this.colMenu,
10034                     iconCls: 'x-cols-icon'
10035                 });
10036             }
10037             this.hmenu.on('itemclick', this.handleHdMenuClick, this);
10038         }
10039     },
10040
10041     setRootNode : function(node){
10042         node.attributes.uiProvider = Ext.ux.tree.TreeGridRootNodeUI;        
10043         node = Ext.ux.tree.TreeGrid.superclass.setRootNode.call(this, node);
10044         if(this.innerCt) {
10045             this.colgroupTpl.insertFirst(this.innerCt, {columns: this.columns});
10046         }
10047         return node;
10048     },
10049     
10050     clearInnerCt : function(){
10051         if(Ext.isIE){
10052             var dom = this.innerCt.dom;
10053             while(dom.firstChild){
10054                 dom.removeChild(dom.firstChild);
10055             }
10056         }else{
10057             Ext.ux.tree.TreeGrid.superclass.clearInnerCt.call(this);
10058         }
10059     },
10060     
10061     initEvents : function() {
10062         Ext.ux.tree.TreeGrid.superclass.initEvents.apply(this, arguments);
10063
10064         this.mon(this.innerBody, 'scroll', this.syncScroll, this);
10065         this.mon(this.innerHd, 'click', this.handleHdDown, this);
10066         this.mon(this.mainHd, {
10067             scope: this,
10068             mouseover: this.handleHdOver,
10069             mouseout: this.handleHdOut
10070         });
10071     },
10072     
10073     onResize : function(w, h) {
10074         Ext.ux.tree.TreeGrid.superclass.onResize.apply(this, arguments);
10075         
10076         var bd = this.innerBody.dom;
10077         var hd = this.innerHd.dom;
10078
10079         if(!bd){
10080             return;
10081         }
10082
10083         if(Ext.isNumber(h)){
10084             bd.style.height = this.body.getHeight(true) - hd.offsetHeight + 'px';
10085         }
10086
10087         if(Ext.isNumber(w)){                        
10088             var sw = Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
10089             if(this.reserveScrollOffset || ((bd.offsetWidth - bd.clientWidth) > 10)){
10090                 this.setScrollOffset(sw);
10091             }else{
10092                 var me = this;
10093                 setTimeout(function(){
10094                     me.setScrollOffset(bd.offsetWidth - bd.clientWidth > 10 ? sw : 0);
10095                 }, 10);
10096             }
10097         }
10098     },
10099
10100     updateColumnWidths : function() {
10101         var cols = this.columns,
10102             colCount = cols.length,
10103             groups = this.outerCt.query('colgroup'),
10104             groupCount = groups.length,
10105             c, g, i, j;
10106
10107         for(i = 0; i<colCount; i++) {
10108             c = cols[i];
10109             for(j = 0; j<groupCount; j++) {
10110                 g = groups[j];
10111                 g.childNodes[i].style.width = (c.hidden ? 0 : c.width) + 'px';
10112             }
10113         }
10114         
10115         for(i = 0, groups = this.innerHd.query('td'), len = groups.length; i<len; i++) {
10116             c = Ext.fly(groups[i]);
10117             if(cols[i] && cols[i].hidden) {
10118                 c.addClass('x-treegrid-hd-hidden');
10119             }
10120             else {
10121                 c.removeClass('x-treegrid-hd-hidden');
10122             }
10123         }
10124
10125         var tcw = this.getTotalColumnWidth();                        
10126         Ext.fly(this.innerHd.dom.firstChild).setWidth(tcw + (this.scrollOffset || 0));
10127         this.outerCt.select('table').setWidth(tcw);
10128         this.syncHeaderScroll();    
10129     },
10130                     
10131     getVisibleColumns : function() {
10132         var columns = [],
10133             cs = this.columns,
10134             len = cs.length,
10135             i;
10136             
10137         for(i = 0; i<len; i++) {
10138             if(!cs[i].hidden) {
10139                 columns.push(cs[i]);
10140             }
10141         }        
10142         return columns;
10143     },
10144
10145     getTotalColumnWidth : function() {
10146         var total = 0;
10147         for(var i = 0, cs = this.getVisibleColumns(), len = cs.length; i<len; i++) {
10148             total += cs[i].width;
10149         }
10150         return total;
10151     },
10152
10153     setScrollOffset : function(scrollOffset) {
10154         this.scrollOffset = scrollOffset;                        
10155         this.updateColumnWidths();
10156     },
10157
10158     // private
10159     handleHdDown : function(e, t){
10160         var hd = e.getTarget('.x-treegrid-hd');
10161
10162         if(hd && Ext.fly(t).hasClass('x-grid3-hd-btn')){
10163             var ms = this.hmenu.items,
10164                 cs = this.columns,
10165                 index = this.findHeaderIndex(hd),
10166                 c = cs[index],
10167                 sort = c.sortable;
10168                 
10169             e.stopEvent();
10170             Ext.fly(hd).addClass('x-grid3-hd-menu-open');
10171             this.hdCtxIndex = index;
10172             
10173             this.fireEvent('headerbuttonclick', ms, c, hd, index);
10174             
10175             this.hmenu.on('hide', function(){
10176                 Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
10177             }, this, {single:true});
10178             
10179             this.hmenu.show(t, 'tl-bl?');
10180         }
10181         else if(hd) {
10182             var index = this.findHeaderIndex(hd);
10183             this.fireEvent('headerclick', this.columns[index], hd, index);
10184         }
10185     },
10186
10187     // private
10188     handleHdOver : function(e, t){                    
10189         var hd = e.getTarget('.x-treegrid-hd');                        
10190         if(hd && !this.headersDisabled){
10191             index = this.findHeaderIndex(hd);
10192             this.activeHdRef = t;
10193             this.activeHdIndex = index;
10194             var el = Ext.get(hd);
10195             this.activeHdRegion = el.getRegion();
10196             el.addClass('x-grid3-hd-over');
10197             this.activeHdBtn = el.child('.x-grid3-hd-btn');
10198             if(this.activeHdBtn){
10199                 this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight-1)+'px';
10200             }
10201         }
10202     },
10203     
10204     // private
10205     handleHdOut : function(e, t){
10206         var hd = e.getTarget('.x-treegrid-hd');
10207         if(hd && (!Ext.isIE || !e.within(hd, true))){
10208             this.activeHdRef = null;
10209             Ext.fly(hd).removeClass('x-grid3-hd-over');
10210             hd.style.cursor = '';
10211         }
10212     },
10213                     
10214     findHeaderIndex : function(hd){
10215         hd = hd.dom || hd;
10216         var cs = hd.parentNode.childNodes;
10217         for(var i = 0, c; c = cs[i]; i++){
10218             if(c == hd){
10219                 return i;
10220             }
10221         }
10222         return -1;
10223     },
10224     
10225     // private
10226     beforeColMenuShow : function(){
10227         var cols = this.columns,  
10228             colCount = cols.length,
10229             i, c;                        
10230         this.colMenu.removeAll();                    
10231         for(i = 1; i < colCount; i++){
10232             c = cols[i];
10233             if(c.hideable !== false){
10234                 this.colMenu.add(new Ext.menu.CheckItem({
10235                     itemId: 'col-' + i,
10236                     text: c.header,
10237                     checked: !c.hidden,
10238                     hideOnClick:false,
10239                     disabled: c.hideable === false
10240                 }));
10241             }
10242         }
10243     },
10244                     
10245     // private
10246     handleHdMenuClick : function(item){
10247         var index = this.hdCtxIndex,
10248             id = item.getItemId();
10249         
10250         if(this.fireEvent('headermenuclick', this.columns[index], id, index) !== false) {
10251             index = id.substr(4);
10252             if(index > 0 && this.columns[index]) {
10253                 this.setColumnVisible(index, !item.checked);
10254             }     
10255         }
10256         
10257         return true;
10258     },
10259     
10260     setColumnVisible : function(index, visible) {
10261         this.columns[index].hidden = !visible;        
10262         this.updateColumnWidths();
10263     },
10264
10265     /**
10266      * Scrolls the grid to the top
10267      */
10268     scrollToTop : function(){
10269         this.innerBody.dom.scrollTop = 0;
10270         this.innerBody.dom.scrollLeft = 0;
10271     },
10272
10273     // private
10274     syncScroll : function(){
10275         this.syncHeaderScroll();
10276         var mb = this.innerBody.dom;
10277         this.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
10278     },
10279
10280     // private
10281     syncHeaderScroll : function(){
10282         var mb = this.innerBody.dom;
10283         this.innerHd.dom.scrollLeft = mb.scrollLeft;
10284         this.innerHd.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
10285     },
10286     
10287     registerNode : function(n) {
10288         Ext.ux.tree.TreeGrid.superclass.registerNode.call(this, n);
10289         if(!n.uiProvider && !n.isRoot && !n.ui.isTreeGridNodeUI) {
10290             n.ui = new Ext.ux.tree.TreeGridNodeUI(n);
10291         }
10292     }
10293 });
10294
10295 Ext.reg('treegrid', Ext.ux.tree.TreeGrid);